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
;
265 /* Need at least "A:B" */
266 if (strlen(capa
) < 3)
269 colon
= strchr(capa
, ':');
276 return got_error_from_errno("strdup");
278 target
= strdup(colon
+ 1);
279 if (target
== NULL
) {
280 err
= got_error_from_errno("strdup");
284 /* We can't validate the ref itself here. The main process will. */
285 err
= got_pathlist_append(symrefs
, name
, target
);
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
;
303 *common_capabilities
= NULL
;
305 capa
= strsep(&capabilities
, " ");
309 equalsign
= strchr(capa
, '=');
310 if (equalsign
!= NULL
&& symrefs
!= NULL
&&
311 strncmp(capa
, "symref", equalsign
- capa
) == 0) {
312 err
= add_symref(symrefs
, equalsign
+ 1);
318 for (i
= 0; i
< ncapa
; i
++) {
319 err
= match_capability(common_capabilities
,
320 capa
, &my_capabilities
[i
]);
326 if (*common_capabilities
== NULL
) {
327 *common_capabilities
= strdup("");
328 if (*common_capabilities
== NULL
)
329 err
= got_error_from_errno("strdup");
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
;
343 if (offset
>= bufsize
|| remain
< 1)
344 return got_error(GOT_ERR_NO_SPACE
);
346 /* Capabilities are hidden behind a NUL byte. */
352 for (i
= 0; i
< ncapa
; i
++) {
353 len
= strlcat(p
, " ", remain
);
355 return got_error(GOT_ERR_NO_SPACE
);
359 len
= strlcat(p
, my_capabilities
[i
].key
, remain
);
361 return got_error(GOT_ERR_NO_SPACE
);
363 *capalen
+= strlen(my_capabilities
[i
].key
);
365 if (my_capabilities
[i
].value
== NULL
)
368 len
= strlcat(p
, "=", remain
);
370 return got_error(GOT_ERR_NO_SPACE
);
374 len
= strlcat(p
, my_capabilities
[i
].value
, remain
);
376 return got_error(GOT_ERR_NO_SPACE
);
378 *capalen
+= strlen(my_capabilities
[i
].value
);
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
;
391 *capabilities
= NULL
;
394 /* Compute number of capabilities on a copy of the input string. */
395 capastr
= strdup(capabilities_str
);
397 return got_error_from_errno("strdup");
399 capa
= strsep(&capastr
, " ");
400 if (capa
&& *capa
!= '\0')
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. */
412 char *key
= NULL
, *value
= NULL
, *equalsign
;
414 capa
= strsep(&capabilities_str
, " ");
420 if (i
>= *ncapabilities
) { /* should not happen */
422 *capabilities
= NULL
;
424 return got_error(GOT_ERR_NO_SPACE
);
429 equalsign
= strchr(capa
, '=');
430 if (equalsign
!= NULL
) {
432 value
= equalsign
+ 1;
435 (*capabilities
)[i
].key
= key
;
436 (*capabilities
)[i
].value
= value
;