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>
28 #include "got_error.h"
31 #include "got_lib_gitproto.h"
34 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
38 free_tokens(char **tokens
, size_t ntokens
)
42 for (i
= 0; i
< ntokens
; i
++) {
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
;
55 for (i
= 0; i
< maxtokens
; i
++)
58 for (i
= 0; n
< len
&& i
< maxtokens
; i
++) {
59 while (n
< len
&& isspace((unsigned char)*line
)) {
64 while (*line
!= '\0' && n
< len
&&
65 (!isspace((unsigned char)*line
) || i
== maxtokens
- 1)) {
69 tokens
[i
] = strndup(p
, line
- p
);
70 if (tokens
[i
] == NULL
) {
71 err
= got_error_from_errno("strndup");
74 /* Skip \0 field-delimiter at end of token. */
75 while (line
[0] == '\0' && n
< len
) {
81 err
= got_error_msg(GOT_ERR_BAD_PACKET
,
82 "pkt-line contains too few tokens");
85 free_tokens(tokens
, i
);
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
;
98 /* don't reset *server_capabilities */
100 err
= tokenize_line(tokens
, line
, len
, 2, nitems(tokens
));
107 *refname
= tokens
[1];
109 if (*server_capabilities
== NULL
) {
111 *server_capabilities
= tokens
[2];
112 p
= strrchr(*server_capabilities
, '\n');
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
;
130 /* don't reset *capabilities */
132 err
= tokenize_line(tokens
, line
, len
, 2, nitems(tokens
));
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");
150 if (*capabilities
== NULL
) {
152 *capabilities
= tokens
[2];
153 p
= strrchr(*capabilities
, '\n');
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
;
171 err
= tokenize_line(tokens
, line
, len
, 2, nitems(tokens
));
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");
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
;
203 /* don't reset *capabilities */
205 err
= tokenize_line(tokens
, line
, len
, 3, nitems(tokens
));
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];
218 if (*capabilities
== NULL
) {
220 *capabilities
= tokens
[3];
221 p
= strrchr(*capabilities
, '\n');
231 static const struct got_error
*
232 match_capability(char **my_capabilities
, const char *capa
,
233 const struct got_capability
*mycapa
)
238 equalsign
= strchr(capa
, '=');
240 if (strncmp(capa
, mycapa
->key
, equalsign
- capa
) != 0)
243 if (strcmp(capa
, mycapa
->key
) != 0)
247 if (asprintf(&s
, "%s %s%s%s",
248 *my_capabilities
!= NULL
? *my_capabilities
: "",
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
;
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)
270 colon
= strchr(capa
, ':');
277 return got_error_from_errno("strdup");
279 target
= strdup(colon
+ 1);
280 if (target
== NULL
) {
281 err
= got_error_from_errno("strdup");
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
);
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
;
306 *common_capabilities
= NULL
;
308 capa
= strsep(&capabilities
, " ");
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
)
321 for (i
= 0; i
< ncapa
; i
++) {
322 err
= match_capability(common_capabilities
,
323 capa
, &my_capabilities
[i
]);
329 if (*common_capabilities
== NULL
) {
330 *common_capabilities
= strdup("");
331 if (*common_capabilities
== NULL
)
332 err
= got_error_from_errno("strdup");
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
;
346 if (offset
>= bufsize
|| remain
< 1)
347 return got_error(GOT_ERR_NO_SPACE
);
349 /* Capabilities are hidden behind a NUL byte. */
355 for (i
= 0; i
< ncapa
; i
++) {
356 len
= strlcat(p
, " ", remain
);
358 return got_error(GOT_ERR_NO_SPACE
);
362 len
= strlcat(p
, my_capabilities
[i
].key
, remain
);
364 return got_error(GOT_ERR_NO_SPACE
);
366 *capalen
+= strlen(my_capabilities
[i
].key
);
368 if (my_capabilities
[i
].value
== NULL
)
371 len
= strlcat(p
, "=", remain
);
373 return got_error(GOT_ERR_NO_SPACE
);
377 len
= strlcat(p
, my_capabilities
[i
].value
, remain
);
379 return got_error(GOT_ERR_NO_SPACE
);
381 *capalen
+= strlen(my_capabilities
[i
].value
);
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
;
394 *capabilities
= NULL
;
397 /* Compute number of capabilities on a copy of the input string. */
398 capastr
= strdup(capabilities_str
);
400 return got_error_from_errno("strdup");
402 capa
= strsep(&capastr
, " ");
403 if (capa
&& *capa
!= '\0')
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. */
415 char *key
= NULL
, *value
= NULL
, *equalsign
;
417 capa
= strsep(&capabilities_str
, " ");
423 if (i
>= *ncapabilities
) { /* should not happen */
425 *capabilities
= NULL
;
427 return got_error(GOT_ERR_NO_SPACE
);
432 equalsign
= strchr(capa
, '=');
433 if (equalsign
!= NULL
) {
435 value
= equalsign
+ 1;
438 (*capabilities
)[i
].key
= key
;
439 (*capabilities
)[i
].value
= value
;