fix a typo in CHANGES
[got-portable.git] / lib / gitproto.c
blobf07fa6478be19260d869e069ac0341bc34889666
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;
264 struct got_pathlist_entry *new;
266 /* Need at least "A:B" */
267 if (strlen(capa) < 3)
268 return NULL;
270 colon = strchr(capa, ':');
271 if (colon == NULL)
272 return NULL;
274 *colon = '\0';
275 name = strdup(capa);
276 if (name == NULL)
277 return got_error_from_errno("strdup");
279 target = strdup(colon + 1);
280 if (target == NULL) {
281 err = got_error_from_errno("strdup");
282 goto done;
285 /* We can't validate the ref itself here. The main process will. */
286 err = got_pathlist_insert(&new, symrefs, name, target);
287 if (err == NULL && new == NULL)
288 err = got_error(GOT_ERR_REF_DUP_ENTRY);
289 done:
290 if (err) {
291 free(name);
292 free(target);
294 return err;
297 const struct got_error *
298 got_gitproto_match_capabilities(char **common_capabilities,
299 struct got_pathlist_head *symrefs, char *capabilities,
300 const struct got_capability my_capabilities[], size_t ncapa)
302 const struct got_error *err = NULL;
303 char *capa, *equalsign;
304 size_t i;
306 *common_capabilities = NULL;
307 do {
308 capa = strsep(&capabilities, " ");
309 if (capa == NULL)
310 return NULL;
312 equalsign = strchr(capa, '=');
313 if (equalsign != NULL && symrefs != NULL &&
314 strncmp(capa, "symref", equalsign - capa) == 0) {
315 err = add_symref(symrefs, equalsign + 1);
316 if (err && err->code != GOT_ERR_REF_DUP_ENTRY)
317 break;
318 continue;
321 for (i = 0; i < ncapa; i++) {
322 err = match_capability(common_capabilities,
323 capa, &my_capabilities[i]);
324 if (err)
325 break;
327 } while (capa);
329 if (*common_capabilities == NULL) {
330 *common_capabilities = strdup("");
331 if (*common_capabilities == NULL)
332 err = got_error_from_errno("strdup");
334 return err;
337 const struct got_error *
338 got_gitproto_append_capabilities(size_t *capalen, char *buf, size_t offset,
339 size_t bufsize, const struct got_capability my_capabilities[], size_t ncapa)
341 char *p = buf + offset;
342 size_t i, len, remain = bufsize - offset;
344 *capalen = 0;
346 if (offset >= bufsize || remain < 1)
347 return got_error(GOT_ERR_NO_SPACE);
349 /* Capabilities are hidden behind a NUL byte. */
350 *p = '\0';
351 p++;
352 remain--;
353 *capalen += 1;
355 for (i = 0; i < ncapa; i++) {
356 len = strlcat(p, " ", remain);
357 if (len >= remain)
358 return got_error(GOT_ERR_NO_SPACE);
359 remain -= len;
360 *capalen += 1;
362 len = strlcat(p, my_capabilities[i].key, remain);
363 if (len >= remain)
364 return got_error(GOT_ERR_NO_SPACE);
365 remain -= len;
366 *capalen += strlen(my_capabilities[i].key);
368 if (my_capabilities[i].value == NULL)
369 continue;
371 len = strlcat(p, "=", remain);
372 if (len >= remain)
373 return got_error(GOT_ERR_NO_SPACE);
374 remain -= len;
375 *capalen += 1;
377 len = strlcat(p, my_capabilities[i].value, remain);
378 if (len >= remain)
379 return got_error(GOT_ERR_NO_SPACE);
380 remain -= len;
381 *capalen += strlen(my_capabilities[i].value);
384 return NULL;
387 const struct got_error *
388 got_gitproto_split_capabilities_str(struct got_capability **capabilities,
389 size_t *ncapabilities, char *capabilities_str)
391 char *capastr, *capa;
392 size_t i;
394 *capabilities = NULL;
395 *ncapabilities = 0;
397 /* Compute number of capabilities on a copy of the input string. */
398 capastr = strdup(capabilities_str);
399 if (capastr == NULL)
400 return got_error_from_errno("strdup");
401 do {
402 capa = strsep(&capastr, " ");
403 if (capa && *capa != '\0')
404 (*ncapabilities)++;
405 } while (capa);
406 free(capastr);
408 *capabilities = calloc(*ncapabilities, sizeof(**capabilities));
409 if (*capabilities == NULL)
410 return got_error_from_errno("calloc");
412 /* Modify input string in place, splitting it into key/value tuples. */
413 i = 0;
414 for (;;) {
415 char *key = NULL, *value = NULL, *equalsign;
417 capa = strsep(&capabilities_str, " ");
418 if (capa == NULL)
419 break;
420 if (*capa == '\0')
421 continue;
423 if (i >= *ncapabilities) { /* should not happen */
424 free(*capabilities);
425 *capabilities = NULL;
426 *ncapabilities = 0;
427 return got_error(GOT_ERR_NO_SPACE);
430 key = capa;
432 equalsign = strchr(capa, '=');
433 if (equalsign != NULL) {
434 *equalsign = '\0';
435 value = equalsign + 1;
438 (*capabilities)[i].key = key;
439 (*capabilities)[i].value = value;
440 i++;
443 return NULL;