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>
22 #include <sys/types.h>
29 #include "got_error.h"
32 #include "got_lib_gitproto.h"
35 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
39 free_tokens(char **tokens
, size_t ntokens
)
43 for (i
= 0; i
< ntokens
; i
++) {
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
;
56 for (i
= 0; i
< maxtokens
; i
++)
59 for (i
= 0; n
< len
&& i
< maxtokens
; i
++) {
60 while (n
< len
&& isspace((unsigned char)*line
)) {
65 while (*line
!= '\0' && n
< len
&&
66 (!isspace((unsigned char)*line
) || i
== maxtokens
- 1)) {
70 tokens
[i
] = strndup(p
, line
- p
);
71 if (tokens
[i
] == NULL
) {
72 err
= got_error_from_errno("strndup");
75 /* Skip \0 field-delimiter at end of token. */
76 while (line
[0] == '\0' && n
< len
) {
82 err
= got_error_msg(GOT_ERR_BAD_PACKET
,
83 "pkt-line contains too few tokens");
86 free_tokens(tokens
, i
);
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
;
99 /* don't reset *server_capabilities */
101 err
= tokenize_line(tokens
, line
, len
, 2, nitems(tokens
));
108 *refname
= tokens
[1];
110 if (*server_capabilities
== NULL
) {
112 *server_capabilities
= tokens
[2];
113 p
= strrchr(*server_capabilities
, '\n');
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
;
131 /* don't reset *capabilities */
133 err
= tokenize_line(tokens
, line
, len
, 2, nitems(tokens
));
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");
151 if (*capabilities
== NULL
) {
153 *capabilities
= tokens
[2];
154 p
= strrchr(*capabilities
, '\n');
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
;
172 err
= tokenize_line(tokens
, line
, len
, 2, nitems(tokens
));
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");
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
;
204 /* don't reset *capabilities */
206 err
= tokenize_line(tokens
, line
, len
, 3, nitems(tokens
));
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];
219 if (*capabilities
== NULL
) {
221 *capabilities
= tokens
[3];
222 p
= strrchr(*capabilities
, '\n');
232 static const struct got_error
*
233 match_capability(char **my_capabilities
, const char *capa
,
234 const struct got_capability
*mycapa
)
239 equalsign
= strchr(capa
, '=');
241 if (strncmp(capa
, mycapa
->key
, equalsign
- capa
) != 0)
244 if (strcmp(capa
, mycapa
->key
) != 0)
248 if (asprintf(&s
, "%s %s%s%s",
249 *my_capabilities
!= NULL
? *my_capabilities
: "",
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
;
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)
271 colon
= strchr(capa
, ':');
278 return got_error_from_errno("strdup");
280 target
= strdup(colon
+ 1);
281 if (target
== NULL
) {
282 err
= got_error_from_errno("strdup");
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
);
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
;
307 *common_capabilities
= NULL
;
309 capa
= strsep(&capabilities
, " ");
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
)
322 for (i
= 0; i
< ncapa
; i
++) {
323 err
= match_capability(common_capabilities
,
324 capa
, &my_capabilities
[i
]);
330 if (*common_capabilities
== NULL
) {
331 *common_capabilities
= strdup("");
332 if (*common_capabilities
== NULL
)
333 err
= got_error_from_errno("strdup");
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
;
347 if (offset
>= bufsize
|| remain
< 1)
348 return got_error(GOT_ERR_NO_SPACE
);
350 /* Capabilities are hidden behind a NUL byte. */
356 for (i
= 0; i
< ncapa
; i
++) {
357 len
= strlcat(p
, " ", remain
);
359 return got_error(GOT_ERR_NO_SPACE
);
363 len
= strlcat(p
, my_capabilities
[i
].key
, remain
);
365 return got_error(GOT_ERR_NO_SPACE
);
367 *capalen
+= strlen(my_capabilities
[i
].key
);
369 if (my_capabilities
[i
].value
== NULL
)
372 len
= strlcat(p
, "=", remain
);
374 return got_error(GOT_ERR_NO_SPACE
);
378 len
= strlcat(p
, my_capabilities
[i
].value
, remain
);
380 return got_error(GOT_ERR_NO_SPACE
);
382 *capalen
+= strlen(my_capabilities
[i
].value
);
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
;
395 *capabilities
= NULL
;
398 /* Compute number of capabilities on a copy of the input string. */
399 capastr
= strdup(capabilities_str
);
401 return got_error_from_errno("strdup");
403 capa
= strsep(&capastr
, " ");
404 if (capa
&& *capa
!= '\0')
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. */
416 char *key
= NULL
, *value
= NULL
, *equalsign
;
418 capa
= strsep(&capabilities_str
, " ");
424 if (i
>= *ncapabilities
) { /* should not happen */
426 *capabilities
= NULL
;
428 return got_error(GOT_ERR_NO_SPACE
);
433 equalsign
= strchr(capa
, '=');
434 if (equalsign
!= NULL
) {
436 value
= equalsign
+ 1;
439 (*capabilities
)[i
].key
= key
;
440 (*capabilities
)[i
].value
= value
;