Blob


1 /*
2 * Copyright (c) 2019 Ori Bernstein <ori@openbsd.org>
3 * Copyright (c) 2021 Stefan Sperling <stsp@openbsd.org>
4 *
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.
8 *
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.
16 */
18 #include <sys/queue.h>
19 #include <sys/types.h>
21 #include <ctype.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
26 #include "got_error.h"
27 #include "got_path.h"
29 #include "got_lib_gitproto.h"
31 #ifndef nitems
32 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
33 #endif
35 static void
36 free_tokens(char **tokens, size_t ntokens)
37 {
38 int i;
40 for (i = 0; i < ntokens; i++) {
41 free(tokens[i]);
42 tokens[i] = NULL;
43 }
44 }
46 static const struct got_error *
47 tokenize_line(char **tokens, char *line, int len, int mintokens, int maxtokens)
48 {
49 const struct got_error *err = NULL;
50 char *p;
51 size_t i, n = 0;
53 for (i = 0; i < maxtokens; i++)
54 tokens[i] = NULL;
56 for (i = 0; n < len && i < maxtokens; i++) {
57 while (n < len && isspace((unsigned char)*line)) {
58 line++;
59 n++;
60 }
61 p = line;
62 while (*line != '\0' && n < len &&
63 (!isspace((unsigned char)*line) || i == maxtokens - 1)) {
64 line++;
65 n++;
66 }
67 tokens[i] = strndup(p, line - p);
68 if (tokens[i] == NULL) {
69 err = got_error_from_errno("strndup");
70 goto done;
71 }
72 /* Skip \0 field-delimiter at end of token. */
73 while (line[0] == '\0' && n < len) {
74 line++;
75 n++;
76 }
77 }
78 if (i < mintokens)
79 err = got_error_msg(GOT_ERR_BAD_PACKET,
80 "pkt-line contains too few tokens");
81 done:
82 if (err)
83 free_tokens(tokens, i);
84 return err;
85 }
87 const struct got_error *
88 got_gitproto_parse_refline(char **id_str, char **refname,
89 char **server_capabilities, char *line, int len)
90 {
91 const struct got_error *err = NULL;
92 char *tokens[3];
94 *id_str = NULL;
95 *refname = NULL;
96 /* don't reset *server_capabilities */
98 err = tokenize_line(tokens, line, len, 2, nitems(tokens));
99 if (err)
100 return err;
102 if (tokens[0])
103 *id_str = tokens[0];
104 if (tokens[1])
105 *refname = tokens[1];
106 if (tokens[2]) {
107 if (*server_capabilities == NULL) {
108 char *p;
109 *server_capabilities = tokens[2];
110 p = strrchr(*server_capabilities, '\n');
111 if (p)
112 *p = '\0';
113 } else
114 free(tokens[2]);
117 return NULL;
120 const struct got_error *
121 got_gitproto_parse_want_line(char **id_str,
122 char **capabilities, char *line, int len)
124 const struct got_error *err = NULL;
125 char *tokens[3];
127 *id_str = NULL;
128 /* don't reset *capabilities */
130 err = tokenize_line(tokens, line, len, 2, nitems(tokens));
131 if (err)
132 return err;
134 if (tokens[0] == NULL) {
135 free_tokens(tokens, nitems(tokens));
136 return got_error_msg(GOT_ERR_BAD_PACKET, "empty want-line");
139 if (strcmp(tokens[0], "want") != 0) {
140 free_tokens(tokens, nitems(tokens));
141 return got_error_msg(GOT_ERR_BAD_PACKET, "bad want-line");
144 free(tokens[0]);
145 if (tokens[1])
146 *id_str = tokens[1];
147 if (tokens[2]) {
148 if (*capabilities == NULL) {
149 char *p;
150 *capabilities = tokens[2];
151 p = strrchr(*capabilities, '\n');
152 if (p)
153 *p = '\0';
154 } else
155 free(tokens[2]);
158 return NULL;
161 const struct got_error *
162 got_gitproto_parse_have_line(char **id_str, char *line, int len)
164 const struct got_error *err = NULL;
165 char *tokens[2];
167 *id_str = NULL;
169 err = tokenize_line(tokens, line, len, 2, nitems(tokens));
170 if (err)
171 return err;
173 if (tokens[0] == NULL) {
174 free_tokens(tokens, nitems(tokens));
175 return got_error_msg(GOT_ERR_BAD_PACKET, "empty have-line");
178 if (strcmp(tokens[0], "have") != 0) {
179 free_tokens(tokens, nitems(tokens));
180 return got_error_msg(GOT_ERR_BAD_PACKET, "bad have-line");
183 free(tokens[0]);
184 if (tokens[1])
185 *id_str = tokens[1];
187 return NULL;
190 const struct got_error *
191 got_gitproto_parse_ref_update_line(char **old_id_str, char **new_id_str,
192 char **refname, char **capabilities, char *line, size_t len)
194 const struct got_error *err = NULL;
195 char *tokens[4];
197 *old_id_str = NULL;
198 *new_id_str = NULL;
199 *refname = NULL;
201 /* don't reset *capabilities */
203 err = tokenize_line(tokens, line, len, 3, nitems(tokens));
204 if (err)
205 return err;
207 if (tokens[0] == NULL || tokens[1] == NULL || tokens[2] == NULL) {
208 free_tokens(tokens, nitems(tokens));
209 return got_error_msg(GOT_ERR_BAD_PACKET, "empty ref-update");
212 *old_id_str = tokens[0];
213 *new_id_str = tokens[1];
214 *refname = tokens[2];
215 if (tokens[3]) {
216 if (*capabilities == NULL) {
217 char *p;
218 *capabilities = tokens[3];
219 p = strrchr(*capabilities, '\n');
220 if (p)
221 *p = '\0';
222 } else
223 free(tokens[3]);
226 return NULL;
229 static const struct got_error *
230 match_capability(char **my_capabilities, const char *capa,
231 const struct got_capability *mycapa)
233 char *equalsign;
234 char *s;
236 equalsign = strchr(capa, '=');
237 if (equalsign) {
238 if (strncmp(capa, mycapa->key, equalsign - capa) != 0)
239 return NULL;
240 } else {
241 if (strcmp(capa, mycapa->key) != 0)
242 return NULL;
245 if (asprintf(&s, "%s %s%s%s",
246 *my_capabilities != NULL ? *my_capabilities : "",
247 mycapa->key,
248 mycapa->value != NULL ? "=" : "",
249 mycapa->value != NULL ? mycapa->value : "") == -1)
250 return got_error_from_errno("asprintf");
252 free(*my_capabilities);
253 *my_capabilities = s;
254 return NULL;
257 static const struct got_error *
258 add_symref(struct got_pathlist_head *symrefs, char *capa)
260 const struct got_error *err = NULL;
261 char *colon, *name = NULL, *target = NULL;
263 /* Need at least "A:B" */
264 if (strlen(capa) < 3)
265 return NULL;
267 colon = strchr(capa, ':');
268 if (colon == NULL)
269 return NULL;
271 *colon = '\0';
272 name = strdup(capa);
273 if (name == NULL)
274 return got_error_from_errno("strdup");
276 target = strdup(colon + 1);
277 if (target == NULL) {
278 err = got_error_from_errno("strdup");
279 goto done;
282 /* We can't validate the ref itself here. The main process will. */
283 err = got_pathlist_append(symrefs, name, target);
284 done:
285 if (err) {
286 free(name);
287 free(target);
289 return err;
292 const struct got_error *
293 got_gitproto_match_capabilities(char **common_capabilities,
294 struct got_pathlist_head *symrefs, char *capabilities,
295 const struct got_capability my_capabilities[], size_t ncapa)
297 const struct got_error *err = NULL;
298 char *capa, *equalsign;
299 size_t i;
301 *common_capabilities = NULL;
302 do {
303 capa = strsep(&capabilities, " ");
304 if (capa == NULL)
305 return NULL;
307 equalsign = strchr(capa, '=');
308 if (equalsign != NULL && symrefs != NULL &&
309 strncmp(capa, "symref", equalsign - capa) == 0) {
310 err = add_symref(symrefs, equalsign + 1);
311 if (err)
312 break;
313 continue;
316 for (i = 0; i < ncapa; i++) {
317 err = match_capability(common_capabilities,
318 capa, &my_capabilities[i]);
319 if (err)
320 break;
322 } while (capa);
324 if (*common_capabilities == NULL) {
325 *common_capabilities = strdup("");
326 if (*common_capabilities == NULL)
327 err = got_error_from_errno("strdup");
329 return err;
332 const struct got_error *
333 got_gitproto_append_capabilities(size_t *capalen, char *buf, size_t offset,
334 size_t bufsize, const struct got_capability my_capabilities[], size_t ncapa)
336 char *p = buf + offset;
337 size_t i, len, remain = bufsize - offset;
339 *capalen = 0;
341 if (offset >= bufsize || remain < 1)
342 return got_error(GOT_ERR_NO_SPACE);
344 /* Capabilities are hidden behind a NUL byte. */
345 *p = '\0';
346 p++;
347 remain--;
348 *capalen += 1;
350 for (i = 0; i < ncapa; i++) {
351 len = strlcat(p, " ", remain);
352 if (len >= remain)
353 return got_error(GOT_ERR_NO_SPACE);
354 remain -= len;
355 *capalen += 1;
357 len = strlcat(p, my_capabilities[i].key, remain);
358 if (len >= remain)
359 return got_error(GOT_ERR_NO_SPACE);
360 remain -= len;
361 *capalen += strlen(my_capabilities[i].key);
363 if (my_capabilities[i].value == NULL)
364 continue;
366 len = strlcat(p, "=", remain);
367 if (len >= remain)
368 return got_error(GOT_ERR_NO_SPACE);
369 remain -= len;
370 *capalen += 1;
372 len = strlcat(p, my_capabilities[i].value, remain);
373 if (len >= remain)
374 return got_error(GOT_ERR_NO_SPACE);
375 remain -= len;
376 *capalen += strlen(my_capabilities[i].value);
379 return NULL;
382 const struct got_error *
383 got_gitproto_split_capabilities_str(struct got_capability **capabilities,
384 size_t *ncapabilities, char *capabilities_str)
386 char *capastr, *capa;
387 size_t i;
389 *capabilities = NULL;
390 *ncapabilities = 0;
392 /* Compute number of capabilities on a copy of the input string. */
393 capastr = strdup(capabilities_str);
394 if (capastr == NULL)
395 return got_error_from_errno("strdup");
396 do {
397 capa = strsep(&capastr, " ");
398 if (capa && *capa != '\0')
399 (*ncapabilities)++;
400 } while (capa);
401 free(capastr);
403 *capabilities = calloc(*ncapabilities, sizeof(**capabilities));
404 if (*capabilities == NULL)
405 return got_error_from_errno("calloc");
407 /* Modify input string in place, splitting it into key/value tuples. */
408 i = 0;
409 for (;;) {
410 char *key = NULL, *value = NULL, *equalsign;
412 capa = strsep(&capabilities_str, " ");
413 if (capa == NULL)
414 break;
415 if (*capa == '\0')
416 continue;
418 if (i >= *ncapabilities) { /* should not happen */
419 free(*capabilities);
420 *capabilities = NULL;
421 *ncapabilities = 0;
422 return got_error(GOT_ERR_NO_SPACE);
425 key = capa;
427 equalsign = strchr(capa, '=');
428 if (equalsign != NULL) {
429 *equalsign = '\0';
430 value = equalsign + 1;
433 (*capabilities)[i].key = key;
434 (*capabilities)[i].value = value;
435 i++;
438 return NULL;