Blob


1 /*
2 * Copyright (c) 2024 Omar Polo <op@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <sys/time.h>
18 #include <sys/types.h>
19 #include <sys/socket.h>
21 #include <err.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <limits.h>
25 #include <netdb.h>
26 #include <poll.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <syslog.h>
32 #include <time.h>
33 #include <unistd.h>
35 #include "got_opentemp.h"
36 #include "got_version.h"
38 #include "bufio.h"
39 #include "log.h"
40 #include "utf8d.h"
42 #define USERAGENT "got-notify-http/" GOT_VERSION_STR
44 static int http_timeout = 300; /* 5 minutes in seconds */
46 __dead static void
47 usage(void)
48 {
49 fprintf(stderr, "usage: %s [-c] -r repo -h host -p port -u user path\n",
50 getprogname());
51 exit(1);
52 }
54 static int
55 dial(const char *host, const char *port)
56 {
57 struct addrinfo hints, *res, *res0;
58 const char *cause = NULL;
59 int s, error, save_errno;
61 memset(&hints, 0, sizeof(hints));
62 hints.ai_family = AF_UNSPEC;
63 hints.ai_socktype = SOCK_STREAM;
64 error = getaddrinfo(host, port, &hints, &res0);
65 if (error)
66 errx(1, "failed to resolve %s:%s: %s", host, port,
67 gai_strerror(error));
69 s = -1;
70 for (res = res0; res; res = res->ai_next) {
71 s = socket(res->ai_family, res->ai_socktype,
72 res->ai_protocol);
73 if (s == -1) {
74 cause = "socket";
75 continue;
76 }
78 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
79 cause = "connect";
80 save_errno = errno;
81 close(s);
82 errno = save_errno;
83 s = -1;
84 continue;
85 }
87 break;
88 }
90 freeaddrinfo(res0);
91 if (s == -1)
92 fatal("%s %s:%s", cause, host, port);
93 return s;
94 }
96 static void
97 escape(FILE *fp, const uint8_t *s)
98 {
99 uint32_t codepoint, state;
100 const uint8_t *start = s;
102 state = 0;
103 for (; *s; ++s) {
104 switch (decode(&state, &codepoint, *s)) {
105 case UTF8_ACCEPT:
106 switch (codepoint) {
107 case '"':
108 case '\\':
109 fprintf(fp, "\\%c", *s);
110 break;
111 case '\b':
112 fprintf(fp, "\\b");
113 break;
114 case '\f':
115 fprintf(fp, "\\f");
116 break;
117 case '\n':
118 fprintf(fp, "\\n");
119 break;
120 case '\r':
121 fprintf(fp, "\\r");
122 break;
123 case '\t':
124 fprintf(fp, "\\t");
125 break;
126 default:
127 /* other control characters */
128 if (codepoint < ' ' || codepoint == 0x7F) {
129 fprintf(fp, "\\u%04x", codepoint);
130 break;
132 fwrite(start, 1, s - start + 1, fp);
133 break;
135 start = s + 1;
136 break;
138 case UTF8_REJECT:
139 /* bad UTF-8 sequence; try to recover */
140 fputs("\\uFFFD", fp);
141 state = UTF8_ACCEPT;
142 start = s + 1;
143 break;
148 static void
149 json_field(FILE *fp, const char *key, const char *val, int comma)
151 fprintf(fp, "\"%s\":\"", key);
152 escape(fp, val);
153 fprintf(fp, "\"%s", comma ? "," : "");
156 static void
157 json_date(FILE *fp, const char *key, const char *date, int comma)
159 fprintf(fp, "\"%s\":%s%s", key, date, comma ? "," : "");
162 static void
163 json_author(FILE *fp, const char *type, char *address, int comma)
165 char *gt, *lt, *at, *email, *endname;
167 fprintf(fp, "\"%s\":{", type);
169 gt = strchr(address, '<');
170 if (gt != NULL) {
171 /* long format, e.g. "Omar Polo <op@openbsd.org>" */
173 json_field(fp, "full", address, 1);
175 endname = gt;
176 while (endname > address && endname[-1] == ' ')
177 endname--;
179 *endname = '\0';
180 json_field(fp, "name", address, 1);
182 email = gt + 1;
183 lt = strchr(email, '>');
184 if (lt)
185 *lt = '\0';
187 json_field(fp, "mail", email, 1);
189 at = strchr(email, '@');
190 if (at)
191 *at = '\0';
193 json_field(fp, "user", email, 0);
194 } else {
195 /* short format only shows the username */
196 json_field(fp, "user", address, 0);
199 fprintf(fp, "}%s", comma ? "," : "");
202 static int
203 jsonify_branch_rm(FILE *fp, char *line, const char *repo, const char *user)
205 char *ref, *id;
207 line = strchr(line, ' ');
208 if (line == NULL)
209 errx(1, "invalid branch rm line");
210 line += strspn(line, " ");
212 ref = line;
214 line = strchr(line, ':');
215 if (line == NULL)
216 errx(1, "invalid branch rm line");
217 *line++ = '\0';
218 id = line + strspn(line, " ");
220 fputc('{', fp);
221 json_field(fp, "type", "branch-deleted", 1);
222 json_field(fp, "repo", repo, 1);
223 json_field(fp, "authenticated_user", user, 1);
224 json_field(fp, "ref", ref, 1);
225 json_field(fp, "id", id, 0);
226 fputc('}', fp);
228 return 0;
231 static int
232 jsonify_commit_short(FILE *fp, char *line, const char *repo, const char *user)
234 char *t, *date, *id, *author, *message;
236 t = line;
237 date = t;
238 if ((t = strchr(t, ' ')) == NULL)
239 errx(1, "malformed line");
240 *t++ = '\0';
242 id = t;
243 if ((t = strchr(t, ' ')) == NULL)
244 errx(1, "malformed line");
245 *t++ = '\0';
247 author = t;
248 if ((t = strchr(t, ' ')) == NULL)
249 errx(1, "malformed line");
250 *t++ = '\0';
252 message = t;
254 fprintf(fp, "{\"type\":\"commit\",\"short\":true,");
255 json_field(fp, "repo", repo, 1);
256 json_field(fp, "authenticated_user", user, 1);
257 json_field(fp, "id", id, 1);
258 json_author(fp, "committer", author, 1);
259 json_date(fp, "date", date, 1);
260 json_field(fp, "short_message", message, 0);
261 fprintf(fp, "}");
263 return 0;
266 static int
267 jsonify_commit(FILE *fp, const char *repo, const char *user,
268 char **line, ssize_t *linesize)
270 const char *errstr;
271 char *author = NULL;
272 char *filename, *t;
273 char *l;
274 ssize_t linelen;
275 int parent = 0;
276 int msglen = 0, msgwrote = 0;
277 int n, files = 0;
278 int done = 0;
279 enum {
280 P_FROM,
281 P_VIA,
282 P_DATE,
283 P_PARENT,
284 P_MSGLEN,
285 P_MSG,
286 P_DST,
287 P_SUM,
288 } phase = P_FROM;
290 l = *line;
291 if (strncmp(l, "commit ", 7) != 0)
292 errx(1, "%s: unexpected line: %s", __func__, l);
293 l += 7;
295 fprintf(fp, "{\"type\":\"commit\",\"short\":false,");
296 json_field(fp, "repo", repo, 1);
297 json_field(fp, "authenticated_user", user, 1);
298 json_field(fp, "id", l, 1);
300 while (!done) {
301 if ((linelen = getline(line, linesize, stdin)) == -1)
302 break;
304 if ((*line)[linelen - 1] == '\n')
305 (*line)[--linelen] = '\0';
307 l = *line;
308 switch (phase) {
309 case P_FROM:
310 if (strncmp(l, "from: ", 6) != 0)
311 errx(1, "unexpected from line");
312 l += 6;
314 author = strdup(l);
315 if (author == NULL)
316 fatal("strdup");
318 json_author(fp, "author", l, 1);
320 phase = P_VIA;
321 break;
323 case P_VIA:
324 /* optional */
325 if (!strncmp(l, "via: ", 5)) {
326 l += 5;
327 json_author(fp, "committer", l, 1);
328 phase = P_DATE;
329 break;
332 if (author == NULL) /* impossible */
333 fatalx("from not specified");
334 json_author(fp, "committer", author, 1);
335 free(author);
336 author = NULL;
338 phase = P_DATE;
339 /* fallthrough */
341 case P_DATE:
342 /* optional */
343 if (!strncmp(l, "date: ", 6)) {
344 l += 6;
345 json_date(fp, "date", l, 1);
346 phase = P_PARENT;
347 break;
349 phase = P_PARENT;
350 /* fallthough */
352 case P_PARENT:
353 /* optional - more than one */
354 if (!strncmp(l, "parent ", 7)) {
355 l += 7;
356 l += strcspn(l, ":");
357 l += strspn(l, " ");
359 if (parent == 0) {
360 parent = 1;
361 fprintf(fp, "\"parents\":[");
364 fputc('"', fp);
365 escape(fp, l);
366 fputc('"', fp);
368 break;
370 if (parent != 0) {
371 fprintf(fp, "],");
372 parent = 0;
374 phase = P_MSGLEN;
375 /* fallthrough */
377 case P_MSGLEN:
378 if (strncmp(l, "messagelen: ", 12) != 0)
379 errx(1, "unexpected messagelen line");
380 l += 12;
381 msglen = strtonum(l, 1, INT_MAX, &errstr);
382 if (errstr)
383 errx(1, "message len is %s: %s", errstr, l);
385 msglen++;
387 phase = P_MSG;
388 break;
390 case P_MSG:
391 /*
392 * The commit message is indented with one extra
393 * space which is not accounted for in messagelen,
394 * but we also strip the trailing \n so that
395 * accounts for it.
397 * Since we read line-by-line and there is always
398 * a \n added at the end of the message,
399 * tolerate one byte less than advertised.
400 */
401 if (*l != ' ')
402 errx(1, "unexpected line in commit message");
404 l++; /* skip leading space */
405 linelen--;
407 if (msgwrote == 0 && linelen != 0) {
408 json_field(fp, "short_message", l, 1);
409 fprintf(fp, "\"message\":\"");
410 escape(fp, l);
411 escape(fp, "\n");
412 msgwrote += linelen;
413 } else if (msgwrote != 0) {
414 escape(fp, l);
415 escape(fp, "\n");
418 msglen -= linelen + 1;
419 if (msglen <= 1) {
420 fprintf(fp, "\",");
421 phase = P_DST;
422 break;
424 break;
426 case P_DST:
427 if (files == 0 && !strcmp(l, " "))
428 break;
430 if (files == 0)
431 fputs("\"diffstat\":{\"files\":[", fp);
433 if (*l == '\0') {
434 fputs("],", fp);
435 phase = P_SUM;
436 break;
439 if (*l != ' ')
440 errx(1, "bad diffstat line");
441 l++;
443 if (files != 0)
444 fputc(',', fp);
445 fputc('{', fp);
447 switch (*l) {
448 case 'A':
449 json_field(fp, "action", "added", 1);
450 break;
451 case 'D':
452 json_field(fp, "action", "deleted", 1);
453 break;
454 case 'M':
455 json_field(fp, "action", "modified", 1);
456 break;
457 case 'm':
458 json_field(fp, "action", "mode changed", 1);
459 break;
460 default:
461 json_field(fp, "action", "unknown", 1);
462 break;
465 l++;
466 while (*l == ' ')
467 *l++ = '\0';
468 if (*l == '\0')
469 errx(1, "invalid diffstat: no filename");
471 filename = l;
472 l = strrchr(l, '|');
473 if (l == NULL)
474 errx(1, "invalid diffstat: no separator");
475 t = l - 1;
476 while (t > filename && *t == ' ')
477 *t-- = '\0';
478 json_field(fp, "file", filename, 1);
480 l++;
481 while (*l == ' ')
482 l++;
484 t = strchr(l, '+');
485 if (t == NULL)
486 errx(1, "invalid diffstat: no added counter");
487 *t++ = '\0';
489 n = strtonum(l, 0, INT_MAX, &errstr);
490 if (errstr)
491 errx(1, "added counter is %s: %s", errstr, l);
492 fprintf(fp, "\"added\":%d,", n);
494 l = ++t;
495 while (*l == ' ')
496 l++;
498 t = strchr(l, '-');
499 if (t == NULL)
500 errx(1, "invalid diffstat: no del counter");
501 *t = '\0';
503 n = strtonum(l, 0, INT_MAX, &errstr);
504 if (errstr)
505 errx(1, "del counter is %s: %s", errstr, l);
506 fprintf(fp, "\"removed\":%d", n);
508 fputc('}', fp);
510 files++;
512 break;
514 case P_SUM:
515 fputs("\"total\":{", fp);
517 t = l;
518 l = strchr(l, ' ');
519 if (l == NULL)
520 errx(1, "missing number of additions");
521 *l++ = '\0';
523 n = strtonum(t, 0, INT_MAX, &errstr);
524 if (errstr)
525 errx(1, "add counter is %s: %s", errstr, t);
526 fprintf(fp, "\"added\":%d,", n);
528 l = strchr(l, ',');
529 if (l == NULL)
530 errx(1, "missing number of deletions");
531 l++;
532 while (*l == ' ')
533 l++;
535 t = strchr(l, ' ');
536 if (t == NULL)
537 errx(1, "malformed diffstat sum line");
538 *t = '\0';
540 n = strtonum(l, 0, INT_MAX, &errstr);
541 if (errstr)
542 errx(1, "del counter is %s: %s", errstr, l);
543 fprintf(fp, "\"removed\":%d", n);
545 fputs("}}", fp);
546 done = 1;
547 break;
549 default:
550 /* unreachable */
551 errx(1, "unexpected line: %s", *line);
554 if (ferror(stdin))
555 fatalx("getline");
556 if (!done)
557 fatalx("%s: unexpected EOF", __func__);
558 fputc('}', fp);
560 return 0;
563 static int
564 jsonify_tag(FILE *fp, const char *repo, const char *user,
565 char **line, ssize_t *linesize)
567 const char *errstr;
568 char *l;
569 ssize_t linelen;
570 int msglen = 0, msgwrote = 0;
571 int done = 0;
572 enum {
573 P_FROM,
574 P_DATE,
575 P_OBJECT,
576 P_MSGLEN,
577 P_MSG,
578 } phase = P_FROM;
580 l = *line;
581 if (strncmp(l, "tag ", 4) != 0)
582 errx(1, "%s: unexpected line: %s", __func__, l);
583 l += 4;
585 fputc('{', fp);
586 json_field(fp, "type", "tag", 1);
587 json_field(fp, "repo", repo, 1);
588 json_field(fp, "authenticated_user", user, 1);
589 json_field(fp, "tag", l, 1);
591 while (!done) {
592 if ((linelen = getline(line, linesize, stdin)) == -1)
593 break;
595 if ((*line)[linelen - 1] == '\n')
596 (*line)[--linelen] = '\0';
598 l = *line;
599 switch (phase) {
600 case P_FROM:
601 if (strncmp(l, "from: ", 6) != 0)
602 errx(1, "unexpected from line");
603 l += 6;
605 json_author(fp, "tagger", l, 1);
607 phase = P_DATE;
608 break;
610 case P_DATE:
611 /* optional */
612 if (!strncmp(l, "date: ", 6)) {
613 l += 6;
614 json_date(fp, "date", l, 1);
615 phase = P_OBJECT;
616 break;
618 phase = P_OBJECT;
619 /* fallthough */
621 case P_OBJECT:
622 /* optional */
623 if (!strncmp(l, "object: ", 8)) {
624 char *type, *id;
626 l += 8;
627 type = l;
628 id = strchr(l, ' ');
629 if (id == NULL)
630 errx(1, "malformed tag object line");
631 *id++ = '\0';
633 fputs("\"object\":{", fp);
634 json_field(fp, "type", type, 1);
635 json_field(fp, "id", id, 0);
636 fputs("},", fp);
638 phase = P_MSGLEN;
639 break;
641 phase = P_MSGLEN;
642 /* fallthrough */
644 case P_MSGLEN:
645 if (strncmp(l, "messagelen: ", 12) != 0)
646 errx(1, "unexpected messagelen line");
647 l += 12;
648 msglen = strtonum(l, 1, INT_MAX, &errstr);
649 if (errstr)
650 errx(1, "message len is %s: %s", errstr, l);
652 msglen++;
654 phase = P_MSG;
655 break;
657 case P_MSG:
658 if (*l != ' ')
659 errx(1, "unexpected line in tag message");
661 l++; /* skip leading space */
662 linelen--;
664 if (msgwrote == 0 && linelen != 0) {
665 fprintf(fp, "\"message\":\"");
666 escape(fp, l);
667 escape(fp, "\n");
668 msgwrote += linelen;
669 } else if (msgwrote != 0) {
670 escape(fp, l);
671 escape(fp, "\n");
674 msglen -= linelen + 1;
675 if (msglen <= 0) {
676 fprintf(fp, "\"");
677 done = 1;
678 break;
680 break;
682 default:
683 /* unreachable */
684 errx(1, "unexpected line: %s", *line);
687 if (ferror(stdin))
688 fatal("getline");
689 if (!done)
690 fatalx("%s: unexpected EOF", __func__);
691 fputc('}', fp);
693 return 0;
696 static int
697 jsonify(FILE *fp, const char *repo, const char *user)
699 char *line = NULL;
700 size_t linesize = 0;
701 ssize_t linelen;
702 int needcomma = 0;
704 fprintf(fp, "{\"notifications\":[");
705 while ((linelen = getline(&line, &linesize, stdin)) != -1) {
706 if (line[linelen - 1] == '\n')
707 line[--linelen] = '\0';
709 if (*line == '\0')
710 continue;
712 if (needcomma)
713 fputc(',', fp);
714 needcomma = 1;
716 if (strncmp(line, "Removed refs/heads/", 19) == 0) {
717 if (jsonify_branch_rm(fp, line, repo, user) == -1)
718 fatal("jsonify_branch_rm");
719 continue;
722 if (strncmp(line, "commit ", 7) == 0) {
723 if (jsonify_commit(fp, repo, user,
724 &line, &linesize) == -1)
725 fatal("jsonify_commit");
726 continue;
729 if (*line >= '0' && *line <= '9') {
730 if (jsonify_commit_short(fp, line, repo, user) == -1)
731 fatal("jsonify_commit_short");
732 continue;
735 if (strncmp(line, "tag ", 4) == 0) {
736 if (jsonify_tag(fp, repo, user, &line, &linesize) == -1)
737 fatal("jsonify_tag");
738 continue;
741 errx(1, "unexpected line: %s", line);
743 if (ferror(stdin))
744 fatal("getline");
745 fprintf(fp, "]}");
747 return 0;
750 static char
751 sixet2ch(int c)
753 c &= 0x3F;
755 if (c < 26)
756 return 'A' + c;
757 c -= 26;
758 if (c < 26)
759 return 'a' + c;
760 c -= 26;
761 if (c < 10)
762 return '0' + c;
763 c -= 10;
764 if (c == 0)
765 return '+';
766 if (c == 1)
767 return '/';
769 errx(1, "invalid sixet 0x%x", c);
772 static char *
773 basic_auth(const char *username, const char *password)
775 char *str, *tmp, *end, *s, *p;
776 char buf[3];
777 int len, i, r;
779 r = asprintf(&str, "%s:%s", username, password);
780 if (r == -1)
781 fatal("asprintf");
783 /*
784 * Will need 4 * r/3 bytes to encode the string, plus a
785 * rounding to the next multiple of 4 for padding, plus NUL.
786 */
787 len = 4 * r / 3;
788 len = (len + 3) & ~3;
789 len++;
791 tmp = calloc(1, len);
792 if (tmp == NULL)
793 fatal("calloc");
795 s = str;
796 p = tmp;
797 while (*s != '\0') {
798 memset(buf, 0, sizeof(buf));
799 for (i = 0; i < 3 && *s != '\0'; ++i, ++s)
800 buf[i] = *s;
802 *p++ = sixet2ch(buf[0] >> 2);
803 *p++ = sixet2ch((buf[1] >> 4) | (buf[0] << 4));
804 if (i > 1)
805 *p++ = sixet2ch((buf[1] << 2) | (buf[2] >> 6));
806 if (i > 2)
807 *p++ = sixet2ch(buf[2]);
810 for (end = tmp + len - 1; p < end; ++p)
811 *p = '=';
813 free(str);
814 return tmp;
817 static inline int
818 bufio2poll(struct bufio *bio)
820 int f, ret = 0;
822 /*
823 * If we have data queued up, retry for both POLLIN and POLLOUT
824 * since we want to push this data to the server while still
825 * processing an eventual reply. Otherwise, we could wait
826 * indefinitely for the server to reply without us having
827 * sent the HTTP request completely.
828 */
829 if (bio->wbuf.len)
830 return POLLIN|POLLOUT;
832 f = bufio_ev(bio);
833 if (f & BUFIO_WANT_READ)
834 ret |= POLLIN;
835 if (f & BUFIO_WANT_WRITE)
836 ret |= POLLOUT;
837 return ret;
840 int
841 main(int argc, char **argv)
843 FILE *tmpfp;
844 struct bufio bio;
845 struct pollfd pfd;
846 struct timespec timeout;
847 const char *username;
848 const char *password;
849 const char *timeoutstr;
850 const char *errstr;
851 const char *repo = NULL;
852 const char *host = NULL, *port = NULL, *path = NULL;
853 const char *gotd_auth_user = NULL;
854 char *auth, *line, *spc;
855 size_t len;
856 ssize_t r;
857 off_t paylen;
858 int tls = 0;
859 int response_code = 0, done = 0;
860 int ch, flags, ret, nonstd = 0;
862 #ifndef PROFILE
863 if (pledge("stdio rpath tmppath dns inet", NULL) == -1)
864 err(1, "pledge");
865 #endif
867 log_init(0, LOG_DAEMON);
869 while ((ch = getopt(argc, argv, "ch:p:r:u:")) != -1) {
870 switch (ch) {
871 case 'c':
872 tls = 1;
873 break;
874 case 'h':
875 host = optarg;
876 break;
877 case 'p':
878 port = optarg;
879 break;
880 case 'r':
881 repo = optarg;
882 break;
883 case 'u':
884 gotd_auth_user = optarg;
885 break;
886 default:
887 usage();
890 argc -= optind;
891 argv += optind;
893 if (host == NULL || repo == NULL || gotd_auth_user == NULL || argc != 1)
894 usage();
895 if (tls && port == NULL)
896 port = "443";
897 path = argv[0];
899 username = getenv("GOT_NOTIFY_HTTP_USER");
900 password = getenv("GOT_NOTIFY_HTTP_PASS");
901 if ((username != NULL && password == NULL) ||
902 (username == NULL && password != NULL))
903 fatalx("username or password are not specified");
904 if (username && *password == '\0')
905 fatalx("password can't be empty");
907 /* used by the regression test suite */
908 timeoutstr = getenv("GOT_NOTIFY_TIMEOUT");
909 if (timeoutstr) {
910 http_timeout = strtonum(timeoutstr, 0, 600, &errstr);
911 if (errstr != NULL)
912 fatalx("timeout in seconds is %s: %s",
913 errstr, timeoutstr);
916 memset(&timeout, 0, sizeof(timeout));
917 timeout.tv_sec = http_timeout;
919 tmpfp = got_opentemp();
920 if (tmpfp == NULL)
921 fatal("opentemp");
923 jsonify(tmpfp, repo, gotd_auth_user);
925 paylen = ftello(tmpfp);
926 if (paylen == -1)
927 fatal("ftello");
928 if (fseeko(tmpfp, 0, SEEK_SET) == -1)
929 fatal("fseeko");
931 #ifndef PROFILE
932 /* drop tmppath */
933 if (pledge("stdio rpath dns inet", NULL) == -1)
934 err(1, "pledge");
935 #endif
937 memset(&pfd, 0, sizeof(pfd));
938 pfd.fd = dial(host, port);
940 if ((flags = fcntl(pfd.fd, F_GETFL)) == -1)
941 fatal("fcntl(F_GETFL)");
942 if (fcntl(pfd.fd, F_SETFL, flags | O_NONBLOCK) == -1)
943 fatal("fcntl(F_SETFL)");
945 if (bufio_init(&bio) == -1)
946 fatal("bufio_init");
947 bufio_set_fd(&bio, pfd.fd);
948 if (tls && bufio_starttls(&bio, host, 0, NULL, 0, NULL, 0) == -1)
949 fatal("bufio_starttls");
951 #ifndef PROFILE
952 /* drop rpath dns inet */
953 if (pledge("stdio", NULL) == -1)
954 err(1, "pledge");
955 #endif
957 if ((!tls && strcmp(port, "80") != 0) ||
958 (tls && strcmp(port, "443")) != 0)
959 nonstd = 1;
961 ret = bufio_compose_fmt(&bio,
962 "POST %s HTTP/1.1\r\n"
963 "Host: %s%s%s\r\n"
964 "Content-Type: application/json\r\n"
965 "Content-Length: %lld\r\n"
966 "User-Agent: %s\r\n"
967 "Connection: close\r\n",
968 path, host,
969 nonstd ? ":" : "", nonstd ? port : "",
970 (long long)paylen, USERAGENT);
971 if (ret == -1)
972 fatal("bufio_compose_fmt");
974 if (username) {
975 auth = basic_auth(username, password);
976 ret = bufio_compose_fmt(&bio, "Authorization: basic %s\r\n",
977 auth);
978 if (ret == -1)
979 fatal("bufio_compose_fmt");
980 free(auth);
983 if (bufio_compose(&bio, "\r\n", 2) == -1)
984 fatal("bufio_compose");
986 while (!done) {
987 struct timespec elapsed, start, stop;
988 char buf[BUFSIZ];
990 pfd.events = bufio2poll(&bio);
991 clock_gettime(CLOCK_MONOTONIC, &start);
992 ret = ppoll(&pfd, 1, &timeout, NULL);
993 if (ret == -1)
994 fatal("poll");
995 clock_gettime(CLOCK_MONOTONIC, &stop);
996 timespecsub(&stop, &start, &elapsed);
997 timespecsub(&timeout, &elapsed, &timeout);
998 if (ret == 0 || timeout.tv_sec <= 0)
999 fatalx("timeout");
1001 if (bio.wbuf.len > 0) {
1002 if (bufio_write(&bio) == -1 && errno != EAGAIN)
1003 fatalx("bufio_write: %s", bufio_io_err(&bio));
1006 r = bufio_read(&bio);
1007 if (r == -1 && errno != EAGAIN)
1008 fatalx("bufio_read: %s", bufio_io_err(&bio));
1009 if (r == 0)
1010 fatalx("unexpected EOF from upstream HTTP server");
1012 for (;;) {
1013 line = buf_getdelim(&bio.rbuf, "\r\n", &len);
1014 if (line == NULL)
1015 break;
1016 if (response_code && *line == '\0') {
1018 * end of headers, don't bother
1019 * reading the body, if there is.
1021 done = 1;
1022 break;
1024 if (response_code) {
1025 buf_drain(&bio.rbuf, len);
1026 continue;
1028 spc = strchr(line, ' ');
1029 if (spc == NULL)
1030 fatalx("bad HTTP response from server");
1031 *spc++ = '\0';
1032 if (strcasecmp(line, "HTTP/1.1") != 0)
1033 log_warnx("unexpected protocol: %s", line);
1034 line = spc;
1036 spc = strchr(line, ' ');
1037 if (spc == NULL)
1038 fatalx("bad HTTP response from server");
1039 *spc++ = '\0';
1041 response_code = strtonum(line, 100, 599,
1042 &errstr);
1043 if (errstr != NULL)
1044 log_warnx("response code is %s: %s",
1045 errstr, line);
1047 buf_drain(&bio.rbuf, len);
1049 if (done)
1050 break;
1052 if (!feof(tmpfp) && bio.wbuf.len < sizeof(buf)) {
1053 len = fread(buf, 1, sizeof(buf), tmpfp);
1054 if (len == 0) {
1055 if (ferror(tmpfp))
1056 fatal("fread");
1057 continue;
1060 if (bufio_compose(&bio, buf, len) == -1)
1061 fatal("buf_compose");
1065 if (response_code >= 200 && response_code < 300)
1066 return 0;
1067 fatalx("request failed with code %d", response_code);