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 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 err(1, "%s", cause);
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)
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, "ref", ref, 1);
224 json_field(fp, "id", id, 0);
225 fputc('}', fp);
227 return 0;
230 static int
231 jsonify_commit_short(FILE *fp, char *line, const char *repo)
233 char *t, *date, *id, *author, *message;
235 t = line;
236 date = t;
237 if ((t = strchr(t, ' ')) == NULL)
238 errx(1, "malformed line");
239 *t++ = '\0';
241 id = t;
242 if ((t = strchr(t, ' ')) == NULL)
243 errx(1, "malformed line");
244 *t++ = '\0';
246 author = t;
247 if ((t = strchr(t, ' ')) == NULL)
248 errx(1, "malformed line");
249 *t++ = '\0';
251 message = t;
253 fprintf(fp, "{\"type\":\"commit\",\"short\":true,");
254 json_field(fp, "repo", repo, 1);
255 json_field(fp, "id", id, 1);
256 json_author(fp, "committer", author, 1);
257 json_date(fp, "date", date, 1);
258 json_field(fp, "short_message", message, 0);
259 fprintf(fp, "}");
261 return 0;
264 static int
265 jsonify_commit(FILE *fp, const char *repo, char **line, ssize_t *linesize)
267 const char *errstr;
268 char *author = NULL;
269 char *filename, *t;
270 char *l;
271 ssize_t linelen;
272 int parent = 0;
273 int msglen = 0, msgwrote = 0;
274 int n, files = 0;
275 int done = 0;
276 enum {
277 P_FROM,
278 P_VIA,
279 P_DATE,
280 P_PARENT,
281 P_MSGLEN,
282 P_MSG,
283 P_DST,
284 P_SUM,
285 } phase = P_FROM;
287 l = *line;
288 if (strncmp(l, "commit ", 7) != 0)
289 errx(1, "%s: unexpected line: %s", __func__, l);
290 l += 7;
292 fprintf(fp, "{\"type\":\"commit\",\"short\":false,");
293 json_field(fp, "repo", repo, 1);
294 json_field(fp, "id", l, 1);
296 while (!done) {
297 if ((linelen = getline(line, linesize, stdin)) == -1)
298 break;
300 if ((*line)[linelen - 1] == '\n')
301 (*line)[--linelen] = '\0';
303 l = *line;
304 switch (phase) {
305 case P_FROM:
306 if (strncmp(l, "from: ", 6) != 0)
307 errx(1, "unexpected from line");
308 l += 6;
310 author = strdup(l);
311 if (author == NULL)
312 err(1, "strdup");
314 json_author(fp, "author", l, 1);
316 phase = P_VIA;
317 break;
319 case P_VIA:
320 /* optional */
321 if (!strncmp(l, "via: ", 5)) {
322 l += 5;
323 json_author(fp, "committer", l, 1);
324 phase = P_DATE;
325 break;
328 if (author == NULL) /* impossible */
329 err(1, "from not specified");
330 json_author(fp, "committer", author, 1);
331 free(author);
332 author = NULL;
334 phase = P_DATE;
335 /* fallthrough */
337 case P_DATE:
338 /* optional */
339 if (!strncmp(l, "date: ", 6)) {
340 l += 6;
341 json_date(fp, "date", l, 1);
342 phase = P_PARENT;
343 break;
345 phase = P_PARENT;
346 /* fallthough */
348 case P_PARENT:
349 /* optional - more than one */
350 if (!strncmp(l, "parent ", 7)) {
351 l += 7;
352 l += strcspn(l, ":");
353 l += strspn(l, " ");
355 if (parent == 0) {
356 parent = 1;
357 fprintf(fp, "\"parents\":[");
360 fputc('"', fp);
361 escape(fp, l);
362 fputc('"', fp);
364 break;
366 if (parent != 0) {
367 fprintf(fp, "],");
368 parent = 0;
370 phase = P_MSGLEN;
371 /* fallthrough */
373 case P_MSGLEN:
374 if (strncmp(l, "messagelen: ", 12) != 0)
375 errx(1, "unexpected messagelen line");
376 l += 12;
377 msglen = strtonum(l, 1, INT_MAX, &errstr);
378 if (errstr)
379 errx(1, "message len is %s: %s", errstr, l);
381 msglen++;
383 phase = P_MSG;
384 break;
386 case P_MSG:
387 /*
388 * The commit message is indented with one extra
389 * space which is not accounted for in messagelen,
390 * but we also strip the trailing \n so that
391 * accounts for it.
393 * Since we read line-by-line and there is always
394 * a \n added at the end of the message,
395 * tolerate one byte less than advertised.
396 */
397 if (*l != ' ')
398 errx(1, "unexpected line in commit message");
400 l++; /* skip leading space */
401 linelen--;
403 if (msgwrote == 0 && linelen != 0) {
404 json_field(fp, "short_message", l, 1);
405 fprintf(fp, "\"message\":\"");
406 escape(fp, l);
407 escape(fp, "\n");
408 msgwrote += linelen;
409 } else if (msgwrote != 0) {
410 escape(fp, l);
411 escape(fp, "\n");
414 msglen -= linelen + 1;
415 if (msglen <= 1) {
416 fprintf(fp, "\",");
417 phase = P_DST;
418 break;
420 break;
422 case P_DST:
423 if (files == 0 && !strcmp(l, " "))
424 break;
426 if (files == 0)
427 fputs("\"diffstat\":{\"files\":[", fp);
429 if (*l == '\0') {
430 fputs("],", fp);
431 phase = P_SUM;
432 break;
435 if (*l != ' ')
436 errx(1, "bad diffstat line");
437 l++;
439 if (files != 0)
440 fputc(',', fp);
441 fputc('{', fp);
443 switch (*l) {
444 case 'A':
445 json_field(fp, "action", "added", 1);
446 break;
447 case 'D':
448 json_field(fp, "action", "deleted", 1);
449 break;
450 case 'M':
451 json_field(fp, "action", "modified", 1);
452 break;
453 case 'm':
454 json_field(fp, "action", "mode changed", 1);
455 break;
456 default:
457 json_field(fp, "action", "unknown", 1);
458 break;
461 l++;
462 while (*l == ' ')
463 *l++ = '\0';
464 if (*l == '\0')
465 errx(1, "invalid diffstat: no filename");
467 filename = l;
468 l = strrchr(l, '|');
469 if (l == NULL)
470 errx(1, "invalid diffstat: no separator");
471 t = l - 1;
472 while (t > filename && *t == ' ')
473 *t-- = '\0';
474 json_field(fp, "file", filename, 1);
476 l++;
477 while (*l == ' ')
478 l++;
480 t = strchr(l, '+');
481 if (t == NULL)
482 errx(1, "invalid diffstat: no added counter");
483 *t++ = '\0';
485 n = strtonum(l, 0, INT_MAX, &errstr);
486 if (errstr)
487 errx(1, "added counter is %s: %s", errstr, l);
488 fprintf(fp, "\"added\":%d,", n);
490 l = ++t;
491 while (*l == ' ')
492 l++;
494 t = strchr(l, '-');
495 if (t == NULL)
496 errx(1, "invalid diffstat: no del counter");
497 *t = '\0';
499 n = strtonum(l, 0, INT_MAX, &errstr);
500 if (errstr)
501 errx(1, "del counter is %s: %s", errstr, l);
502 fprintf(fp, "\"removed\":%d", n);
504 fputc('}', fp);
506 files++;
508 break;
510 case P_SUM:
511 fputs("\"total\":{", fp);
513 t = l;
514 l = strchr(l, ' ');
515 if (l == NULL)
516 errx(1, "missing number of additions");
517 *l++ = '\0';
519 n = strtonum(t, 0, INT_MAX, &errstr);
520 if (errstr)
521 errx(1, "add counter is %s: %s", errstr, t);
522 fprintf(fp, "\"added\":%d,", n);
524 l = strchr(l, ',');
525 if (l == NULL)
526 errx(1, "missing number of deletions");
527 l++;
528 while (*l == ' ')
529 l++;
531 t = strchr(l, ' ');
532 if (t == NULL)
533 errx(1, "malformed diffstat sum line");
534 *t = '\0';
536 n = strtonum(l, 0, INT_MAX, &errstr);
537 if (errstr)
538 errx(1, "del counter is %s: %s", errstr, l);
539 fprintf(fp, "\"removed\":%d", n);
541 fputs("}}", fp);
542 done = 1;
543 break;
545 default:
546 /* unreachable */
547 errx(1, "unexpected line: %s", *line);
550 if (ferror(stdin))
551 err(1, "getline");
552 if (!done)
553 errx(1, "unexpected EOF");
554 fputc('}', fp);
556 return 0;
559 static int
560 jsonify_tag(FILE *fp, const char *repo, char **line, ssize_t *linesize)
562 const char *errstr;
563 char *l;
564 ssize_t linelen;
565 int msglen = 0, msgwrote = 0;
566 int done = 0;
567 enum {
568 P_FROM,
569 P_DATE,
570 P_OBJECT,
571 P_MSGLEN,
572 P_MSG,
573 } phase = P_FROM;
575 l = *line;
576 if (strncmp(l, "tag ", 4) != 0)
577 errx(1, "%s: unexpected line: %s", __func__, l);
578 l += 4;
580 fputc('{', fp);
581 json_field(fp, "type", "tag", 1);
582 json_field(fp, "repo", repo, 1);
583 json_field(fp, "tag", l, 1);
585 while (!done) {
586 if ((linelen = getline(line, linesize, stdin)) == -1)
587 break;
589 if ((*line)[linelen - 1] == '\n')
590 (*line)[--linelen] = '\0';
592 l = *line;
593 switch (phase) {
594 case P_FROM:
595 if (strncmp(l, "from: ", 6) != 0)
596 errx(1, "unexpected from line");
597 l += 6;
599 json_author(fp, "tagger", l, 1);
601 phase = P_DATE;
602 break;
604 case P_DATE:
605 /* optional */
606 if (!strncmp(l, "date: ", 6)) {
607 l += 6;
608 json_date(fp, "date", l, 1);
609 phase = P_OBJECT;
610 break;
612 phase = P_OBJECT;
613 /* fallthough */
615 case P_OBJECT:
616 /* optional */
617 if (!strncmp(l, "object: ", 8)) {
618 char *type, *id;
620 l += 8;
621 type = l;
622 id = strchr(l, ' ');
623 if (id == NULL)
624 errx(1, "malformed tag object line");
625 *id++ = '\0';
627 fputs("\"object\":{", fp);
628 json_field(fp, "type", type, 1);
629 json_field(fp, "id", id, 0);
630 fputs("},", fp);
632 phase = P_MSGLEN;
633 break;
635 phase = P_MSGLEN;
636 /* fallthrough */
638 case P_MSGLEN:
639 if (strncmp(l, "messagelen: ", 12) != 0)
640 errx(1, "unexpected messagelen line");
641 l += 12;
642 msglen = strtonum(l, 1, INT_MAX, &errstr);
643 if (errstr)
644 errx(1, "message len is %s: %s", errstr, l);
646 msglen++;
648 phase = P_MSG;
649 break;
651 case P_MSG:
652 if (*l != ' ')
653 errx(1, "unexpected line in tag message");
655 l++; /* skip leading space */
656 linelen--;
658 if (msgwrote == 0 && linelen != 0) {
659 fprintf(fp, "\"message\":\"");
660 escape(fp, l);
661 escape(fp, "\n");
662 msgwrote += linelen;
663 } else if (msgwrote != 0) {
664 escape(fp, l);
665 escape(fp, "\n");
668 msglen -= linelen + 1;
669 if (msglen <= 0) {
670 fprintf(fp, "\"");
671 done = 1;
672 break;
674 break;
676 default:
677 /* unreachable */
678 errx(1, "unexpected line: %s", *line);
681 if (ferror(stdin))
682 err(1, "getline");
683 if (!done)
684 errx(1, "unexpected EOF");
685 fputc('}', fp);
687 return 0;
690 static int
691 jsonify(FILE *fp, const char *repo)
693 char *line = NULL;
694 size_t linesize = 0;
695 ssize_t linelen;
696 int needcomma = 0;
698 fprintf(fp, "{\"notifications\":[");
699 while ((linelen = getline(&line, &linesize, stdin)) != -1) {
700 if (line[linelen - 1] == '\n')
701 line[--linelen] = '\0';
703 if (*line == '\0')
704 continue;
706 if (needcomma)
707 fputc(',', fp);
708 needcomma = 1;
710 if (strncmp(line, "Removed refs/heads/", 19) == 0) {
711 if (jsonify_branch_rm(fp, line, repo) == -1)
712 err(1, "jsonify_branch_rm");
713 continue;
716 if (strncmp(line, "commit ", 7) == 0) {
717 if (jsonify_commit(fp, repo, &line, &linesize) == -1)
718 err(1, "jsonify_commit");
719 continue;
722 if (*line >= '0' && *line <= '9') {
723 if (jsonify_commit_short(fp, line, repo) == -1)
724 err(1, "jsonify_commit_short");
725 continue;
728 if (strncmp(line, "tag ", 4) == 0) {
729 if (jsonify_tag(fp, repo, &line, &linesize) == -1)
730 err(1, "jsonify_tag");
731 continue;
734 errx(1, "unexpected line: %s", line);
736 if (ferror(stdin))
737 err(1, "getline");
738 fprintf(fp, "]}");
740 return 0;
743 static char
744 sixet2ch(int c)
746 c &= 0x3F;
748 if (c < 26)
749 return 'A' + c;
750 c -= 26;
751 if (c < 26)
752 return 'a' + c;
753 c -= 26;
754 if (c < 10)
755 return '0' + c;
756 c -= 10;
757 if (c == 0)
758 return '+';
759 if (c == 1)
760 return '/';
762 errx(1, "invalid sixet 0x%x", c);
765 static char *
766 basic_auth(const char *username, const char *password)
768 char *str, *tmp, *end, *s, *p;
769 char buf[3];
770 int len, i, r;
772 r = asprintf(&str, "%s:%s", username, password);
773 if (r == -1)
774 err(1, "asprintf");
776 /*
777 * Will need 4 * r/3 bytes to encode the string, plus a
778 * rounding to the next multiple of 4 for padding, plus NUL.
779 */
780 len = 4 * r / 3;
781 len = (len + 3) & ~3;
782 len++;
784 tmp = calloc(1, len);
785 if (tmp == NULL)
786 err(1, "malloc");
788 s = str;
789 p = tmp;
790 while (*s != '\0') {
791 memset(buf, 0, sizeof(buf));
792 for (i = 0; i < 3 && *s != '\0'; ++i, ++s)
793 buf[i] = *s;
795 *p++ = sixet2ch(buf[0] >> 2);
796 *p++ = sixet2ch((buf[1] >> 4) | (buf[0] << 4));
797 if (i > 1)
798 *p++ = sixet2ch((buf[1] << 2) | (buf[2] >> 6));
799 if (i > 2)
800 *p++ = sixet2ch(buf[2]);
803 for (end = tmp + len - 1; p < end; ++p)
804 *p = '=';
806 free(str);
807 return tmp;
810 static inline int
811 bufio2poll(struct bufio *bio)
813 int f, ret = 0;
815 f = bufio_ev(bio);
816 if (f & BUFIO_WANT_READ)
817 ret |= POLLIN;
818 if (f & BUFIO_WANT_WRITE)
819 ret |= POLLOUT;
820 return ret;
823 int
824 main(int argc, char **argv)
826 FILE *tmpfp;
827 struct bufio bio;
828 struct pollfd pfd;
829 struct timespec timeout;
830 const char *username;
831 const char *password;
832 const char *timeoutstr;
833 const char *errstr;
834 const char *repo = NULL;
835 const char *host = NULL, *port = NULL, *path = NULL;
836 char *auth, *line, *spc;
837 size_t len;
838 ssize_t r;
839 off_t paylen;
840 int tls = 0;
841 int response_code = 0, done = 0;
842 int ch, flags, ret, nonstd = 0;
844 #ifndef PROFILE
845 if (pledge("stdio rpath tmppath dns inet", NULL) == -1)
846 err(1, "pledge");
847 #endif
849 log_init(0, LOG_DAEMON);
851 while ((ch = getopt(argc, argv, "ch:p:r:")) != -1) {
852 switch (ch) {
853 case 'c':
854 tls = 1;
855 break;
856 case 'h':
857 host = optarg;
858 break;
859 case 'p':
860 port = optarg;
861 break;
862 case 'r':
863 repo = optarg;
864 break;
865 default:
866 usage();
869 argc -= optind;
870 argv += optind;
872 if (host == NULL || repo == NULL || argc != 1)
873 usage();
874 if (tls && port == NULL)
875 port = "443";
876 path = argv[0];
878 username = getenv("GOT_NOTIFY_HTTP_USER");
879 password = getenv("GOT_NOTIFY_HTTP_PASS");
880 if ((username != NULL && password == NULL) ||
881 (username == NULL && password != NULL))
882 fatalx("username or password are not specified");
883 if (username && *password == '\0')
884 fatalx("password can't be empty");
886 /* used by the regression test suite */
887 timeoutstr = getenv("GOT_NOTIFY_TIMEOUT");
888 if (timeoutstr) {
889 http_timeout = strtonum(timeoutstr, 0, 600, &errstr);
890 if (errstr != NULL)
891 fatalx("timeout in seconds is %s: %s",
892 errstr, timeoutstr);
895 memset(&timeout, 0, sizeof(timeout));
896 timeout.tv_sec = http_timeout;
898 tmpfp = got_opentemp();
899 if (tmpfp == NULL)
900 fatal("opentemp");
902 jsonify(tmpfp, repo);
904 paylen = ftello(tmpfp);
905 if (paylen == -1)
906 fatal("ftello");
907 if (fseeko(tmpfp, 0, SEEK_SET) == -1)
908 fatal("fseeko");
910 #ifndef PROFILE
911 /* drop tmppath */
912 if (pledge("stdio rpath dns inet", NULL) == -1)
913 err(1, "pledge");
914 #endif
916 memset(&pfd, 0, sizeof(pfd));
917 pfd.fd = dial(host, port);
919 if ((flags = fcntl(pfd.fd, F_GETFL)) == -1)
920 fatal("fcntl(F_GETFL)");
921 if (fcntl(pfd.fd, F_SETFL, flags | O_NONBLOCK) == -1)
922 fatal("fcntl(F_SETFL)");
924 if (bufio_init(&bio) == -1)
925 fatal("bufio_init");
926 bufio_set_fd(&bio, pfd.fd);
927 if (tls && bufio_starttls(&bio, host, 0, NULL, 0, NULL, 0) == -1)
928 fatal("bufio_starttls");
930 #ifndef PROFILE
931 /* drop rpath dns inet */
932 if (pledge("stdio", NULL) == -1)
933 err(1, "pledge");
934 #endif
936 if ((!tls && strcmp(port, "80") != 0) ||
937 (tls && strcmp(port, "443")) != 0)
938 nonstd = 1;
940 ret = bufio_compose_fmt(&bio,
941 "POST %s HTTP/1.1\r\n"
942 "Host: %s%s%s\r\n"
943 "Content-Type: application/json\r\n"
944 "Content-Length: %lld\r\n"
945 "User-Agent: %s\r\n"
946 "Connection: close\r\n",
947 path, host,
948 nonstd ? ":" : "", nonstd ? port : "",
949 (long long)paylen, USERAGENT);
950 if (ret == -1)
951 fatal("bufio_compose_fmt");
953 if (username) {
954 auth = basic_auth(username, password);
955 ret = bufio_compose_fmt(&bio, "Authorization: basic %s\r\n",
956 auth);
957 if (ret == -1)
958 fatal("bufio_compose_fmt");
959 free(auth);
962 if (bufio_compose(&bio, "\r\n", 2) == -1)
963 fatal("bufio_compose");
965 while (!done) {
966 struct timespec elapsed, start, stop;
967 char buf[BUFSIZ];
969 pfd.events = bufio2poll(&bio);
970 clock_gettime(CLOCK_MONOTONIC, &start);
971 ret = ppoll(&pfd, 1, &timeout, NULL);
972 if (ret == -1)
973 fatal("poll");
974 clock_gettime(CLOCK_MONOTONIC, &stop);
975 timespecsub(&stop, &start, &elapsed);
976 timespecsub(&timeout, &elapsed, &timeout);
977 if (ret == 0 || timeout.tv_sec <= 0)
978 fatalx("timeout");
980 if (bio.wbuf.len > 0) {
981 if (bufio_write(&bio) == -1 && errno != EAGAIN)
982 fatalx("bufio_write: %s", bufio_io_err(&bio));
985 r = bufio_read(&bio);
986 if (r == -1 && errno != EAGAIN)
987 fatalx("bufio_read: %s", bufio_io_err(&bio));
988 if (r == 0)
989 fatalx("unexpected EOF");
991 for (;;) {
992 line = buf_getdelim(&bio.rbuf, "\r\n", &len);
993 if (line == NULL)
994 break;
995 if (response_code && *line == '\0') {
996 /*
997 * end of headers, don't bother
998 * reading the body, if there is.
999 */
1000 done = 1;
1001 break;
1003 if (response_code) {
1004 buf_drain(&bio.rbuf, len);
1005 continue;
1007 spc = strchr(line, ' ');
1008 if (spc == NULL)
1009 fatalx("bad HTTP response from server");
1010 *spc++ = '\0';
1011 if (strcasecmp(line, "HTTP/1.1") != 0)
1012 log_warnx("unexpected protocol: %s", line);
1013 line = spc;
1015 spc = strchr(line, ' ');
1016 if (spc == NULL)
1017 fatalx("bad HTTP response from server");
1018 *spc++ = '\0';
1020 response_code = strtonum(line, 100, 599,
1021 &errstr);
1022 if (errstr != NULL)
1023 log_warnx("response code is %s: %s",
1024 errstr, line);
1026 buf_drain(&bio.rbuf, len);
1028 if (done)
1029 break;
1031 if (!feof(tmpfp) && bio.wbuf.len < sizeof(buf)) {
1032 len = fread(buf, 1, sizeof(buf), tmpfp);
1033 if (len == 0) {
1034 if (ferror(tmpfp))
1035 fatal("fread");
1036 continue;
1039 if (bufio_compose(&bio, buf, len) == -1)
1040 fatal("buf_compose");
1044 if (response_code >= 200 && response_code < 300)
1045 return 0;
1046 fatal("request failed with code %d", response_code);