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 <unistd.h>
34 #include "got_opentemp.h"
35 #include "got_version.h"
37 #include "bufio.h"
38 #include "log.h"
39 #include "utf8d.h"
41 #define USERAGENT "got-notify-http/" GOT_VERSION_STR
43 static int http_timeout = 300; /* 5 minutes in seconds */
45 __dead static void
46 usage(void)
47 {
48 fprintf(stderr, "usage: %s [-c] -r repo -h host -p port path\n",
49 getprogname());
50 exit(1);
51 }
53 static int
54 dial(const char *host, const char *port)
55 {
56 struct addrinfo hints, *res, *res0;
57 const char *cause = NULL;
58 int s, error, save_errno;
60 memset(&hints, 0, sizeof(hints));
61 hints.ai_family = AF_UNSPEC;
62 hints.ai_socktype = SOCK_STREAM;
63 error = getaddrinfo(host, port, &hints, &res0);
64 if (error)
65 errx(1, "failed to resolve %s:%s: %s", host, port,
66 gai_strerror(error));
68 s = -1;
69 for (res = res0; res; res = res->ai_next) {
70 s = socket(res->ai_family, res->ai_socktype,
71 res->ai_protocol);
72 if (s == -1) {
73 cause = "socket";
74 continue;
75 }
77 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
78 cause = "connect";
79 save_errno = errno;
80 close(s);
81 errno = save_errno;
82 s = -1;
83 continue;
84 }
86 break;
87 }
89 freeaddrinfo(res0);
90 if (s == -1)
91 err(1, "%s", cause);
92 return s;
93 }
95 static void
96 escape(FILE *fp, const uint8_t *s)
97 {
98 uint32_t codepoint, state;
99 const uint8_t *start = s;
101 state = 0;
102 for (; *s; ++s) {
103 switch (decode(&state, &codepoint, *s)) {
104 case UTF8_ACCEPT:
105 switch (codepoint) {
106 case '"':
107 case '\\':
108 fprintf(fp, "\\%c", *s);
109 break;
110 case '\b':
111 fprintf(fp, "\\b");
112 break;
113 case '\f':
114 fprintf(fp, "\\f");
115 break;
116 case '\n':
117 fprintf(fp, "\\n");
118 break;
119 case '\r':
120 fprintf(fp, "\\r");
121 break;
122 case '\t':
123 fprintf(fp, "\\t");
124 break;
125 default:
126 /* other control characters */
127 if (codepoint < ' ' || codepoint == 0x7F) {
128 fprintf(fp, "\\u%04x", codepoint);
129 break;
131 fwrite(start, 1, s - start + 1, fp);
132 break;
134 start = s + 1;
135 break;
137 case UTF8_REJECT:
138 /* bad UTF-8 sequence; try to recover */
139 fputs("\\uFFFD", fp);
140 state = UTF8_ACCEPT;
141 start = s + 1;
142 break;
147 static void
148 json_field(FILE *fp, const char *key, const char *val, int comma)
150 fprintf(fp, "\"%s\":\"", key);
151 escape(fp, val);
152 fprintf(fp, "\"%s", comma ? "," : "");
155 static void
156 json_author(FILE *fp, const char *type, char *address, int comma)
158 char *gt, *lt, *at, *email, *endname;
160 fprintf(fp, "\"%s\":{", type);
162 gt = strchr(address, '<');
163 if (gt != NULL) {
164 /* long format, e.g. "Omar Polo <op@openbsd.org>" */
166 json_field(fp, "full", address, 1);
168 endname = gt;
169 while (endname > address && endname[-1] == ' ')
170 endname--;
172 *endname = '\0';
173 json_field(fp, "name", address, 1);
175 email = gt + 1;
176 lt = strchr(email, '>');
177 if (lt)
178 *lt = '\0';
180 json_field(fp, "mail", email, 1);
182 at = strchr(email, '@');
183 if (at)
184 *at = '\0';
186 json_field(fp, "user", email, 0);
187 } else {
188 /* short format only shows the username */
189 json_field(fp, "user", address, 0);
192 fprintf(fp, "}%s", comma ? "," : "");
195 static int
196 jsonify_branch_rm(FILE *fp, char *line, const char *repo)
198 char *ref, *id;
200 line = strchr(line, ' ');
201 if (line == NULL)
202 errx(1, "invalid branch rm line");
203 line += strspn(line, " ");
205 ref = line;
207 line = strchr(line, ':');
208 if (line == NULL)
209 errx(1, "invalid branch rm line");
210 *line++ = '\0';
211 id = line + strspn(line, " ");
213 fputc('{', fp);
214 json_field(fp, "type", "branch-deleted", 1);
215 json_field(fp, "repo", repo, 1);
216 json_field(fp, "ref", ref, 1);
217 json_field(fp, "id", id, 0);
218 fputc('}', fp);
220 return 0;
223 static int
224 jsonify_commit_short(FILE *fp, char *line, const char *repo)
226 char *t, *date, *id, *author, *message;
228 t = line;
229 date = t;
230 if ((t = strchr(t, ' ')) == NULL)
231 errx(1, "malformed line");
232 *t++ = '\0';
234 id = t;
235 if ((t = strchr(t, ' ')) == NULL)
236 errx(1, "malformed line");
237 *t++ = '\0';
239 author = t;
240 if ((t = strchr(t, ' ')) == NULL)
241 errx(1, "malformed line");
242 *t++ = '\0';
244 message = t;
246 fprintf(fp, "{\"type\":\"commit\",\"short\":true,");
247 json_field(fp, "repo", repo, 1);
248 json_field(fp, "id", id, 1);
249 json_author(fp, "committer", author, 1);
250 json_field(fp, "date", date, 1);
251 json_field(fp, "short_message", message, 0);
252 fprintf(fp, "}");
254 return 0;
257 static int
258 jsonify_commit(FILE *fp, const char *repo, char **line, ssize_t *linesize)
260 const char *errstr;
261 char *author = NULL;
262 char *filename, *t;
263 char *l;
264 ssize_t linelen;
265 int parent = 0;
266 int msglen = 0, msgwrote = 0;
267 int n, files = 0;
268 int done = 0;
269 enum {
270 P_FROM,
271 P_VIA,
272 P_DATE,
273 P_PARENT,
274 P_MSGLEN,
275 P_MSG,
276 P_DST,
277 P_SUM,
278 } phase = P_FROM;
280 l = *line;
281 if (strncmp(l, "commit ", 7) != 0)
282 errx(1, "%s: unexpected line: %s", __func__, l);
283 l += 7;
285 fprintf(fp, "{\"type\":\"commit\",\"short\":false,");
286 json_field(fp, "repo", repo, 1);
287 json_field(fp, "id", l, 1);
289 while (!done) {
290 if ((linelen = getline(line, linesize, stdin)) == -1)
291 break;
293 if ((*line)[linelen - 1] == '\n')
294 (*line)[--linelen] = '\0';
296 l = *line;
297 switch (phase) {
298 case P_FROM:
299 if (strncmp(l, "from: ", 6) != 0)
300 errx(1, "unexpected from line");
301 l += 6;
303 author = strdup(l);
304 if (author == NULL)
305 err(1, "strdup");
307 json_author(fp, "author", l, 1);
309 phase = P_VIA;
310 break;
312 case P_VIA:
313 /* optional */
314 if (!strncmp(l, "via: ", 5)) {
315 l += 5;
316 json_author(fp, "committer", l, 1);
317 phase = P_DATE;
318 break;
321 if (author == NULL) /* impossible */
322 err(1, "from not specified");
323 json_author(fp, "committer", author, 1);
324 free(author);
325 author = NULL;
327 phase = P_DATE;
328 /* fallthrough */
330 case P_DATE:
331 /* optional */
332 if (!strncmp(l, "date: ", 6)) {
333 l += 6;
334 json_field(fp, "date", l, 1);
335 phase = P_PARENT;
336 break;
338 phase = P_PARENT;
339 /* fallthough */
341 case P_PARENT:
342 /* optional - more than one */
343 if (!strncmp(l, "parent ", 7)) {
344 l += 7;
345 l += strcspn(l, ":");
346 l += strspn(l, " ");
348 if (parent == 0) {
349 parent = 1;
350 fprintf(fp, "\"parents\":[");
353 fputc('"', fp);
354 escape(fp, l);
355 fputc('"', fp);
357 break;
359 if (parent != 0) {
360 fprintf(fp, "],");
361 parent = 0;
363 phase = P_MSGLEN;
364 /* fallthrough */
366 case P_MSGLEN:
367 if (strncmp(l, "messagelen: ", 12) != 0)
368 errx(1, "unexpected messagelen line");
369 l += 12;
370 msglen = strtonum(l, 1, INT_MAX, &errstr);
371 if (errstr)
372 errx(1, "message len is %s: %s", errstr, l);
374 msglen++;
376 phase = P_MSG;
377 break;
379 case P_MSG:
380 /*
381 * The commit message is indented with one extra
382 * space which is not accounted for in messagelen,
383 * but we also strip the trailing \n so that
384 * accounts for it.
386 * Since we read line-by-line and there is always
387 * a \n added at the end of the message,
388 * tolerate one byte less than advertised.
389 */
390 if (*l != ' ')
391 errx(1, "unexpected line in commit message");
393 l++; /* skip leading space */
394 linelen--;
396 if (msgwrote == 0 && linelen != 0) {
397 json_field(fp, "short_message", l, 1);
398 fprintf(fp, "\"message\":\"");
399 escape(fp, l);
400 escape(fp, "\n");
401 msgwrote += linelen;
402 } else if (msgwrote != 0) {
403 escape(fp, l);
404 escape(fp, "\n");
407 msglen -= linelen + 1;
408 if (msglen <= 1) {
409 fprintf(fp, "\",");
410 phase = P_DST;
411 break;
413 break;
415 case P_DST:
416 if (files == 0 && !strcmp(l, " "))
417 break;
419 if (files == 0)
420 fputs("\"diffstat\":{\"files\":[", fp);
422 if (*l == '\0') {
423 fputs("],", fp);
424 phase = P_SUM;
425 break;
428 if (*l != ' ')
429 errx(1, "bad diffstat line");
430 l++;
432 if (files != 0)
433 fputc(',', fp);
434 fputc('{', fp);
436 switch (*l) {
437 case 'A':
438 json_field(fp, "action", "added", 1);
439 break;
440 case 'D':
441 json_field(fp, "action", "deleted", 1);
442 break;
443 case 'M':
444 json_field(fp, "action", "modified", 1);
445 break;
446 case 'm':
447 json_field(fp, "action", "mode changed", 1);
448 break;
449 default:
450 json_field(fp, "action", "unknown", 1);
451 break;
454 l++;
455 while (*l == ' ')
456 *l++ = '\0';
457 if (*l == '\0')
458 errx(1, "invalid diffstat: no filename");
460 filename = l;
461 l = strrchr(l, '|');
462 if (l == NULL)
463 errx(1, "invalid diffstat: no separator");
464 t = l - 1;
465 while (t > filename && *t == ' ')
466 *t-- = '\0';
467 json_field(fp, "file", filename, 1);
469 l++;
470 while (*l == ' ')
471 l++;
473 t = strchr(l, '+');
474 if (t == NULL)
475 errx(1, "invalid diffstat: no added counter");
476 *t++ = '\0';
478 n = strtonum(l, 0, INT_MAX, &errstr);
479 if (errstr)
480 errx(1, "added counter is %s: %s", errstr, l);
481 fprintf(fp, "\"added\":%d,", n);
483 l = ++t;
484 while (*l == ' ')
485 l++;
487 t = strchr(l, '-');
488 if (t == NULL)
489 errx(1, "invalid diffstat: no del counter");
490 *t = '\0';
492 n = strtonum(l, 0, INT_MAX, &errstr);
493 if (errstr)
494 errx(1, "del counter is %s: %s", errstr, l);
495 fprintf(fp, "\"removed\":%d", n);
497 fputc('}', fp);
499 files++;
501 break;
503 case P_SUM:
504 fputs("\"total\":{", fp);
506 t = l;
507 l = strchr(l, ' ');
508 if (l == NULL)
509 errx(1, "missing number of additions");
510 *l++ = '\0';
512 n = strtonum(t, 0, INT_MAX, &errstr);
513 if (errstr)
514 errx(1, "add counter is %s: %s", errstr, t);
515 fprintf(fp, "\"added\":%d,", n);
517 l = strchr(l, ',');
518 if (l == NULL)
519 errx(1, "missing number of deletions");
520 l++;
521 while (*l == ' ')
522 l++;
524 t = strchr(l, ' ');
525 if (t == NULL)
526 errx(1, "malformed diffstat sum line");
527 *t = '\0';
529 n = strtonum(l, 0, INT_MAX, &errstr);
530 if (errstr)
531 errx(1, "del counter is %s: %s", errstr, l);
532 fprintf(fp, "\"removed\":%d", n);
534 fputs("}}", fp);
535 done = 1;
536 break;
538 default:
539 /* unreachable */
540 errx(1, "unexpected line: %s", *line);
543 if (ferror(stdin))
544 err(1, "getline");
545 if (!done)
546 errx(1, "unexpected EOF");
547 fputc('}', fp);
549 return 0;
552 static int
553 jsonify_tag(FILE *fp, const char *repo, char **line, ssize_t *linesize)
555 const char *errstr;
556 char *l;
557 ssize_t linelen;
558 int msglen = 0, msgwrote = 0;
559 int done = 0;
560 enum {
561 P_FROM,
562 P_DATE,
563 P_OBJECT,
564 P_MSGLEN,
565 P_MSG,
566 } phase = P_FROM;
568 l = *line;
569 if (strncmp(l, "tag ", 4) != 0)
570 errx(1, "%s: unexpected line: %s", __func__, l);
571 l += 4;
573 fputc('{', fp);
574 json_field(fp, "type", "tag", 1);
575 json_field(fp, "repo", repo, 1);
576 json_field(fp, "tag", l, 1);
578 while (!done) {
579 if ((linelen = getline(line, linesize, stdin)) == -1)
580 break;
582 if ((*line)[linelen - 1] == '\n')
583 (*line)[--linelen] = '\0';
585 l = *line;
586 switch (phase) {
587 case P_FROM:
588 if (strncmp(l, "from: ", 6) != 0)
589 errx(1, "unexpected from line");
590 l += 6;
592 json_author(fp, "tagger", l, 1);
594 phase = P_DATE;
595 break;
597 case P_DATE:
598 /* optional */
599 if (!strncmp(l, "date: ", 6)) {
600 l += 6;
601 json_field(fp, "date", l, 1);
602 phase = P_OBJECT;
603 break;
605 phase = P_OBJECT;
606 /* fallthough */
608 case P_OBJECT:
609 /* optional */
610 if (!strncmp(l, "object: ", 8)) {
611 char *type, *id;
613 l += 8;
614 type = l;
615 id = strchr(l, ' ');
616 if (id == NULL)
617 errx(1, "malformed tag object line");
618 *id++ = '\0';
620 fputs("\"object\":{", fp);
621 json_field(fp, "type", type, 1);
622 json_field(fp, "id", id, 0);
623 fputs("},", fp);
625 phase = P_MSGLEN;
626 break;
628 phase = P_MSGLEN;
629 /* fallthrough */
631 case P_MSGLEN:
632 if (strncmp(l, "messagelen: ", 12) != 0)
633 errx(1, "unexpected messagelen line");
634 l += 12;
635 msglen = strtonum(l, 1, INT_MAX, &errstr);
636 if (errstr)
637 errx(1, "message len is %s: %s", errstr, l);
639 msglen++;
641 phase = P_MSG;
642 break;
644 case P_MSG:
645 if (*l != ' ')
646 errx(1, "unexpected line in tag message");
648 l++; /* skip leading space */
649 linelen--;
651 if (msgwrote == 0 && linelen != 0) {
652 fprintf(fp, "\"message\":\"");
653 escape(fp, l);
654 escape(fp, "\n");
655 msgwrote += linelen;
656 } else if (msgwrote != 0) {
657 escape(fp, l);
658 escape(fp, "\n");
661 msglen -= linelen + 1;
662 if (msglen <= 0) {
663 fprintf(fp, "\"");
664 done = 1;
665 break;
667 break;
669 default:
670 /* unreachable */
671 errx(1, "unexpected line: %s", *line);
674 if (ferror(stdin))
675 err(1, "getline");
676 if (!done)
677 errx(1, "unexpected EOF");
678 fputc('}', fp);
680 return 0;
683 static int
684 jsonify(FILE *fp, const char *repo)
686 char *line = NULL;
687 size_t linesize = 0;
688 ssize_t linelen;
689 int needcomma = 0;
691 fprintf(fp, "{\"notifications\":[");
692 while ((linelen = getline(&line, &linesize, stdin)) != -1) {
693 if (line[linelen - 1] == '\n')
694 line[--linelen] = '\0';
696 if (*line == '\0')
697 continue;
699 if (needcomma)
700 fputc(',', fp);
701 needcomma = 1;
703 if (strncmp(line, "Removed refs/heads/", 19) == 0) {
704 if (jsonify_branch_rm(fp, line, repo) == -1)
705 err(1, "jsonify_branch_rm");
706 continue;
709 if (strncmp(line, "commit ", 7) == 0) {
710 if (jsonify_commit(fp, repo, &line, &linesize) == -1)
711 err(1, "jsonify_commit");
712 continue;
715 if (*line >= '0' && *line <= '9') {
716 if (jsonify_commit_short(fp, line, repo) == -1)
717 err(1, "jsonify_commit_short");
718 continue;
721 if (strncmp(line, "tag ", 4) == 0) {
722 if (jsonify_tag(fp, repo, &line, &linesize) == -1)
723 err(1, "jsonify_tag");
724 continue;
727 errx(1, "unexpected line: %s", line);
729 if (ferror(stdin))
730 err(1, "getline");
731 fprintf(fp, "]}");
733 return 0;
736 static char
737 sixet2ch(int c)
739 c &= 0x3F;
741 if (c < 26)
742 return 'A' + c;
743 c -= 26;
744 if (c < 26)
745 return 'a' + c;
746 c -= 26;
747 if (c < 10)
748 return '0' + c;
749 c -= 10;
750 if (c == 0)
751 return '+';
752 if (c == 1)
753 return '/';
755 errx(1, "invalid sixet 0x%x", c);
758 static char *
759 basic_auth(const char *username, const char *password)
761 char *str, *tmp, *end, *s, *p;
762 char buf[3];
763 int len, i, r;
765 r = asprintf(&str, "%s:%s", username, password);
766 if (r == -1)
767 err(1, "asprintf");
769 /*
770 * Will need 4 * r/3 bytes to encode the string, plus a
771 * rounding to the next multiple of 4 for padding, plus NUL.
772 */
773 len = 4 * r / 3;
774 len = (len + 3) & ~3;
775 len++;
777 tmp = calloc(1, len);
778 if (tmp == NULL)
779 err(1, "malloc");
781 s = str;
782 p = tmp;
783 while (*s != '\0') {
784 memset(buf, 0, sizeof(buf));
785 for (i = 0; i < 3 && *s != '\0'; ++i, ++s)
786 buf[i] = *s;
788 *p++ = sixet2ch(buf[0] >> 2);
789 *p++ = sixet2ch((buf[1] >> 4) | (buf[0] << 4));
790 if (i > 1)
791 *p++ = sixet2ch((buf[1] << 2) | (buf[2] >> 6));
792 if (i > 2)
793 *p++ = sixet2ch(buf[2]);
796 for (end = tmp + len - 1; p < end; ++p)
797 *p = '=';
799 free(str);
800 return tmp;
803 static inline int
804 bufio2poll(struct bufio *bio)
806 int f, ret = 0;
808 f = bufio_ev(bio);
809 if (f & BUFIO_WANT_READ)
810 ret |= POLLIN;
811 if (f & BUFIO_WANT_WRITE)
812 ret |= POLLOUT;
813 return ret;
816 int
817 main(int argc, char **argv)
819 FILE *tmpfp;
820 struct bufio bio;
821 struct pollfd pfd;
822 struct timespec timeout;
823 const char *username;
824 const char *password;
825 const char *timeoutstr;
826 const char *errstr;
827 const char *repo = NULL;
828 const char *host = NULL, *port = NULL, *path = NULL;
829 char *auth, *line, *spc;
830 size_t len;
831 ssize_t r;
832 off_t paylen;
833 int tls = 0;
834 int response_code = 0, done = 0;
835 int ch, flags, ret, nonstd = 0;
837 #ifndef PROFILE
838 if (pledge("stdio rpath tmppath dns inet", NULL) == -1)
839 err(1, "pledge");
840 #endif
842 log_init(0, LOG_DAEMON);
844 while ((ch = getopt(argc, argv, "ch:p:r:")) != -1) {
845 switch (ch) {
846 case 'c':
847 tls = 1;
848 break;
849 case 'h':
850 host = optarg;
851 break;
852 case 'p':
853 port = optarg;
854 break;
855 case 'r':
856 repo = optarg;
857 break;
858 default:
859 usage();
862 argc -= optind;
863 argv += optind;
865 if (host == NULL || repo == NULL || argc != 1)
866 usage();
867 if (tls && port == NULL)
868 port = "443";
869 path = argv[0];
871 username = getenv("GOT_NOTIFY_HTTP_USER");
872 password = getenv("GOT_NOTIFY_HTTP_PASS");
873 if ((username != NULL && password == NULL) ||
874 (username == NULL && password != NULL))
875 fatalx("username or password are not specified");
876 if (username && *password == '\0')
877 fatalx("password can't be empty");
879 /* used by the regression test suite */
880 timeoutstr = getenv("GOT_NOTIFY_TIMEOUT");
881 if (timeoutstr) {
882 http_timeout = strtonum(timeoutstr, 0, 600, &errstr);
883 if (errstr != NULL)
884 fatalx("timeout in seconds is %s: %s",
885 errstr, timeoutstr);
888 memset(&timeout, 0, sizeof(timeout));
889 timeout.tv_sec = http_timeout;
891 tmpfp = got_opentemp();
892 if (tmpfp == NULL)
893 fatal("opentemp");
895 jsonify(tmpfp, repo);
897 paylen = ftello(tmpfp);
898 if (paylen == -1)
899 fatal("ftello");
900 if (fseeko(tmpfp, 0, SEEK_SET) == -1)
901 fatal("fseeko");
903 #ifndef PROFILE
904 /* drop tmppath */
905 if (pledge("stdio rpath dns inet", NULL) == -1)
906 err(1, "pledge");
907 #endif
909 memset(&pfd, 0, sizeof(pfd));
910 pfd.fd = dial(host, port);
912 if ((flags = fcntl(pfd.fd, F_GETFL)) == -1)
913 fatal("fcntl(F_GETFL)");
914 if (fcntl(pfd.fd, F_SETFL, flags | O_NONBLOCK) == -1)
915 fatal("fcntl(F_SETFL)");
917 if (bufio_init(&bio) == -1)
918 fatal("bufio_init");
919 bufio_set_fd(&bio, pfd.fd);
920 if (tls && bufio_starttls(&bio, host, 0, NULL, 0, NULL, 0) == -1)
921 fatal("bufio_starttls");
923 #ifndef PROFILE
924 /* drop rpath dns inet */
925 if (pledge("stdio", NULL) == -1)
926 err(1, "pledge");
927 #endif
929 if ((!tls && strcmp(port, "80") != 0) ||
930 (tls && strcmp(port, "443")) != 0)
931 nonstd = 1;
933 ret = bufio_compose_fmt(&bio,
934 "POST %s HTTP/1.1\r\n"
935 "Host: %s%s%s\r\n"
936 "Content-Type: application/json\r\n"
937 "Content-Length: %lld\r\n"
938 "User-Agent: %s\r\n"
939 "Connection: close\r\n",
940 path, host,
941 nonstd ? ":" : "", nonstd ? port : "",
942 (long long)paylen, USERAGENT);
943 if (ret == -1)
944 fatal("bufio_compose_fmt");
946 if (username) {
947 auth = basic_auth(username, password);
948 ret = bufio_compose_fmt(&bio, "Authorization: basic %s\r\n",
949 auth);
950 if (ret == -1)
951 fatal("bufio_compose_fmt");
952 free(auth);
955 if (bufio_compose(&bio, "\r\n", 2) == -1)
956 fatal("bufio_compose");
958 while (!done) {
959 struct timespec elapsed, start, stop;
960 char buf[BUFSIZ];
962 pfd.events = bufio2poll(&bio);
963 clock_gettime(CLOCK_MONOTONIC, &start);
964 ret = ppoll(&pfd, 1, &timeout, NULL);
965 if (ret == -1)
966 fatal("poll");
967 clock_gettime(CLOCK_MONOTONIC, &stop);
968 timespecsub(&stop, &start, &elapsed);
969 timespecsub(&timeout, &elapsed, &timeout);
970 if (ret == 0 || timeout.tv_sec <= 0)
971 fatalx("timeout");
973 if (bio.wbuf.len > 0) {
974 if (bufio_write(&bio) == -1 && errno != EAGAIN)
975 fatalx("bufio_write: %s", bufio_io_err(&bio));
978 r = bufio_read(&bio);
979 if (r == -1 && errno != EAGAIN)
980 fatalx("bufio_read: %s", bufio_io_err(&bio));
981 if (r == 0)
982 fatalx("unexpected EOF");
984 for (;;) {
985 line = buf_getdelim(&bio.rbuf, "\r\n", &len);
986 if (line == NULL)
987 break;
988 if (response_code && *line == '\0') {
989 /*
990 * end of headers, don't bother
991 * reading the body, if there is.
992 */
993 done = 1;
994 break;
996 if (response_code) {
997 buf_drain(&bio.rbuf, len);
998 continue;
1000 spc = strchr(line, ' ');
1001 if (spc == NULL)
1002 fatalx("bad HTTP response from server");
1003 *spc++ = '\0';
1004 if (strcasecmp(line, "HTTP/1.1") != 0)
1005 log_warnx("unexpected protocol: %s", line);
1006 line = spc;
1008 spc = strchr(line, ' ');
1009 if (spc == NULL)
1010 fatalx("bad HTTP response from server");
1011 *spc++ = '\0';
1013 response_code = strtonum(line, 100, 599,
1014 &errstr);
1015 if (errstr != NULL)
1016 log_warnx("response code is %s: %s",
1017 errstr, line);
1019 buf_drain(&bio.rbuf, len);
1021 if (done)
1022 break;
1024 if (!feof(tmpfp) && bio.wbuf.len < sizeof(buf)) {
1025 len = fread(buf, 1, sizeof(buf), tmpfp);
1026 if (len == 0) {
1027 if (ferror(tmpfp))
1028 fatal("fread");
1029 continue;
1032 if (bufio_compose(&bio, buf, len) == -1)
1033 fatal("buf_compose");
1037 if (response_code >= 200 && response_code < 300)
1038 return 0;
1039 fatal("request failed with code %d", response_code);