2 * Copyright (c) 2024 Tobias Heider <me@tobhe.de>
3 * Copyright (c) 2022 Omar Polo <op@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/types.h>
21 #include <sys/queue.h>
22 #include <sys/socket.h>
35 #include "got_error.h"
37 #include "got_version.h"
39 #include "got_lib_pkt.h"
43 #define UPLOAD_PACK_ADV "application/x-git-upload-pack-advertisement"
44 #define UPLOAD_PACK_REQ "application/x-git-upload-pack-request"
45 #define UPLOAD_PACK_RES "application/x-git-upload-pack-result"
47 #define GOT_USERAGENT "got/" GOT_VERSION_STR
48 #define MINIMUM(a, b) ((a) < (b) ? (a) : (b))
49 #define hasprfx(str, p) (strncasecmp(str, p, strlen(p)) == 0)
56 bufio_getdelim_sync(struct bufio
*bio
, const char *nl
, size_t *len
)
62 if (r
== -1 && errno
!= EAGAIN
)
63 errx(1, "bufio_read: %s", bufio_io_err(bio
));
64 } while (r
== -1 && errno
== EAGAIN
);
65 return buf_getdelim(&bio
->rbuf
, nl
, len
);
69 bufio_drain_sync(struct bufio
*bio
, void *d
, size_t len
)
75 if (r
== -1 && errno
!= EAGAIN
)
76 errx(1, "bufio_read: %s", bufio_io_err(bio
));
77 } while (r
== -1 && errno
== EAGAIN
);
78 return bufio_drain(bio
, d
, len
);
82 bufio_close_sync(struct bufio
*bio
)
88 if (r
== -1 && errno
!= EAGAIN
)
89 errx(1, "bufio_close: %s", bufio_io_err(bio
));
90 } while (r
== -1 && errno
== EAGAIN
);
94 hexstrtonum(const char *str
, long long min
, long long max
, const char **errstr
)
100 lval
= strtoll(str
, &cp
, 16);
101 if (*str
== '\0' || *cp
!= '\0') {
102 *errstr
= "not a number";
105 if ((errno
== ERANGE
&& (lval
== LONG_MAX
|| lval
== LONG_MIN
)) ||
106 lval
< min
|| lval
> max
) {
107 *errstr
= "out of range";
116 dial(int https
, const char *host
, const char *port
)
118 struct addrinfo hints
, *res
, *res0
;
119 int error
, saved_errno
, fd
= -1;
120 const char *cause
= NULL
;
122 memset(&hints
, 0, sizeof(hints
));
123 hints
.ai_family
= AF_UNSPEC
;
124 hints
.ai_socktype
= SOCK_STREAM
;
125 error
= getaddrinfo(host
, port
, &hints
, &res0
);
127 warnx("%s", gai_strerror(error
));
131 for (res
= res0
; res
; res
= res
->ai_next
) {
132 fd
= socket(res
->ai_family
, res
->ai_socktype
,
139 if (connect(fd
, res
->ai_addr
, res
->ai_addrlen
) == 0)
159 http_open(struct bufio
*bio
, int https
, const char *method
, const char *host
, const char *port
,
160 const char *path
, const char *path_sufx
, const char *query
, const char *ctype
)
162 const char *chdr
= NULL
, *te
= "";
166 if (strcmp(method
, "POST") == 0)
167 te
= "\r\nTransfer-Encoding: chunked\r\n";
170 chdr
= "Content-Type: ";
172 r
= asprintf(&p
, "%s%s/%s%s%s", got_path_is_absolute(path
) ? "" :"/",
173 path
, path_sufx
, query
? "?" : "", query
? query
: "");
177 r
= asprintf(&req
, "%s %s HTTP/1.1\r\n"
179 "Connection: close\r\n"
182 method
, p
, host
, GOT_USERAGENT
,
183 chdr
? chdr
: "", ctype
? ctype
: "", te
);
189 fprintf(stderr
, "%s: request: %s\n", getprogname(), req
);
191 r
= bufio_compose(bio
, req
, r
);
193 err(1, "bufio_compose_fmt");
197 r
= bufio_write(bio
);
198 if (r
== -1 && errno
!= EAGAIN
)
199 errx(1, "bufio_write: %s", bufio_io_err(bio
));
200 } while (bio
->wbuf
.len
!= 0);
206 http_parse_reply(struct bufio
*bio
, int *chunked
, const char *expected_ctype
)
213 line
= bufio_getdelim_sync(bio
, "\r\n", &linelen
);
215 warnx("%s: bufio_getdelim_sync()", __func__
);
220 fprintf(stderr
, "%s: response: %s\n", getprogname(), line
);
222 if ((cp
= strchr(line
, ' ')) == NULL
) {
223 warnx("malformed HTTP response");
228 if (strncmp(cp
, "200 ", 4) != 0) {
229 warnx("malformed HTTP response");
232 buf_drain(&bio
->rbuf
, linelen
);
235 line
= bufio_getdelim_sync(bio
, "\r\n", &linelen
);
237 warnx("%s: bufio_getdelim_sync()", __func__
);
241 buf_drain(&bio
->rbuf
, linelen
);
245 if (hasprfx(line
, "content-type:")) {
246 cp
= strchr(line
, ':') + 1;
247 cp
+= strspn(cp
, " \t");
248 cp
[strcspn(cp
, " \t")] = '\0';
249 if (strcmp(cp
, expected_ctype
) != 0) {
250 warnx("server not using the \"smart\" "
255 if (hasprfx(line
, "transfer-encoding:")) {
256 cp
= strchr(line
, ':') + 1;
257 cp
+= strspn(cp
, " \t");
258 cp
[strcspn(cp
, " \t")] = '\0';
259 if (strcmp(cp
, "chunked") != 0) {
260 warnx("unknown transfer-encoding");
265 buf_drain(&bio
->rbuf
, linelen
);
272 http_read(struct bufio
*bio
, int chunked
, size_t *chunksz
, char *buf
, size_t bufsz
)
277 ssize_t ret
= 0, linelen
;
280 return bufio_drain_sync(bio
, buf
, bufsz
);
285 line
= bufio_getdelim_sync(bio
, "\r\n", &linelen
);
287 buf_drain(&bio
->rbuf
, linelen
);
291 buf_drain(&bio
->rbuf
, linelen
);
292 goto again
; /* was the CRLF after the chunk */
295 *chunksz
= hexstrtonum(line
, 0, INT_MAX
, &errstr
);
296 if (errstr
!= NULL
) {
297 warnx("invalid HTTP chunk: size is %s (%s)",
304 buf_drain(&bio
->rbuf
, linelen
);
307 buf_drain(&bio
->rbuf
, linelen
);
310 r
= bufio_drain_sync(bio
, buf
, MINIMUM(*chunksz
, bufsz
));
325 http_chunk(struct bufio
*bio
, const void *buf
, size_t len
)
329 if (bufio_compose_fmt(bio
, "%zx\r\n", len
) == -1 ||
330 bufio_compose(bio
, buf
, len
) == -1 ||
331 bufio_compose(bio
, "\r\n", 2) == -1)
335 r
= bufio_write(bio
);
336 if (r
== -1 && errno
!= EAGAIN
)
337 errx(1, "bufio_read: %s", bufio_io_err(bio
));
338 } while (bio
->wbuf
.len
!= 0);
344 get_refs(int https
, const char *host
, const char *port
, const char *path
)
347 char buf
[GOT_PKT_MAX
];
348 const struct got_error
*e
;
356 if ((sock
= dial(https
, host
, port
)) == -1)
359 if (bufio_init(&bio
)) {
363 bufio_set_fd(&bio
, sock
);
364 if (https
&& bufio_starttls(&bio
, host
, 0, NULL
, 0, NULL
, 0) == -1) {
365 warnx("bufio_starttls");
369 if (http_open(&bio
, https
, "GET", host
, port
, path
, "info/refs",
370 "service=git-upload-pack", NULL
) == -1)
373 /* Fetch the initial reference announcement from the server. */
374 if (http_parse_reply(&bio
, &chunked
, UPLOAD_PACK_ADV
) == -1)
377 /* skip first pack; why git over http is like this? */
378 r
= http_read(&bio
, chunked
, &chunksz
, buf
, 4);
382 e
= got_pkt_readlen(&skip
, buf
, verbose
);
388 /* TODO: validate it's # service=git-upload-pack\n */
390 r
= http_read(&bio
, chunked
, &chunksz
, buf
,
391 MINIMUM(skip
, sizeof(buf
)));
398 r
= http_read(&bio
, chunked
, &chunksz
, buf
, sizeof(buf
));
405 fwrite(buf
, 1, r
, stdout
);
411 bufio_close_sync(&bio
);
417 upload_request(int https
, const char *host
, const char *port
, const char *path
,
421 char buf
[GOT_PKT_MAX
];
422 const struct got_error
*e
;
430 if ((sock
= dial(https
, host
, port
)) == -1)
433 if (bufio_init(&bio
)) {
437 bufio_set_fd(&bio
, sock
);
438 if (https
&& bufio_starttls(&bio
, host
, 0, NULL
, 0, NULL
, 0) == -1) {
439 warnx("bufio_starttls");
443 /* TODO: can we push this upwards such that get_refs() is covered? */
444 if (pledge("stdio", NULL
) == -1)
447 if (http_open(&bio
, https
, "POST", host
, port
, path
, "git-upload-pack",
448 NULL
, UPLOAD_PACK_REQ
) == -1)
452 * Read have/want lines generated by got-fetch-pack and forward
453 * them to the server in the POST request body.
456 r
= fread(buf
, 1, 4, in
);
460 e
= got_pkt_readlen(&t
, buf
, verbose
);
467 const char *flushpkt
= "0000";
468 if (http_chunk(&bio
, flushpkt
, strlen(flushpkt
)))
470 continue; /* got-fetch-pack will send "done" */
474 warnx("pktline len is too small");
478 r
= fread(buf
+ 4, 1, t
- 4, in
);
482 if (http_chunk(&bio
, buf
, t
))
486 * Once got-fetch-pack is done the server will
487 * send pack file data.
489 if (t
== 9 && strncmp(buf
+ 4, "done\n", 5) == 0) {
490 if (http_chunk(&bio
, NULL
, 0))
496 if (http_parse_reply(&bio
, &chunked
, UPLOAD_PACK_RES
) == -1)
499 /* Fetch pack file data from server. */
501 r
= http_read(&bio
, chunked
, &chunksz
, buf
, sizeof(buf
));
508 fwrite(buf
, 1, r
, stdout
);
513 bufio_close_sync(&bio
);
521 fprintf(stderr
, "usage: %s [-qv] proto host port path\n",
527 main(int argc
, char **argv
)
530 const char *host
, *port
;
536 if (pledge("stdio rpath inet dns unveil", NULL
) == -1)
540 while ((ch
= getopt(argc
, argv
, "qv")) != -1) {
558 https
= strcmp(argv
[0], "https") == 0;
561 if (unveil("/etc/ssl/cert.pem", "r") == -1)
562 err(1, "unveil /etc/ssl/cert.pem");
565 if (pledge("stdio inet dns unveil", NULL
) == -1)
569 if (unveil("gmon.out", "rwc") != 0)
570 err(1, "unveil gmon.out");
572 if (unveil(NULL
, NULL
) == -1)
573 err(1, "unveil NULL");
578 got_path_strip_trailing_slashes(path
);
580 if (get_refs(https
, host
, port
, path
) == -1)
581 errx(1, "failed to get refs");
585 if (poll(&pfd
, 1, INFTIM
) == -1)
588 if ((ch
= fgetc(stdin
)) == EOF
)
592 if (upload_request(https
, host
, port
, path
, stdin
) == -1) {
594 errx(1, "failed to upload request");