portable: release 0.93
[got-portable.git] / lib / gitproto.c
blobac496f46223ff4b0af6a50e31808ff95280ff8c3
1 /*
2 * Copyright (c) 2019 Ori Bernstein <ori@openbsd.org>
3 * Copyright (c) 2021 Stefan Sperling <stsp@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include "got_compat.h"
20 #include <sys/queue.h>
21 #include <sys/types.h>
23 #include <ctype.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
28 #include "got_error.h"
29 #include "got_path.h"
31 #include "got_lib_gitproto.h"
33 #ifndef nitems
34 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
35 #endif
37 static void
38 free_tokens(char **tokens, size_t ntokens)
40 int i;
42 for (i = 0; i < ntokens; i++) {
43 free(tokens[i]);
44 tokens[i] = NULL;
48 static const struct got_error *
49 tokenize_line(char **tokens, char *line, int len, int mintokens, int maxtokens)
51 const struct got_error *err = NULL;
52 char *p;
53 size_t i, n = 0;
55 for (i = 0; i < maxtokens; i++)
56 tokens[i] = NULL;
58 for (i = 0; n < len && i < maxtokens; i++) {
59 while (n < len && isspace((unsigned char)*line)) {
60 line++;
61 n++;
63 p = line;
64 while (*line != '\0' && n < len &&
65 (!isspace((unsigned char)*line) || i == maxtokens - 1)) {
66 line++;
67 n++;
69 tokens[i] = strndup(p, line - p);
70 if (tokens[i] == NULL) {
71 err = got_error_from_errno("strndup");
72 goto done;
74 /* Skip \0 field-delimiter at end of token. */
75 while (line[0] == '\0' && n < len) {
76 line++;
77 n++;
80 if (i < mintokens)
81 err = got_error_msg(GOT_ERR_BAD_PACKET,
82 "pkt-line contains too few tokens");
83 done:
84 if (err)
85 free_tokens(tokens, i);
86 return err;
89 const struct got_error *
90 got_gitproto_parse_refline(char **id_str, char **refname,
91 char **server_capabilities, char *line, int len)
93 const struct got_error *err = NULL;
94 char *tokens[3];
96 *id_str = NULL;
97 *refname = NULL;
98 /* don't reset *server_capabilities */
100 err = tokenize_line(tokens, line, len, 2, nitems(tokens));
101 if (err)
102 return err;
104 if (tokens[0])
105 *id_str = tokens[0];
106 if (tokens[1])
107 *refname = tokens[1];
108 if (tokens[2]) {
109 if (*server_capabilities == NULL) {
110 char *p;
111 *server_capabilities = tokens[2];
112 p = strrchr(*server_capabilities, '\n');
113 if (p)
114 *p = '\0';
115 } else
116 free(tokens[2]);
119 return NULL;
122 const struct got_error *
123 got_gitproto_parse_want_line(char **id_str,
124 char **capabilities, char *line, int len)
126 const struct got_error *err = NULL;
127 char *tokens[3];
129 *id_str = NULL;
130 /* don't reset *capabilities */
132 err = tokenize_line(tokens, line, len, 2, nitems(tokens));
133 if (err)
134 return err;
136 if (tokens[0] == NULL) {
137 free_tokens(tokens, nitems(tokens));
138 return got_error_msg(GOT_ERR_BAD_PACKET, "empty want-line");
141 if (strcmp(tokens[0], "want") != 0) {
142 free_tokens(tokens, nitems(tokens));
143 return got_error_msg(GOT_ERR_BAD_PACKET, "bad want-line");
146 free(tokens[0]);
147 if (tokens[1])
148 *id_str = tokens[1];
149 if (tokens[2]) {
150 if (*capabilities == NULL) {
151 char *p;
152 *capabilities = tokens[2];
153 p = strrchr(*capabilities, '\n');
154 if (p)
155 *p = '\0';
156 } else
157 free(tokens[2]);
160 return NULL;
163 const struct got_error *
164 got_gitproto_parse_have_line(char **id_str, char *line, int len)
166 const struct got_error *err = NULL;
167 char *tokens[2];
169 *id_str = NULL;
171 err = tokenize_line(tokens, line, len, 2, nitems(tokens));
172 if (err)
173 return err;
175 if (tokens[0] == NULL) {
176 free_tokens(tokens, nitems(tokens));
177 return got_error_msg(GOT_ERR_BAD_PACKET, "empty have-line");
180 if (strcmp(tokens[0], "have") != 0) {
181 free_tokens(tokens, nitems(tokens));
182 return got_error_msg(GOT_ERR_BAD_PACKET, "bad have-line");
185 free(tokens[0]);
186 if (tokens[1])
187 *id_str = tokens[1];
189 return NULL;
192 const struct got_error *
193 got_gitproto_parse_ref_update_line(char **old_id_str, char **new_id_str,
194 char **refname, char **capabilities, char *line, size_t len)
196 const struct got_error *err = NULL;
197 char *tokens[4];
199 *old_id_str = NULL;
200 *new_id_str = NULL;
201 *refname = NULL;
203 /* don't reset *capabilities */
205 err = tokenize_line(tokens, line, len, 3, nitems(tokens));
206 if (err)
207 return err;
209 if (tokens[0] == NULL || tokens[1] == NULL || tokens[2] == NULL) {
210 free_tokens(tokens, nitems(tokens));
211 return got_error_msg(GOT_ERR_BAD_PACKET, "empty ref-update");
214 *old_id_str = tokens[0];
215 *new_id_str = tokens[1];
216 *refname = tokens[2];
217 if (tokens[3]) {
218 if (*capabilities == NULL) {
219 char *p;
220 *capabilities = tokens[3];
221 p = strrchr(*capabilities, '\n');
222 if (p)
223 *p = '\0';
224 } else
225 free(tokens[3]);
228 return NULL;
231 static const struct got_error *
232 match_capability(char **my_capabilities, const char *capa,
233 const struct got_capability *mycapa)
235 char *equalsign;
236 char *s;
238 equalsign = strchr(capa, '=');
239 if (equalsign) {
240 if (strncmp(capa, mycapa->key, equalsign - capa) != 0)
241 return NULL;
242 } else {
243 if (strcmp(capa, mycapa->key) != 0)
244 return NULL;
247 if (asprintf(&s, "%s %s%s%s",
248 *my_capabilities != NULL ? *my_capabilities : "",
249 mycapa->key,
250 mycapa->value != NULL ? "=" : "",
251 mycapa->value != NULL ? mycapa->value : "") == -1)
252 return got_error_from_errno("asprintf");
254 free(*my_capabilities);
255 *my_capabilities = s;
256 return NULL;
259 static const struct got_error *
260 add_symref(struct got_pathlist_head *symrefs, char *capa)
262 const struct got_error *err = NULL;
263 char *colon, *name = NULL, *target = NULL;
265 /* Need at least "A:B" */
266 if (strlen(capa) < 3)
267 return NULL;
269 colon = strchr(capa, ':');
270 if (colon == NULL)
271 return NULL;
273 *colon = '\0';
274 name = strdup(capa);
275 if (name == NULL)
276 return got_error_from_errno("strdup");
278 target = strdup(colon + 1);
279 if (target == NULL) {
280 err = got_error_from_errno("strdup");
281 goto done;
284 /* We can't validate the ref itself here. The main process will. */
285 err = got_pathlist_append(symrefs, name, target);
286 done:
287 if (err) {
288 free(name);
289 free(target);
291 return err;
294 const struct got_error *
295 got_gitproto_match_capabilities(char **common_capabilities,
296 struct got_pathlist_head *symrefs, char *capabilities,
297 const struct got_capability my_capabilities[], size_t ncapa)
299 const struct got_error *err = NULL;
300 char *capa, *equalsign;
301 size_t i;
303 *common_capabilities = NULL;
304 do {
305 capa = strsep(&capabilities, " ");
306 if (capa == NULL)
307 return NULL;
309 equalsign = strchr(capa, '=');
310 if (equalsign != NULL && symrefs != NULL &&
311 strncmp(capa, "symref", equalsign - capa) == 0) {
312 err = add_symref(symrefs, equalsign + 1);
313 if (err)
314 break;
315 continue;
318 for (i = 0; i < ncapa; i++) {
319 err = match_capability(common_capabilities,
320 capa, &my_capabilities[i]);
321 if (err)
322 break;
324 } while (capa);
326 if (*common_capabilities == NULL) {
327 *common_capabilities = strdup("");
328 if (*common_capabilities == NULL)
329 err = got_error_from_errno("strdup");
331 return err;
334 const struct got_error *
335 got_gitproto_append_capabilities(size_t *capalen, char *buf, size_t offset,
336 size_t bufsize, const struct got_capability my_capabilities[], size_t ncapa)
338 char *p = buf + offset;
339 size_t i, len, remain = bufsize - offset;
341 *capalen = 0;
343 if (offset >= bufsize || remain < 1)
344 return got_error(GOT_ERR_NO_SPACE);
346 /* Capabilities are hidden behind a NUL byte. */
347 *p = '\0';
348 p++;
349 remain--;
350 *capalen += 1;
352 for (i = 0; i < ncapa; i++) {
353 len = strlcat(p, " ", remain);
354 if (len >= remain)
355 return got_error(GOT_ERR_NO_SPACE);
356 remain -= len;
357 *capalen += 1;
359 len = strlcat(p, my_capabilities[i].key, remain);
360 if (len >= remain)
361 return got_error(GOT_ERR_NO_SPACE);
362 remain -= len;
363 *capalen += strlen(my_capabilities[i].key);
365 if (my_capabilities[i].value == NULL)
366 continue;
368 len = strlcat(p, "=", remain);
369 if (len >= remain)
370 return got_error(GOT_ERR_NO_SPACE);
371 remain -= len;
372 *capalen += 1;
374 len = strlcat(p, my_capabilities[i].value, remain);
375 if (len >= remain)
376 return got_error(GOT_ERR_NO_SPACE);
377 remain -= len;
378 *capalen += strlen(my_capabilities[i].value);
381 return NULL;
384 const struct got_error *
385 got_gitproto_split_capabilities_str(struct got_capability **capabilities,
386 size_t *ncapabilities, char *capabilities_str)
388 char *capastr, *capa;
389 size_t i;
391 *capabilities = NULL;
392 *ncapabilities = 0;
394 /* Compute number of capabilities on a copy of the input string. */
395 capastr = strdup(capabilities_str);
396 if (capastr == NULL)
397 return got_error_from_errno("strdup");
398 do {
399 capa = strsep(&capastr, " ");
400 if (capa && *capa != '\0')
401 (*ncapabilities)++;
402 } while (capa);
403 free(capastr);
405 *capabilities = calloc(*ncapabilities, sizeof(**capabilities));
406 if (*capabilities == NULL)
407 return got_error_from_errno("calloc");
409 /* Modify input string in place, splitting it into key/value tuples. */
410 i = 0;
411 for (;;) {
412 char *key = NULL, *value = NULL, *equalsign;
414 capa = strsep(&capabilities_str, " ");
415 if (capa == NULL)
416 break;
417 if (*capa == '\0')
418 continue;
420 if (i >= *ncapabilities) { /* should not happen */
421 free(*capabilities);
422 *capabilities = NULL;
423 *ncapabilities = 0;
424 return got_error(GOT_ERR_NO_SPACE);
427 key = capa;
429 equalsign = strchr(capa, '=');
430 if (equalsign != NULL) {
431 *equalsign = '\0';
432 value = equalsign + 1;
435 (*capabilities)[i].key = key;
436 (*capabilities)[i].value = value;
437 i++;
440 return NULL;