make got-read-gotconfig clear its imsgbuf before exit in an error case
[got-portable.git] / lib / gitproto.c
blob9e974d3a22c873dd7105c07ebf092fc62d6cf9cd
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/tree.h>
22 #include <sys/types.h>
24 #include <ctype.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
29 #include "got_error.h"
30 #include "got_path.h"
32 #include "got_lib_gitproto.h"
34 #ifndef nitems
35 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
36 #endif
38 static void
39 free_tokens(char **tokens, size_t ntokens)
41 int i;
43 for (i = 0; i < ntokens; i++) {
44 free(tokens[i]);
45 tokens[i] = NULL;
49 static const struct got_error *
50 tokenize_line(char **tokens, char *line, int len, int mintokens, int maxtokens)
52 const struct got_error *err = NULL;
53 char *p;
54 size_t i, n = 0;
56 for (i = 0; i < maxtokens; i++)
57 tokens[i] = NULL;
59 for (i = 0; n < len && i < maxtokens; i++) {
60 while (n < len && isspace((unsigned char)*line)) {
61 line++;
62 n++;
64 p = line;
65 while (*line != '\0' && n < len &&
66 (!isspace((unsigned char)*line) || i == maxtokens - 1)) {
67 line++;
68 n++;
70 tokens[i] = strndup(p, line - p);
71 if (tokens[i] == NULL) {
72 err = got_error_from_errno("strndup");
73 goto done;
75 /* Skip \0 field-delimiter at end of token. */
76 while (line[0] == '\0' && n < len) {
77 line++;
78 n++;
81 if (i < mintokens)
82 err = got_error_msg(GOT_ERR_BAD_PACKET,
83 "pkt-line contains too few tokens");
84 done:
85 if (err)
86 free_tokens(tokens, i);
87 return err;
90 const struct got_error *
91 got_gitproto_parse_refline(char **id_str, char **refname,
92 char **server_capabilities, char *line, int len)
94 const struct got_error *err = NULL;
95 char *tokens[3];
97 *id_str = NULL;
98 *refname = NULL;
99 /* don't reset *server_capabilities */
101 err = tokenize_line(tokens, line, len, 2, nitems(tokens));
102 if (err)
103 return err;
105 if (tokens[0])
106 *id_str = tokens[0];
107 if (tokens[1])
108 *refname = tokens[1];
109 if (tokens[2]) {
110 if (*server_capabilities == NULL) {
111 char *p;
112 *server_capabilities = tokens[2];
113 p = strrchr(*server_capabilities, '\n');
114 if (p)
115 *p = '\0';
116 } else
117 free(tokens[2]);
120 return NULL;
123 const struct got_error *
124 got_gitproto_parse_want_line(char **id_str,
125 char **capabilities, char *line, int len)
127 const struct got_error *err = NULL;
128 char *tokens[3];
130 *id_str = NULL;
131 /* don't reset *capabilities */
133 err = tokenize_line(tokens, line, len, 2, nitems(tokens));
134 if (err)
135 return err;
137 if (tokens[0] == NULL) {
138 free_tokens(tokens, nitems(tokens));
139 return got_error_msg(GOT_ERR_BAD_PACKET, "empty want-line");
142 if (strcmp(tokens[0], "want") != 0) {
143 free_tokens(tokens, nitems(tokens));
144 return got_error_msg(GOT_ERR_BAD_PACKET, "bad want-line");
147 free(tokens[0]);
148 if (tokens[1])
149 *id_str = tokens[1];
150 if (tokens[2]) {
151 if (*capabilities == NULL) {
152 char *p;
153 *capabilities = tokens[2];
154 p = strrchr(*capabilities, '\n');
155 if (p)
156 *p = '\0';
157 } else
158 free(tokens[2]);
161 return NULL;
164 const struct got_error *
165 got_gitproto_parse_have_line(char **id_str, char *line, int len)
167 const struct got_error *err = NULL;
168 char *tokens[2];
170 *id_str = NULL;
172 err = tokenize_line(tokens, line, len, 2, nitems(tokens));
173 if (err)
174 return err;
176 if (tokens[0] == NULL) {
177 free_tokens(tokens, nitems(tokens));
178 return got_error_msg(GOT_ERR_BAD_PACKET, "empty have-line");
181 if (strcmp(tokens[0], "have") != 0) {
182 free_tokens(tokens, nitems(tokens));
183 return got_error_msg(GOT_ERR_BAD_PACKET, "bad have-line");
186 free(tokens[0]);
187 if (tokens[1])
188 *id_str = tokens[1];
190 return NULL;
193 const struct got_error *
194 got_gitproto_parse_ref_update_line(char **old_id_str, char **new_id_str,
195 char **refname, char **capabilities, char *line, size_t len)
197 const struct got_error *err = NULL;
198 char *tokens[4];
200 *old_id_str = NULL;
201 *new_id_str = NULL;
202 *refname = NULL;
204 /* don't reset *capabilities */
206 err = tokenize_line(tokens, line, len, 3, nitems(tokens));
207 if (err)
208 return err;
210 if (tokens[0] == NULL || tokens[1] == NULL || tokens[2] == NULL) {
211 free_tokens(tokens, nitems(tokens));
212 return got_error_msg(GOT_ERR_BAD_PACKET, "empty ref-update");
215 *old_id_str = tokens[0];
216 *new_id_str = tokens[1];
217 *refname = tokens[2];
218 if (tokens[3]) {
219 if (*capabilities == NULL) {
220 char *p;
221 *capabilities = tokens[3];
222 p = strrchr(*capabilities, '\n');
223 if (p)
224 *p = '\0';
225 } else
226 free(tokens[3]);
229 return NULL;
232 static const struct got_error *
233 match_capability(char **my_capabilities, const char *capa,
234 const struct got_capability *mycapa)
236 char *equalsign;
237 char *s;
239 equalsign = strchr(capa, '=');
240 if (equalsign) {
241 if (strncmp(capa, mycapa->key, equalsign - capa) != 0)
242 return NULL;
243 } else {
244 if (strcmp(capa, mycapa->key) != 0)
245 return NULL;
248 if (asprintf(&s, "%s %s%s%s",
249 *my_capabilities != NULL ? *my_capabilities : "",
250 mycapa->key,
251 mycapa->value != NULL ? "=" : "",
252 mycapa->value != NULL ? mycapa->value : "") == -1)
253 return got_error_from_errno("asprintf");
255 free(*my_capabilities);
256 *my_capabilities = s;
257 return NULL;
260 static const struct got_error *
261 add_symref(struct got_pathlist_head *symrefs, char *capa)
263 const struct got_error *err = NULL;
264 char *colon, *name = NULL, *target = NULL;
265 struct got_pathlist_entry *new;
267 /* Need at least "A:B" */
268 if (strlen(capa) < 3)
269 return NULL;
271 colon = strchr(capa, ':');
272 if (colon == NULL)
273 return NULL;
275 *colon = '\0';
276 name = strdup(capa);
277 if (name == NULL)
278 return got_error_from_errno("strdup");
280 target = strdup(colon + 1);
281 if (target == NULL) {
282 err = got_error_from_errno("strdup");
283 goto done;
286 /* We can't validate the ref itself here. The main process will. */
287 err = got_pathlist_insert(&new, symrefs, name, target);
288 if (err == NULL && new == NULL)
289 err = got_error(GOT_ERR_REF_DUP_ENTRY);
290 done:
291 if (err) {
292 free(name);
293 free(target);
295 return err;
298 const struct got_error *
299 got_gitproto_match_capabilities(char **common_capabilities,
300 struct got_pathlist_head *symrefs, char *capabilities,
301 const struct got_capability my_capabilities[], size_t ncapa)
303 const struct got_error *err = NULL;
304 char *capa, *equalsign;
305 size_t i;
307 *common_capabilities = NULL;
308 do {
309 capa = strsep(&capabilities, " ");
310 if (capa == NULL)
311 return NULL;
313 equalsign = strchr(capa, '=');
314 if (equalsign != NULL && symrefs != NULL &&
315 strncmp(capa, "symref", equalsign - capa) == 0) {
316 err = add_symref(symrefs, equalsign + 1);
317 if (err && err->code != GOT_ERR_REF_DUP_ENTRY)
318 break;
319 continue;
322 for (i = 0; i < ncapa; i++) {
323 err = match_capability(common_capabilities,
324 capa, &my_capabilities[i]);
325 if (err)
326 break;
328 } while (capa);
330 if (*common_capabilities == NULL) {
331 *common_capabilities = strdup("");
332 if (*common_capabilities == NULL)
333 err = got_error_from_errno("strdup");
335 return err;
338 const struct got_error *
339 got_gitproto_append_capabilities(size_t *capalen, char *buf, size_t offset,
340 size_t bufsize, const struct got_capability my_capabilities[], size_t ncapa)
342 char *p = buf + offset;
343 size_t i, len, remain = bufsize - offset;
345 *capalen = 0;
347 if (offset >= bufsize || remain < 1)
348 return got_error(GOT_ERR_NO_SPACE);
350 /* Capabilities are hidden behind a NUL byte. */
351 *p = '\0';
352 p++;
353 remain--;
354 *capalen += 1;
356 for (i = 0; i < ncapa; i++) {
357 len = strlcat(p, " ", remain);
358 if (len >= remain)
359 return got_error(GOT_ERR_NO_SPACE);
360 remain -= len;
361 *capalen += 1;
363 len = strlcat(p, my_capabilities[i].key, remain);
364 if (len >= remain)
365 return got_error(GOT_ERR_NO_SPACE);
366 remain -= len;
367 *capalen += strlen(my_capabilities[i].key);
369 if (my_capabilities[i].value == NULL)
370 continue;
372 len = strlcat(p, "=", remain);
373 if (len >= remain)
374 return got_error(GOT_ERR_NO_SPACE);
375 remain -= len;
376 *capalen += 1;
378 len = strlcat(p, my_capabilities[i].value, remain);
379 if (len >= remain)
380 return got_error(GOT_ERR_NO_SPACE);
381 remain -= len;
382 *capalen += strlen(my_capabilities[i].value);
385 return NULL;
388 const struct got_error *
389 got_gitproto_split_capabilities_str(struct got_capability **capabilities,
390 size_t *ncapabilities, char *capabilities_str)
392 char *capastr, *capa;
393 size_t i;
395 *capabilities = NULL;
396 *ncapabilities = 0;
398 /* Compute number of capabilities on a copy of the input string. */
399 capastr = strdup(capabilities_str);
400 if (capastr == NULL)
401 return got_error_from_errno("strdup");
402 do {
403 capa = strsep(&capastr, " ");
404 if (capa && *capa != '\0')
405 (*ncapabilities)++;
406 } while (capa);
407 free(capastr);
409 *capabilities = calloc(*ncapabilities, sizeof(**capabilities));
410 if (*capabilities == NULL)
411 return got_error_from_errno("calloc");
413 /* Modify input string in place, splitting it into key/value tuples. */
414 i = 0;
415 for (;;) {
416 char *key = NULL, *value = NULL, *equalsign;
418 capa = strsep(&capabilities_str, " ");
419 if (capa == NULL)
420 break;
421 if (*capa == '\0')
422 continue;
424 if (i >= *ncapabilities) { /* should not happen */
425 free(*capabilities);
426 *capabilities = NULL;
427 *ncapabilities = 0;
428 return got_error(GOT_ERR_NO_SPACE);
431 key = capa;
433 equalsign = strchr(capa, '=');
434 if (equalsign != NULL) {
435 *equalsign = '\0';
436 value = equalsign + 1;
439 (*capabilities)[i].key = key;
440 (*capabilities)[i].value = value;
441 i++;
444 return NULL;