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 <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
32 #include "got_opentemp.h"
33 #include "got_version.h"
35 #include "bufio.h"
36 #include "utf8d.h"
38 #define USERAGENT "got-notify-http/" GOT_VERSION_STR
40 static int http_timeout = 300; /* 5 minutes in seconds */
42 __dead static void
43 usage(void)
44 {
45 fprintf(stderr, "usage: %s [-c] -r repo -h host -p port path\n",
46 getprogname());
47 exit(1);
48 }
50 static int
51 dial(const char *host, const char *port)
52 {
53 struct addrinfo hints, *res, *res0;
54 const char *cause = NULL;
55 int s, error, save_errno;
57 memset(&hints, 0, sizeof(hints));
58 hints.ai_family = AF_UNSPEC;
59 hints.ai_socktype = SOCK_STREAM;
60 error = getaddrinfo(host, port, &hints, &res0);
61 if (error)
62 errx(1, "failed to resolve %s:%s: %s", host, port,
63 gai_strerror(error));
65 s = -1;
66 for (res = res0; res; res = res->ai_next) {
67 s = socket(res->ai_family, res->ai_socktype,
68 res->ai_protocol);
69 if (s == -1) {
70 cause = "socket";
71 continue;
72 }
74 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
75 cause = "connect";
76 save_errno = errno;
77 close(s);
78 errno = save_errno;
79 s = -1;
80 continue;
81 }
83 break;
84 }
86 freeaddrinfo(res0);
87 if (s == -1)
88 err(1, "%s", cause);
89 return s;
90 }
92 static void
93 escape(FILE *fp, const uint8_t *s)
94 {
95 uint32_t codepoint, state;
96 const uint8_t *start = s;
98 state = 0;
99 for (; *s; ++s) {
100 switch (decode(&state, &codepoint, *s)) {
101 case UTF8_ACCEPT:
102 switch (codepoint) {
103 case '"':
104 case '\\':
105 fprintf(fp, "\\%c", *s);
106 break;
107 case '\b':
108 fprintf(fp, "\\b");
109 break;
110 case '\f':
111 fprintf(fp, "\\f");
112 break;
113 case '\n':
114 fprintf(fp, "\\n");
115 break;
116 case '\r':
117 fprintf(fp, "\\r");
118 break;
119 case '\t':
120 fprintf(fp, "\\t");
121 break;
122 default:
123 /* other control characters */
124 if (codepoint < ' ' || codepoint == 0x7F) {
125 fprintf(fp, "\\u%04x", codepoint);
126 break;
128 fwrite(start, 1, s - start + 1, fp);
129 break;
131 start = s + 1;
132 break;
134 case UTF8_REJECT:
135 /* bad UTF-8 sequence; try to recover */
136 fputs("\\uFFFD", fp);
137 state = UTF8_ACCEPT;
138 start = s + 1;
139 break;
144 static void
145 json_field(FILE *fp, const char *key, const char *val, int comma)
147 fprintf(fp, "\"%s\":\"", key);
148 escape(fp, val);
149 fprintf(fp, "\"%s", comma ? "," : "");
152 static void
153 json_author(FILE *fp, const char *type, char *address, int comma)
155 char *gt, *lt, *at, *email, *endname;
157 fprintf(fp, "\"%s\":{", type);
159 gt = strchr(address, '<');
160 if (gt != NULL) {
161 /* long format, e.g. "Omar Polo <op@openbsd.org>" */
163 json_field(fp, "full", address, 1);
165 endname = gt;
166 while (endname > address && endname[-1] == ' ')
167 endname--;
169 *endname = '\0';
170 json_field(fp, "name", address, 1);
172 email = gt + 1;
173 lt = strchr(email, '>');
174 if (lt)
175 *lt = '\0';
177 json_field(fp, "mail", email, 1);
179 at = strchr(email, '@');
180 if (at)
181 *at = '\0';
183 json_field(fp, "user", email, 0);
184 } else {
185 /* short format only shows the username */
186 json_field(fp, "user", address, 0);
189 fprintf(fp, "}%s", comma ? "," : "");
192 static int
193 jsonify_branch_rm(FILE *fp, char *line, const char *repo)
195 char *ref, *id;
197 line = strchr(line, ' ');
198 if (line == NULL)
199 errx(1, "invalid branch rm line");
200 line += strspn(line, " ");
202 ref = line;
204 line = strchr(line, ':');
205 if (line == NULL)
206 errx(1, "invalid branch rm line");
207 *line++ = '\0';
208 id = line + strspn(line, " ");
210 fputc('{', fp);
211 json_field(fp, "type", "branch-deleted", 1);
212 json_field(fp, "repo", repo, 1);
213 json_field(fp, "ref", ref, 1);
214 json_field(fp, "id", id, 0);
215 fputc('}', fp);
217 return 0;
220 static int
221 jsonify_commit_short(FILE *fp, char *line, const char *repo)
223 char *t, *date, *id, *author, *message;
225 t = line;
226 date = t;
227 if ((t = strchr(t, ' ')) == NULL)
228 errx(1, "malformed line");
229 *t++ = '\0';
231 id = t;
232 if ((t = strchr(t, ' ')) == NULL)
233 errx(1, "malformed line");
234 *t++ = '\0';
236 author = t;
237 if ((t = strchr(t, ' ')) == NULL)
238 errx(1, "malformed line");
239 *t++ = '\0';
241 message = t;
243 fprintf(fp, "{\"type\":\"commit\",\"short\":true,");
244 json_field(fp, "repo", repo, 1);
245 json_field(fp, "id", id, 1);
246 json_author(fp, "committer", author, 1);
247 json_field(fp, "date", date, 1);
248 json_field(fp, "short_message", message, 0);
249 fprintf(fp, "}");
251 return 0;
254 static int
255 jsonify_commit(FILE *fp, const char *repo, char **line, ssize_t *linesize)
257 const char *errstr;
258 char *author = NULL;
259 char *filename, *t;
260 char *l;
261 ssize_t linelen;
262 int parent = 0;
263 int msglen = 0, msgwrote = 0;
264 int n, files = 0;
265 int done = 0;
266 enum {
267 P_FROM,
268 P_VIA,
269 P_DATE,
270 P_PARENT,
271 P_MSGLEN,
272 P_MSG,
273 P_DST,
274 P_SUM,
275 } phase = P_FROM;
277 l = *line;
278 if (strncmp(l, "commit ", 7) != 0)
279 errx(1, "%s: unexpected line: %s", __func__, l);
280 l += 7;
282 fprintf(fp, "{\"type\":\"commit\",\"short\":false,");
283 json_field(fp, "repo", repo, 1);
284 json_field(fp, "id", l, 1);
286 while (!done) {
287 if ((linelen = getline(line, linesize, stdin)) == -1)
288 break;
290 if ((*line)[linelen - 1] == '\n')
291 (*line)[--linelen] = '\0';
293 l = *line;
294 switch (phase) {
295 case P_FROM:
296 if (strncmp(l, "from: ", 6) != 0)
297 errx(1, "unexpected from line");
298 l += 6;
300 author = strdup(l);
301 if (author == NULL)
302 err(1, "strdup");
304 json_author(fp, "author", l, 1);
306 phase = P_VIA;
307 break;
309 case P_VIA:
310 /* optional */
311 if (!strncmp(l, "via: ", 5)) {
312 l += 5;
313 json_author(fp, "committer", l, 1);
314 phase = P_DATE;
315 break;
318 if (author == NULL) /* impossible */
319 err(1, "from not specified");
320 json_author(fp, "committer", author, 1);
321 free(author);
322 author = NULL;
324 phase = P_DATE;
325 /* fallthrough */
327 case P_DATE:
328 /* optional */
329 if (!strncmp(l, "date: ", 6)) {
330 l += 6;
331 json_field(fp, "date", l, 1);
332 phase = P_PARENT;
333 break;
335 phase = P_PARENT;
336 /* fallthough */
338 case P_PARENT:
339 /* optional - more than one */
340 if (!strncmp(l, "parent ", 7)) {
341 l += 7;
342 l += strcspn(l, ":");
343 l += strspn(l, " ");
345 if (parent == 0) {
346 parent = 1;
347 fprintf(fp, "\"parents\":[");
350 fputc('"', fp);
351 escape(fp, l);
352 fputc('"', fp);
354 break;
356 if (parent != 0) {
357 fprintf(fp, "],");
358 parent = 0;
360 phase = P_MSGLEN;
361 /* fallthrough */
363 case P_MSGLEN:
364 if (strncmp(l, "messagelen: ", 12) != 0)
365 errx(1, "unexpected messagelen line");
366 l += 12;
367 msglen = strtonum(l, 1, INT_MAX, &errstr);
368 if (errstr)
369 errx(1, "message len is %s: %s", errstr, l);
371 msglen++;
373 phase = P_MSG;
374 break;
376 case P_MSG:
377 /*
378 * The commit message is indented with one extra
379 * space which is not accounted for in messagelen,
380 * but we also strip the trailing \n so that
381 * accounts for it.
383 * Since we read line-by-line and there is always
384 * a \n added at the end of the message,
385 * tolerate one byte less than advertised.
386 */
387 if (*l != ' ')
388 errx(1, "unexpected line in commit message");
390 l++; /* skip leading space */
391 linelen--;
393 if (msgwrote == 0 && linelen != 0) {
394 json_field(fp, "short_message", l, 1);
395 fprintf(fp, "\"message\":\"");
396 escape(fp, l);
397 escape(fp, "\n");
398 msgwrote += linelen;
399 } else if (msgwrote != 0) {
400 escape(fp, l);
401 escape(fp, "\n");
404 msglen -= linelen + 1;
405 if (msglen <= 1) {
406 fprintf(fp, "\",");
407 phase = P_DST;
408 break;
410 break;
412 case P_DST:
413 if (files == 0 && !strcmp(l, " "))
414 break;
416 if (files == 0)
417 fputs("\"diffstat\":{\"files\":[", fp);
419 if (*l == '\0') {
420 fputs("],", fp);
421 phase = P_SUM;
422 break;
425 if (*l != ' ')
426 errx(1, "bad diffstat line");
427 l++;
429 if (files != 0)
430 fputc(',', fp);
431 fputc('{', fp);
433 switch (*l) {
434 case 'A':
435 json_field(fp, "action", "added", 1);
436 break;
437 case 'D':
438 json_field(fp, "action", "deleted", 1);
439 break;
440 case 'M':
441 json_field(fp, "action", "modified", 1);
442 break;
443 case 'm':
444 json_field(fp, "action", "mode changed", 1);
445 break;
446 default:
447 json_field(fp, "action", "unknown", 1);
448 break;
451 l++;
452 while (*l == ' ')
453 *l++ = '\0';
454 if (*l == '\0')
455 errx(1, "invalid diffstat: no filename");
457 filename = l;
458 l = strrchr(l, '|');
459 if (l == NULL)
460 errx(1, "invalid diffstat: no separator");
461 t = l - 1;
462 while (t > filename && *t == ' ')
463 *t-- = '\0';
464 json_field(fp, "file", filename, 1);
466 l++;
467 while (*l == ' ')
468 l++;
470 t = strchr(l, '+');
471 if (t == NULL)
472 errx(1, "invalid diffstat: no added counter");
473 *t++ = '\0';
475 n = strtonum(l, 0, INT_MAX, &errstr);
476 if (errstr)
477 errx(1, "added counter is %s: %s", errstr, l);
478 fprintf(fp, "\"added\":%d,", n);
480 l = ++t;
481 while (*l == ' ')
482 l++;
484 t = strchr(l, '-');
485 if (t == NULL)
486 errx(1, "invalid diffstat: no del counter");
487 *t = '\0';
489 n = strtonum(l, 0, INT_MAX, &errstr);
490 if (errstr)
491 errx(1, "del counter is %s: %s", errstr, l);
492 fprintf(fp, "\"removed\":%d", n);
494 fputc('}', fp);
496 files++;
498 break;
500 case P_SUM:
501 fputs("\"total\":{", fp);
503 t = l;
504 l = strchr(l, ' ');
505 if (l == NULL)
506 errx(1, "missing number of additions");
507 *l++ = '\0';
509 n = strtonum(t, 0, INT_MAX, &errstr);
510 if (errstr)
511 errx(1, "add counter is %s: %s", errstr, t);
512 fprintf(fp, "\"added\":%d,", n);
514 l = strchr(l, ',');
515 if (l == NULL)
516 errx(1, "missing number of deletions");
517 l++;
518 while (*l == ' ')
519 l++;
521 t = strchr(l, ' ');
522 if (t == NULL)
523 errx(1, "malformed diffstat sum line");
524 *t = '\0';
526 n = strtonum(l, 0, INT_MAX, &errstr);
527 if (errstr)
528 errx(1, "del counter is %s: %s", errstr, l);
529 fprintf(fp, "\"removed\":%d", n);
531 fputs("}}", fp);
532 done = 1;
533 break;
535 default:
536 /* unreachable */
537 errx(1, "unexpected line: %s", *line);
540 if (ferror(stdin))
541 err(1, "getline");
542 if (!done)
543 errx(1, "unexpected EOF");
544 fputc('}', fp);
546 return 0;
549 static int
550 jsonify_tag(FILE *fp, const char *repo, char **line, ssize_t *linesize)
552 const char *errstr;
553 char *l;
554 ssize_t linelen;
555 int msglen = 0, msgwrote = 0;
556 int done = 0;
557 enum {
558 P_FROM,
559 P_DATE,
560 P_OBJECT,
561 P_MSGLEN,
562 P_MSG,
563 } phase = P_FROM;
565 l = *line;
566 if (strncmp(l, "tag ", 4) != 0)
567 errx(1, "%s: unexpected line: %s", __func__, l);
568 l += 4;
570 fputc('{', fp);
571 json_field(fp, "type", "tag", 1);
572 json_field(fp, "repo", repo, 1);
573 json_field(fp, "tag", l, 1);
575 while (!done) {
576 if ((linelen = getline(line, linesize, stdin)) == -1)
577 break;
579 if ((*line)[linelen - 1] == '\n')
580 (*line)[--linelen] = '\0';
582 l = *line;
583 switch (phase) {
584 case P_FROM:
585 if (strncmp(l, "from: ", 6) != 0)
586 errx(1, "unexpected from line");
587 l += 6;
589 json_author(fp, "tagger", l, 1);
591 phase = P_DATE;
592 break;
594 case P_DATE:
595 /* optional */
596 if (!strncmp(l, "date: ", 6)) {
597 l += 6;
598 json_field(fp, "date", l, 1);
599 phase = P_OBJECT;
600 break;
602 phase = P_OBJECT;
603 /* fallthough */
605 case P_OBJECT:
606 /* optional */
607 if (!strncmp(l, "object: ", 8)) {
608 char *type, *id;
610 l += 8;
611 type = l;
612 id = strchr(l, ' ');
613 if (id == NULL)
614 errx(1, "malformed tag object line");
615 *id++ = '\0';
617 fputs("\"object\":{", fp);
618 json_field(fp, "type", type, 1);
619 json_field(fp, "id", id, 0);
620 fputs("},", fp);
622 phase = P_MSGLEN;
623 break;
625 phase = P_MSGLEN;
626 /* fallthrough */
628 case P_MSGLEN:
629 if (strncmp(l, "messagelen: ", 12) != 0)
630 errx(1, "unexpected messagelen line");
631 l += 12;
632 msglen = strtonum(l, 1, INT_MAX, &errstr);
633 if (errstr)
634 errx(1, "message len is %s: %s", errstr, l);
636 msglen++;
638 phase = P_MSG;
639 break;
641 case P_MSG:
642 if (*l != ' ')
643 errx(1, "unexpected line in tag message");
645 l++; /* skip leading space */
646 linelen--;
648 if (msgwrote == 0 && linelen != 0) {
649 fprintf(fp, "\"message\":\"");
650 escape(fp, l);
651 escape(fp, "\n");
652 msgwrote += linelen;
653 } else if (msgwrote != 0) {
654 escape(fp, l);
655 escape(fp, "\n");
658 msglen -= linelen + 1;
659 if (msglen <= 0) {
660 fprintf(fp, "\"");
661 done = 1;
662 break;
664 break;
666 default:
667 /* unreachable */
668 errx(1, "unexpected line: %s", *line);
671 if (ferror(stdin))
672 err(1, "getline");
673 if (!done)
674 errx(1, "unexpected EOF");
675 fputc('}', fp);
677 return 0;
680 static int
681 jsonify(FILE *fp, const char *repo)
683 char *line = NULL;
684 size_t linesize = 0;
685 ssize_t linelen;
686 int needcomma = 0;
688 fprintf(fp, "{\"notifications\":[");
689 while ((linelen = getline(&line, &linesize, stdin)) != -1) {
690 if (line[linelen - 1] == '\n')
691 line[--linelen] = '\0';
693 if (*line == '\0')
694 continue;
696 if (needcomma)
697 fputc(',', fp);
698 needcomma = 1;
700 if (strncmp(line, "Removed refs/heads/", 19) == 0) {
701 if (jsonify_branch_rm(fp, line, repo) == -1)
702 err(1, "jsonify_branch_rm");
703 continue;
706 if (strncmp(line, "commit ", 7) == 0) {
707 if (jsonify_commit(fp, repo, &line, &linesize) == -1)
708 err(1, "jsonify_commit");
709 continue;
712 if (*line >= '0' && *line <= '9') {
713 if (jsonify_commit_short(fp, line, repo) == -1)
714 err(1, "jsonify_commit_short");
715 continue;
718 if (strncmp(line, "tag ", 4) == 0) {
719 if (jsonify_tag(fp, repo, &line, &linesize) == -1)
720 err(1, "jsonify_tag");
721 continue;
724 errx(1, "unexpected line: %s", line);
726 if (ferror(stdin))
727 err(1, "getline");
728 fprintf(fp, "]}");
730 return 0;
733 static char
734 sixet2ch(int c)
736 c &= 0x3F;
738 if (c < 26)
739 return 'A' + c;
740 c -= 26;
741 if (c < 26)
742 return 'a' + c;
743 c -= 26;
744 if (c < 10)
745 return '0' + c;
746 c -= 10;
747 if (c == 0)
748 return '+';
749 if (c == 1)
750 return '/';
752 errx(1, "invalid sixet 0x%x", c);
755 static char *
756 basic_auth(const char *username, const char *password)
758 char *str, *tmp, *end, *s, *p;
759 char buf[3];
760 int len, i, r;
762 r = asprintf(&str, "%s:%s", username, password);
763 if (r == -1)
764 err(1, "asprintf");
766 /*
767 * Will need 4 * r/3 bytes to encode the string, plus a
768 * rounding to the next multiple of 4 for padding, plus NUL.
769 */
770 len = 4 * r / 3;
771 len = (len + 3) & ~3;
772 len++;
774 tmp = calloc(1, len);
775 if (tmp == NULL)
776 err(1, "malloc");
778 s = str;
779 p = tmp;
780 while (*s != '\0') {
781 memset(buf, 0, sizeof(buf));
782 for (i = 0; i < 3 && *s != '\0'; ++i, ++s)
783 buf[i] = *s;
785 *p++ = sixet2ch(buf[0] >> 2);
786 *p++ = sixet2ch((buf[1] >> 4) | (buf[0] << 4));
787 if (i > 1)
788 *p++ = sixet2ch((buf[1] << 2) | (buf[2] >> 6));
789 if (i > 2)
790 *p++ = sixet2ch(buf[2]);
793 for (end = tmp + len - 1; p < end; ++p)
794 *p = '=';
796 free(str);
797 return tmp;
800 static inline int
801 bufio2poll(struct bufio *bio)
803 int f, ret = 0;
805 f = bufio_ev(bio);
806 if (f & BUFIO_WANT_READ)
807 ret |= POLLIN;
808 if (f & BUFIO_WANT_WRITE)
809 ret |= POLLOUT;
810 return ret;
813 int
814 main(int argc, char **argv)
816 FILE *tmpfp;
817 struct bufio bio;
818 struct pollfd pfd;
819 struct timespec timeout;
820 const char *username;
821 const char *password;
822 const char *timeoutstr;
823 const char *errstr;
824 const char *repo = NULL;
825 const char *host = NULL, *port = NULL, *path = NULL;
826 char *auth, *line, *spc;
827 size_t len;
828 ssize_t r;
829 off_t paylen;
830 int tls = 0;
831 int response_code = 0, done = 0;
832 int ch, flags, ret, nonstd = 0;
834 #ifndef PROFILE
835 if (pledge("stdio rpath tmppath dns inet", NULL) == -1)
836 err(1, "pledge");
837 #endif
839 while ((ch = getopt(argc, argv, "ch:p:r:")) != -1) {
840 switch (ch) {
841 case 'c':
842 tls = 1;
843 break;
844 case 'h':
845 host = optarg;
846 break;
847 case 'p':
848 port = optarg;
849 break;
850 case 'r':
851 repo = optarg;
852 break;
853 default:
854 usage();
857 argc -= optind;
858 argv += optind;
860 if (host == NULL || repo == NULL || argc != 1)
861 usage();
862 if (tls && port == NULL)
863 port = "443";
864 path = argv[0];
866 username = getenv("GOT_NOTIFY_HTTP_USER");
867 password = getenv("GOT_NOTIFY_HTTP_PASS");
868 if ((username != NULL && password == NULL) ||
869 (username == NULL && password != NULL))
870 errx(1, "username or password are not specified");
871 if (username && *password == '\0')
872 errx(1, "password can't be empty");
874 /* used by the regression test suite */
875 timeoutstr = getenv("GOT_NOTIFY_TIMEOUT");
876 if (timeoutstr) {
877 http_timeout = strtonum(timeoutstr, 0, 600, &errstr);
878 if (errstr != NULL)
879 errx(1, "timeout in seconds is %s: %s",
880 errstr, timeoutstr);
883 memset(&timeout, 0, sizeof(timeout));
884 timeout.tv_sec = http_timeout;
886 tmpfp = got_opentemp();
887 if (tmpfp == NULL)
888 err(1, "opentemp");
890 jsonify(tmpfp, repo);
892 paylen = ftello(tmpfp);
893 if (paylen == -1)
894 err(1, "ftello");
895 if (fseeko(tmpfp, 0, SEEK_SET) == -1)
896 err(1, "fseeko");
898 #ifndef PROFILE
899 /* drop tmppath */
900 if (pledge("stdio rpath dns inet", NULL) == -1)
901 err(1, "pledge");
902 #endif
904 memset(&pfd, 0, sizeof(pfd));
905 pfd.fd = dial(host, port);
907 if ((flags = fcntl(pfd.fd, F_GETFL)) == -1)
908 err(1, "fcntl(F_GETFL)");
909 if (fcntl(pfd.fd, F_SETFL, flags | O_NONBLOCK) == -1)
910 err(1, "fcntl(F_SETFL)");
912 if (bufio_init(&bio) == -1)
913 err(1, "bufio_init");
914 bufio_set_fd(&bio, pfd.fd);
915 if (tls && bufio_starttls(&bio, host, 0, NULL, 0, NULL, 0) == -1)
916 err(1, "bufio_starttls");
918 #ifndef PROFILE
919 /* drop rpath dns inet */
920 if (pledge("stdio", NULL) == -1)
921 err(1, "pledge");
922 #endif
924 if ((!tls && strcmp(port, "80") != 0) ||
925 (tls && strcmp(port, "443")) != 0)
926 nonstd = 1;
928 ret = bufio_compose_fmt(&bio,
929 "POST %s HTTP/1.1\r\n"
930 "Host: %s%s%s\r\n"
931 "Content-Type: application/json\r\n"
932 "Content-Length: %lld\r\n"
933 "User-Agent: %s\r\n"
934 "Connection: close\r\n",
935 path, host,
936 nonstd ? ":" : "", nonstd ? port : "",
937 (long long)paylen, USERAGENT);
938 if (ret == -1)
939 err(1, "bufio_compose_fmt");
941 if (username) {
942 auth = basic_auth(username, password);
943 ret = bufio_compose_fmt(&bio, "Authorization: basic %s\r\n",
944 auth);
945 if (ret == -1)
946 err(1, "bufio_compose_fmt");
947 free(auth);
950 if (bufio_compose(&bio, "\r\n", 2) == -1)
951 err(1, "bufio_compose");
953 while (!done) {
954 struct timespec elapsed, start, stop;
955 char buf[BUFSIZ];
957 pfd.events = bufio2poll(&bio);
958 clock_gettime(CLOCK_MONOTONIC, &start);
959 ret = ppoll(&pfd, 1, &timeout, NULL);
960 if (ret == -1)
961 err(1, "poll");
962 clock_gettime(CLOCK_MONOTONIC, &stop);
963 timespecsub(&stop, &start, &elapsed);
964 timespecsub(&timeout, &elapsed, &timeout);
965 if (ret == 0 || timeout.tv_sec <= 0)
966 errx(1, "timeout");
968 if (bio.wbuf.len > 0 && (pfd.revents & POLLOUT)) {
969 if (bufio_write(&bio) == -1 && errno != EAGAIN)
970 errx(1, "bufio_write: %s", bufio_io_err(&bio));
972 if (pfd.revents & POLLIN) {
973 r = bufio_read(&bio);
974 if (r == -1 && errno != EAGAIN)
975 errx(1, "bufio_read: %s", bufio_io_err(&bio));
976 if (r == 0)
977 errx(1, "unexpected EOF");
979 for (;;) {
980 line = buf_getdelim(&bio.rbuf, "\r\n", &len);
981 if (line == NULL)
982 break;
983 if (response_code && *line == '\0') {
984 /*
985 * end of headers, don't bother
986 * reading the body, if there is.
987 */
988 done = 1;
989 break;
991 if (response_code) {
992 buf_drain(&bio.rbuf, len);
993 continue;
995 spc = strchr(line, ' ');
996 if (spc == NULL)
997 errx(1, "bad reply");
998 *spc++ = '\0';
999 if (strcasecmp(line, "HTTP/1.1") != 0)
1000 errx(1, "unexpected protocol: %s",
1001 line);
1002 line = spc;
1004 spc = strchr(line, ' ');
1005 if (spc == NULL)
1006 errx(1, "bad reply");
1007 *spc++ = '\0';
1009 response_code = strtonum(line, 100, 599,
1010 &errstr);
1011 if (errstr != NULL)
1012 errx(1, "response code is %s: %s",
1013 errstr, line);
1015 buf_drain(&bio.rbuf, len);
1017 if (done)
1018 break;
1021 if (!feof(tmpfp) && bio.wbuf.len < sizeof(buf)) {
1022 len = fread(buf, 1, sizeof(buf), tmpfp);
1023 if (len == 0) {
1024 if (ferror(tmpfp))
1025 err(1, "fread");
1026 continue;
1029 if (bufio_compose(&bio, buf, len) == -1)
1030 err(1, "buf_compose");
1034 if (response_code >= 200 && response_code < 300)
1035 return 0;
1036 errx(1, "request failed with code %d", response_code);