2 5565365c 2024-03-27 op * Copyright (c) 2024 Omar Polo <op@openbsd.org>
4 5565365c 2024-03-27 op * Permission to use, copy, modify, and distribute this software for any
5 5565365c 2024-03-27 op * purpose with or without fee is hereby granted, provided that the above
6 5565365c 2024-03-27 op * copyright notice and this permission notice appear in all copies.
8 5565365c 2024-03-27 op * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 5565365c 2024-03-27 op * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 5565365c 2024-03-27 op * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 5565365c 2024-03-27 op * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 5565365c 2024-03-27 op * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 5565365c 2024-03-27 op * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 5565365c 2024-03-27 op * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 5565365c 2024-03-27 op #include <sys/time.h>
18 5565365c 2024-03-27 op #include <sys/types.h>
19 5565365c 2024-03-27 op #include <sys/socket.h>
21 5565365c 2024-03-27 op #include <err.h>
22 5565365c 2024-03-27 op #include <errno.h>
23 5565365c 2024-03-27 op #include <fcntl.h>
24 5565365c 2024-03-27 op #include <limits.h>
25 5565365c 2024-03-27 op #include <netdb.h>
26 5565365c 2024-03-27 op #include <poll.h>
27 cb29e255 2024-04-18 stsp #include <stdarg.h>
28 02dab75a 2024-04-18 stsp #include <stdio.h>
29 5565365c 2024-03-27 op #include <stdlib.h>
30 5565365c 2024-03-27 op #include <string.h>
31 cb29e255 2024-04-18 stsp #include <syslog.h>
32 939d3016 2024-04-23 op #include <time.h>
33 5565365c 2024-03-27 op #include <unistd.h>
35 5565365c 2024-03-27 op #include "got_opentemp.h"
36 5565365c 2024-03-27 op #include "got_version.h"
38 5565365c 2024-03-27 op #include "bufio.h"
39 cb29e255 2024-04-18 stsp #include "log.h"
40 02dab75a 2024-04-18 stsp #include "utf8d.h"
42 5565365c 2024-03-27 op #define USERAGENT "got-notify-http/" GOT_VERSION_STR
44 5565365c 2024-03-27 op static int http_timeout = 300; /* 5 minutes in seconds */
46 5565365c 2024-03-27 op __dead static void
49 d36998ae 2024-04-29 stsp fprintf(stderr, "usage: %s [-c] -r repo -h host -p port -u user path\n",
50 5565365c 2024-03-27 op getprogname());
55 5565365c 2024-03-27 op dial(const char *host, const char *port)
57 5565365c 2024-03-27 op struct addrinfo hints, *res, *res0;
58 5565365c 2024-03-27 op const char *cause = NULL;
59 5565365c 2024-03-27 op int s, error, save_errno;
61 5565365c 2024-03-27 op memset(&hints, 0, sizeof(hints));
62 5565365c 2024-03-27 op hints.ai_family = AF_UNSPEC;
63 5565365c 2024-03-27 op hints.ai_socktype = SOCK_STREAM;
64 5565365c 2024-03-27 op error = getaddrinfo(host, port, &hints, &res0);
66 5565365c 2024-03-27 op errx(1, "failed to resolve %s:%s: %s", host, port,
67 5565365c 2024-03-27 op gai_strerror(error));
70 5565365c 2024-03-27 op for (res = res0; res; res = res->ai_next) {
71 5565365c 2024-03-27 op s = socket(res->ai_family, res->ai_socktype,
72 5565365c 2024-03-27 op res->ai_protocol);
73 5565365c 2024-03-27 op if (s == -1) {
74 5565365c 2024-03-27 op cause = "socket";
78 5565365c 2024-03-27 op if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
79 5565365c 2024-03-27 op cause = "connect";
80 5565365c 2024-03-27 op save_errno = errno;
82 5565365c 2024-03-27 op errno = save_errno;
90 5565365c 2024-03-27 op freeaddrinfo(res0);
92 50ccbcd9 2024-05-11 stsp fatal("%s %s:%s", cause, host, port);
97 5565365c 2024-03-27 op escape(FILE *fp, const uint8_t *s)
99 ea5e974d 2024-03-28 op uint32_t codepoint, state;
100 ea5e974d 2024-03-28 op const uint8_t *start = s;
103 ea5e974d 2024-03-28 op for (; *s; ++s) {
104 ea5e974d 2024-03-28 op switch (decode(&state, &codepoint, *s)) {
105 ea5e974d 2024-03-28 op case UTF8_ACCEPT:
106 ea5e974d 2024-03-28 op switch (codepoint) {
109 ea5e974d 2024-03-28 op fprintf(fp, "\\%c", *s);
112 ea5e974d 2024-03-28 op fprintf(fp, "\\b");
115 ea5e974d 2024-03-28 op fprintf(fp, "\\f");
118 ea5e974d 2024-03-28 op fprintf(fp, "\\n");
121 ea5e974d 2024-03-28 op fprintf(fp, "\\r");
124 ea5e974d 2024-03-28 op fprintf(fp, "\\t");
127 ea5e974d 2024-03-28 op /* other control characters */
128 ea5e974d 2024-03-28 op if (codepoint < ' ' || codepoint == 0x7F) {
129 ea5e974d 2024-03-28 op fprintf(fp, "\\u%04x", codepoint);
132 ea5e974d 2024-03-28 op fwrite(start, 1, s - start + 1, fp);
135 ea5e974d 2024-03-28 op start = s + 1;
138 ea5e974d 2024-03-28 op case UTF8_REJECT:
139 ea5e974d 2024-03-28 op /* bad UTF-8 sequence; try to recover */
140 ea5e974d 2024-03-28 op fputs("\\uFFFD", fp);
141 ea5e974d 2024-03-28 op state = UTF8_ACCEPT;
142 ea5e974d 2024-03-28 op start = s + 1;
149 5565365c 2024-03-27 op json_field(FILE *fp, const char *key, const char *val, int comma)
151 5565365c 2024-03-27 op fprintf(fp, "\"%s\":\"", key);
152 5565365c 2024-03-27 op escape(fp, val);
153 5565365c 2024-03-27 op fprintf(fp, "\"%s", comma ? "," : "");
157 939d3016 2024-04-23 op json_date(FILE *fp, const char *key, const char *date, int comma)
159 939d3016 2024-04-23 op fprintf(fp, "\"%s\":%s%s", key, date, comma ? "," : "");
163 ac0a4dfc 2024-03-28 op json_author(FILE *fp, const char *type, char *address, int comma)
165 ac0a4dfc 2024-03-28 op char *gt, *lt, *at, *email, *endname;
167 ac0a4dfc 2024-03-28 op fprintf(fp, "\"%s\":{", type);
169 ac0a4dfc 2024-03-28 op gt = strchr(address, '<');
170 ac0a4dfc 2024-03-28 op if (gt != NULL) {
171 ac0a4dfc 2024-03-28 op /* long format, e.g. "Omar Polo <op@openbsd.org>" */
173 ac0a4dfc 2024-03-28 op json_field(fp, "full", address, 1);
175 ac0a4dfc 2024-03-28 op endname = gt;
176 ac0a4dfc 2024-03-28 op while (endname > address && endname[-1] == ' ')
179 ac0a4dfc 2024-03-28 op *endname = '\0';
180 ac0a4dfc 2024-03-28 op json_field(fp, "name", address, 1);
182 ac0a4dfc 2024-03-28 op email = gt + 1;
183 ac0a4dfc 2024-03-28 op lt = strchr(email, '>');
187 ac0a4dfc 2024-03-28 op json_field(fp, "mail", email, 1);
189 ac0a4dfc 2024-03-28 op at = strchr(email, '@');
193 ac0a4dfc 2024-03-28 op json_field(fp, "user", email, 0);
195 ac0a4dfc 2024-03-28 op /* short format only shows the username */
196 ac0a4dfc 2024-03-28 op json_field(fp, "user", address, 0);
199 ac0a4dfc 2024-03-28 op fprintf(fp, "}%s", comma ? "," : "");
203 d36998ae 2024-04-29 stsp jsonify_branch_rm(FILE *fp, char *line, const char *repo, const char *user)
205 d6057084 2024-03-28 op char *ref, *id;
207 d6057084 2024-03-28 op line = strchr(line, ' ');
208 d6057084 2024-03-28 op if (line == NULL)
209 d6057084 2024-03-28 op errx(1, "invalid branch rm line");
210 d6057084 2024-03-28 op line += strspn(line, " ");
214 d6057084 2024-03-28 op line = strchr(line, ':');
215 d6057084 2024-03-28 op if (line == NULL)
216 d6057084 2024-03-28 op errx(1, "invalid branch rm line");
217 d6057084 2024-03-28 op *line++ = '\0';
218 d6057084 2024-03-28 op id = line + strspn(line, " ");
220 d6057084 2024-03-28 op fputc('{', fp);
221 d6057084 2024-03-28 op json_field(fp, "type", "branch-deleted", 1);
222 c1003102 2024-04-15 op json_field(fp, "repo", repo, 1);
223 841969e1 2024-04-30 op json_field(fp, "authenticated_user", user, 1);
224 d6057084 2024-03-28 op json_field(fp, "ref", ref, 1);
225 d6057084 2024-03-28 op json_field(fp, "id", id, 0);
226 d6057084 2024-03-28 op fputc('}', fp);
232 d36998ae 2024-04-29 stsp jsonify_commit_short(FILE *fp, char *line, const char *repo, const char *user)
234 5565365c 2024-03-27 op char *t, *date, *id, *author, *message;
238 ec405b99 2024-03-28 op if ((t = strchr(t, ' ')) == NULL)
239 ec405b99 2024-03-28 op errx(1, "malformed line");
243 ec405b99 2024-03-28 op if ((t = strchr(t, ' ')) == NULL)
244 ec405b99 2024-03-28 op errx(1, "malformed line");
248 ec405b99 2024-03-28 op if ((t = strchr(t, ' ')) == NULL)
249 ec405b99 2024-03-28 op errx(1, "malformed line");
254 93623901 2024-03-28 op fprintf(fp, "{\"type\":\"commit\",\"short\":true,");
255 c1003102 2024-04-15 op json_field(fp, "repo", repo, 1);
256 841969e1 2024-04-30 op json_field(fp, "authenticated_user", user, 1);
257 ec405b99 2024-03-28 op json_field(fp, "id", id, 1);
258 ec405b99 2024-03-28 op json_author(fp, "committer", author, 1);
259 939d3016 2024-04-23 op json_date(fp, "date", date, 1);
260 ec405b99 2024-03-28 op json_field(fp, "short_message", message, 0);
261 ec405b99 2024-03-28 op fprintf(fp, "}");
267 d36998ae 2024-04-29 stsp jsonify_commit(FILE *fp, const char *repo, const char *user,
268 d36998ae 2024-04-29 stsp char **line, ssize_t *linesize)
270 5565365c 2024-03-27 op const char *errstr;
271 ac0a4dfc 2024-03-28 op char *author = NULL;
272 763b7f49 2024-03-28 op char *filename, *t;
274 5565365c 2024-03-27 op ssize_t linelen;
275 5565365c 2024-03-27 op int parent = 0;
276 5565365c 2024-03-27 op int msglen = 0, msgwrote = 0;
277 763b7f49 2024-03-28 op int n, files = 0;
278 ec405b99 2024-03-28 op int done = 0;
288 ec405b99 2024-03-28 op } phase = P_FROM;
291 ec405b99 2024-03-28 op if (strncmp(l, "commit ", 7) != 0)
292 ec405b99 2024-03-28 op errx(1, "%s: unexpected line: %s", __func__, l);
295 93623901 2024-03-28 op fprintf(fp, "{\"type\":\"commit\",\"short\":false,");
296 c1003102 2024-04-15 op json_field(fp, "repo", repo, 1);
297 841969e1 2024-04-30 op json_field(fp, "authenticated_user", user, 1);
298 ec405b99 2024-03-28 op json_field(fp, "id", l, 1);
300 ec405b99 2024-03-28 op while (!done) {
301 ec405b99 2024-03-28 op if ((linelen = getline(line, linesize, stdin)) == -1)
304 ec405b99 2024-03-28 op if ((*line)[linelen - 1] == '\n')
305 ec405b99 2024-03-28 op (*line)[--linelen] = '\0';
308 ec405b99 2024-03-28 op switch (phase) {
310 5565365c 2024-03-27 op if (strncmp(l, "from: ", 6) != 0)
311 5565365c 2024-03-27 op errx(1, "unexpected from line");
314 ac0a4dfc 2024-03-28 op author = strdup(l);
315 ac0a4dfc 2024-03-28 op if (author == NULL)
316 b3fabc96 2024-04-26 op fatal("strdup");
318 ac0a4dfc 2024-03-28 op json_author(fp, "author", l, 1);
320 5565365c 2024-03-27 op phase = P_VIA;
324 5565365c 2024-03-27 op /* optional */
325 5565365c 2024-03-27 op if (!strncmp(l, "via: ", 5)) {
327 ac0a4dfc 2024-03-28 op json_author(fp, "committer", l, 1);
328 5565365c 2024-03-27 op phase = P_DATE;
332 ac0a4dfc 2024-03-28 op if (author == NULL) /* impossible */
333 b3fabc96 2024-04-26 op fatalx("from not specified");
334 ac0a4dfc 2024-03-28 op json_author(fp, "committer", author, 1);
335 ac0a4dfc 2024-03-28 op free(author);
336 ac0a4dfc 2024-03-28 op author = NULL;
338 5565365c 2024-03-27 op phase = P_DATE;
339 5565365c 2024-03-27 op /* fallthrough */
342 5565365c 2024-03-27 op /* optional */
343 5565365c 2024-03-27 op if (!strncmp(l, "date: ", 6)) {
345 939d3016 2024-04-23 op json_date(fp, "date", l, 1);
346 5565365c 2024-03-27 op phase = P_PARENT;
349 5565365c 2024-03-27 op phase = P_PARENT;
350 5565365c 2024-03-27 op /* fallthough */
352 5565365c 2024-03-27 op case P_PARENT:
353 5565365c 2024-03-27 op /* optional - more than one */
354 5565365c 2024-03-27 op if (!strncmp(l, "parent ", 7)) {
356 5565365c 2024-03-27 op l += strcspn(l, ":");
357 5565365c 2024-03-27 op l += strspn(l, " ");
359 5565365c 2024-03-27 op if (parent == 0) {
361 5565365c 2024-03-27 op fprintf(fp, "\"parents\":[");
364 5565365c 2024-03-27 op fputc('"', fp);
365 5565365c 2024-03-27 op escape(fp, l);
366 5565365c 2024-03-27 op fputc('"', fp);
370 5565365c 2024-03-27 op if (parent != 0) {
371 5565365c 2024-03-27 op fprintf(fp, "],");
374 5565365c 2024-03-27 op phase = P_MSGLEN;
375 5565365c 2024-03-27 op /* fallthrough */
377 5565365c 2024-03-27 op case P_MSGLEN:
378 5565365c 2024-03-27 op if (strncmp(l, "messagelen: ", 12) != 0)
379 5565365c 2024-03-27 op errx(1, "unexpected messagelen line");
381 5565365c 2024-03-27 op msglen = strtonum(l, 1, INT_MAX, &errstr);
383 5565365c 2024-03-27 op errx(1, "message len is %s: %s", errstr, l);
387 5565365c 2024-03-27 op phase = P_MSG;
392 5565365c 2024-03-27 op * The commit message is indented with one extra
393 5565365c 2024-03-27 op * space which is not accounted for in messagelen,
394 5565365c 2024-03-27 op * but we also strip the trailing \n so that
395 5565365c 2024-03-27 op * accounts for it.
397 5565365c 2024-03-27 op * Since we read line-by-line and there is always
398 5565365c 2024-03-27 op * a \n added at the end of the message,
399 5565365c 2024-03-27 op * tolerate one byte less than advertised.
401 763b7f49 2024-03-28 op if (*l != ' ')
402 763b7f49 2024-03-28 op errx(1, "unexpected line in commit message");
404 763b7f49 2024-03-28 op l++; /* skip leading space */
407 763b7f49 2024-03-28 op if (msgwrote == 0 && linelen != 0) {
408 763b7f49 2024-03-28 op json_field(fp, "short_message", l, 1);
409 763b7f49 2024-03-28 op fprintf(fp, "\"message\":\"");
410 763b7f49 2024-03-28 op escape(fp, l);
411 763b7f49 2024-03-28 op escape(fp, "\n");
412 763b7f49 2024-03-28 op msgwrote += linelen;
413 763b7f49 2024-03-28 op } else if (msgwrote != 0) {
414 763b7f49 2024-03-28 op escape(fp, l);
415 763b7f49 2024-03-28 op escape(fp, "\n");
418 ac0a4dfc 2024-03-28 op msglen -= linelen + 1;
419 5565365c 2024-03-27 op if (msglen <= 1) {
420 5565365c 2024-03-27 op fprintf(fp, "\",");
421 5565365c 2024-03-27 op phase = P_DST;
427 763b7f49 2024-03-28 op if (files == 0 && !strcmp(l, " "))
430 763b7f49 2024-03-28 op if (files == 0)
431 763b7f49 2024-03-28 op fputs("\"diffstat\":{\"files\":[", fp);
433 ec405b99 2024-03-28 op if (*l == '\0') {
434 763b7f49 2024-03-28 op fputs("],", fp);
435 5565365c 2024-03-27 op phase = P_SUM;
439 763b7f49 2024-03-28 op if (*l != ' ')
440 763b7f49 2024-03-28 op errx(1, "bad diffstat line");
443 763b7f49 2024-03-28 op if (files != 0)
444 763b7f49 2024-03-28 op fputc(',', fp);
445 763b7f49 2024-03-28 op fputc('{', fp);
447 763b7f49 2024-03-28 op switch (*l) {
449 763b7f49 2024-03-28 op json_field(fp, "action", "added", 1);
452 763b7f49 2024-03-28 op json_field(fp, "action", "deleted", 1);
455 763b7f49 2024-03-28 op json_field(fp, "action", "modified", 1);
458 763b7f49 2024-03-28 op json_field(fp, "action", "mode changed", 1);
461 763b7f49 2024-03-28 op json_field(fp, "action", "unknown", 1);
466 763b7f49 2024-03-28 op while (*l == ' ')
468 763b7f49 2024-03-28 op if (*l == '\0')
469 763b7f49 2024-03-28 op errx(1, "invalid diffstat: no filename");
471 763b7f49 2024-03-28 op filename = l;
472 763b7f49 2024-03-28 op l = strrchr(l, '|');
473 763b7f49 2024-03-28 op if (l == NULL)
474 763b7f49 2024-03-28 op errx(1, "invalid diffstat: no separator");
476 763b7f49 2024-03-28 op while (t > filename && *t == ' ')
478 763b7f49 2024-03-28 op json_field(fp, "file", filename, 1);
481 763b7f49 2024-03-28 op while (*l == ' ')
484 763b7f49 2024-03-28 op t = strchr(l, '+');
485 763b7f49 2024-03-28 op if (t == NULL)
486 763b7f49 2024-03-28 op errx(1, "invalid diffstat: no added counter");
489 763b7f49 2024-03-28 op n = strtonum(l, 0, INT_MAX, &errstr);
491 763b7f49 2024-03-28 op errx(1, "added counter is %s: %s", errstr, l);
492 763b7f49 2024-03-28 op fprintf(fp, "\"added\":%d,", n);
495 763b7f49 2024-03-28 op while (*l == ' ')
498 763b7f49 2024-03-28 op t = strchr(l, '-');
499 763b7f49 2024-03-28 op if (t == NULL)
500 763b7f49 2024-03-28 op errx(1, "invalid diffstat: no del counter");
503 763b7f49 2024-03-28 op n = strtonum(l, 0, INT_MAX, &errstr);
505 763b7f49 2024-03-28 op errx(1, "del counter is %s: %s", errstr, l);
506 763b7f49 2024-03-28 op fprintf(fp, "\"removed\":%d", n);
508 763b7f49 2024-03-28 op fputc('}', fp);
515 763b7f49 2024-03-28 op fputs("\"total\":{", fp);
518 763b7f49 2024-03-28 op l = strchr(l, ' ');
519 763b7f49 2024-03-28 op if (l == NULL)
520 763b7f49 2024-03-28 op errx(1, "missing number of additions");
523 763b7f49 2024-03-28 op n = strtonum(t, 0, INT_MAX, &errstr);
525 763b7f49 2024-03-28 op errx(1, "add counter is %s: %s", errstr, t);
526 763b7f49 2024-03-28 op fprintf(fp, "\"added\":%d,", n);
528 763b7f49 2024-03-28 op l = strchr(l, ',');
529 763b7f49 2024-03-28 op if (l == NULL)
530 763b7f49 2024-03-28 op errx(1, "missing number of deletions");
532 763b7f49 2024-03-28 op while (*l == ' ')
535 763b7f49 2024-03-28 op t = strchr(l, ' ');
536 763b7f49 2024-03-28 op if (t == NULL)
537 763b7f49 2024-03-28 op errx(1, "malformed diffstat sum line");
540 763b7f49 2024-03-28 op n = strtonum(l, 0, INT_MAX, &errstr);
542 763b7f49 2024-03-28 op errx(1, "del counter is %s: %s", errstr, l);
543 763b7f49 2024-03-28 op fprintf(fp, "\"removed\":%d", n);
545 763b7f49 2024-03-28 op fputs("}}", fp);
550 ec405b99 2024-03-28 op /* unreachable */
551 ec405b99 2024-03-28 op errx(1, "unexpected line: %s", *line);
554 5565365c 2024-03-27 op if (ferror(stdin))
555 b3fabc96 2024-04-26 op fatalx("getline");
557 b3fabc96 2024-04-26 op fatalx("%s: unexpected EOF", __func__);
558 763b7f49 2024-03-28 op fputc('}', fp);
564 d36998ae 2024-04-29 stsp jsonify_tag(FILE *fp, const char *repo, const char *user,
565 d36998ae 2024-04-29 stsp char **line, ssize_t *linesize)
567 553d8347 2024-03-28 op const char *errstr;
569 553d8347 2024-03-28 op ssize_t linelen;
570 553d8347 2024-03-28 op int msglen = 0, msgwrote = 0;
571 553d8347 2024-03-28 op int done = 0;
578 553d8347 2024-03-28 op } phase = P_FROM;
581 553d8347 2024-03-28 op if (strncmp(l, "tag ", 4) != 0)
582 553d8347 2024-03-28 op errx(1, "%s: unexpected line: %s", __func__, l);
585 553d8347 2024-03-28 op fputc('{', fp);
586 553d8347 2024-03-28 op json_field(fp, "type", "tag", 1);
587 c1003102 2024-04-15 op json_field(fp, "repo", repo, 1);
588 841969e1 2024-04-30 op json_field(fp, "authenticated_user", user, 1);
589 553d8347 2024-03-28 op json_field(fp, "tag", l, 1);
591 553d8347 2024-03-28 op while (!done) {
592 553d8347 2024-03-28 op if ((linelen = getline(line, linesize, stdin)) == -1)
595 553d8347 2024-03-28 op if ((*line)[linelen - 1] == '\n')
596 553d8347 2024-03-28 op (*line)[--linelen] = '\0';
599 553d8347 2024-03-28 op switch (phase) {
601 553d8347 2024-03-28 op if (strncmp(l, "from: ", 6) != 0)
602 553d8347 2024-03-28 op errx(1, "unexpected from line");
605 553d8347 2024-03-28 op json_author(fp, "tagger", l, 1);
607 553d8347 2024-03-28 op phase = P_DATE;
611 553d8347 2024-03-28 op /* optional */
612 553d8347 2024-03-28 op if (!strncmp(l, "date: ", 6)) {
614 939d3016 2024-04-23 op json_date(fp, "date", l, 1);
615 553d8347 2024-03-28 op phase = P_OBJECT;
618 553d8347 2024-03-28 op phase = P_OBJECT;
619 553d8347 2024-03-28 op /* fallthough */
621 553d8347 2024-03-28 op case P_OBJECT:
622 553d8347 2024-03-28 op /* optional */
623 553d8347 2024-03-28 op if (!strncmp(l, "object: ", 8)) {
624 553d8347 2024-03-28 op char *type, *id;
628 553d8347 2024-03-28 op id = strchr(l, ' ');
629 553d8347 2024-03-28 op if (id == NULL)
630 553d8347 2024-03-28 op errx(1, "malformed tag object line");
631 553d8347 2024-03-28 op *id++ = '\0';
633 553d8347 2024-03-28 op fputs("\"object\":{", fp);
634 553d8347 2024-03-28 op json_field(fp, "type", type, 1);
635 553d8347 2024-03-28 op json_field(fp, "id", id, 0);
636 553d8347 2024-03-28 op fputs("},", fp);
638 553d8347 2024-03-28 op phase = P_MSGLEN;
641 553d8347 2024-03-28 op phase = P_MSGLEN;
642 553d8347 2024-03-28 op /* fallthrough */
644 553d8347 2024-03-28 op case P_MSGLEN:
645 553d8347 2024-03-28 op if (strncmp(l, "messagelen: ", 12) != 0)
646 553d8347 2024-03-28 op errx(1, "unexpected messagelen line");
648 553d8347 2024-03-28 op msglen = strtonum(l, 1, INT_MAX, &errstr);
650 553d8347 2024-03-28 op errx(1, "message len is %s: %s", errstr, l);
654 553d8347 2024-03-28 op phase = P_MSG;
658 763b7f49 2024-03-28 op if (*l != ' ')
659 763b7f49 2024-03-28 op errx(1, "unexpected line in tag message");
661 763b7f49 2024-03-28 op l++; /* skip leading space */
664 763b7f49 2024-03-28 op if (msgwrote == 0 && linelen != 0) {
665 763b7f49 2024-03-28 op fprintf(fp, "\"message\":\"");
666 763b7f49 2024-03-28 op escape(fp, l);
667 763b7f49 2024-03-28 op escape(fp, "\n");
668 763b7f49 2024-03-28 op msgwrote += linelen;
669 763b7f49 2024-03-28 op } else if (msgwrote != 0) {
670 763b7f49 2024-03-28 op escape(fp, l);
671 763b7f49 2024-03-28 op escape(fp, "\n");
674 553d8347 2024-03-28 op msglen -= linelen + 1;
675 e789f02b 2024-03-28 op if (msglen <= 0) {
676 553d8347 2024-03-28 op fprintf(fp, "\"");
683 553d8347 2024-03-28 op /* unreachable */
684 553d8347 2024-03-28 op errx(1, "unexpected line: %s", *line);
687 553d8347 2024-03-28 op if (ferror(stdin))
688 b3fabc96 2024-04-26 op fatal("getline");
690 b3fabc96 2024-04-26 op fatalx("%s: unexpected EOF", __func__);
691 553d8347 2024-03-28 op fputc('}', fp);
697 d36998ae 2024-04-29 stsp jsonify(FILE *fp, const char *repo, const char *user)
699 ec405b99 2024-03-28 op char *line = NULL;
700 ec405b99 2024-03-28 op size_t linesize = 0;
701 ec405b99 2024-03-28 op ssize_t linelen;
702 ec405b99 2024-03-28 op int needcomma = 0;
704 ec405b99 2024-03-28 op fprintf(fp, "{\"notifications\":[");
705 ec405b99 2024-03-28 op while ((linelen = getline(&line, &linesize, stdin)) != -1) {
706 ec405b99 2024-03-28 op if (line[linelen - 1] == '\n')
707 ec405b99 2024-03-28 op line[--linelen] = '\0';
709 ec405b99 2024-03-28 op if (*line == '\0')
712 ec405b99 2024-03-28 op if (needcomma)
713 ec405b99 2024-03-28 op fputc(',', fp);
714 ec405b99 2024-03-28 op needcomma = 1;
716 d6057084 2024-03-28 op if (strncmp(line, "Removed refs/heads/", 19) == 0) {
717 d36998ae 2024-04-29 stsp if (jsonify_branch_rm(fp, line, repo, user) == -1)
718 b3fabc96 2024-04-26 op fatal("jsonify_branch_rm");
722 ec405b99 2024-03-28 op if (strncmp(line, "commit ", 7) == 0) {
723 d36998ae 2024-04-29 stsp if (jsonify_commit(fp, repo, user,
724 d36998ae 2024-04-29 stsp &line, &linesize) == -1)
725 b3fabc96 2024-04-26 op fatal("jsonify_commit");
729 ec405b99 2024-03-28 op if (*line >= '0' && *line <= '9') {
730 d36998ae 2024-04-29 stsp if (jsonify_commit_short(fp, line, repo, user) == -1)
731 b3fabc96 2024-04-26 op fatal("jsonify_commit_short");
735 553d8347 2024-03-28 op if (strncmp(line, "tag ", 4) == 0) {
736 d36998ae 2024-04-29 stsp if (jsonify_tag(fp, repo, user, &line, &linesize) == -1)
737 b3fabc96 2024-04-26 op fatal("jsonify_tag");
741 ec405b99 2024-03-28 op errx(1, "unexpected line: %s", line);
743 ec405b99 2024-03-28 op if (ferror(stdin))
744 b3fabc96 2024-04-26 op fatal("getline");
745 5565365c 2024-03-27 op fprintf(fp, "]}");
751 050c0b8c 2024-04-16 op sixet2ch(int c)
756 050c0b8c 2024-04-16 op return 'A' + c;
759 050c0b8c 2024-04-16 op return 'a' + c;
762 050c0b8c 2024-04-16 op return '0' + c;
769 050c0b8c 2024-04-16 op errx(1, "invalid sixet 0x%x", c);
772 5565365c 2024-03-27 op static char *
773 5565365c 2024-03-27 op basic_auth(const char *username, const char *password)
775 050c0b8c 2024-04-16 op char *str, *tmp, *end, *s, *p;
777 050c0b8c 2024-04-16 op int len, i, r;
779 050c0b8c 2024-04-16 op r = asprintf(&str, "%s:%s", username, password);
781 b3fabc96 2024-04-26 op fatal("asprintf");
784 050c0b8c 2024-04-16 op * Will need 4 * r/3 bytes to encode the string, plus a
785 050c0b8c 2024-04-16 op * rounding to the next multiple of 4 for padding, plus NUL.
787 050c0b8c 2024-04-16 op len = 4 * r / 3;
788 050c0b8c 2024-04-16 op len = (len + 3) & ~3;
791 050c0b8c 2024-04-16 op tmp = calloc(1, len);
792 050c0b8c 2024-04-16 op if (tmp == NULL)
793 b3fabc96 2024-04-26 op fatal("calloc");
797 050c0b8c 2024-04-16 op while (*s != '\0') {
798 050c0b8c 2024-04-16 op memset(buf, 0, sizeof(buf));
799 050c0b8c 2024-04-16 op for (i = 0; i < 3 && *s != '\0'; ++i, ++s)
802 050c0b8c 2024-04-16 op *p++ = sixet2ch(buf[0] >> 2);
803 050c0b8c 2024-04-16 op *p++ = sixet2ch((buf[1] >> 4) | (buf[0] << 4));
805 050c0b8c 2024-04-16 op *p++ = sixet2ch((buf[1] << 2) | (buf[2] >> 6));
807 050c0b8c 2024-04-16 op *p++ = sixet2ch(buf[2]);
810 050c0b8c 2024-04-16 op for (end = tmp + len - 1; p < end; ++p)
817 5565365c 2024-03-27 op static inline int
818 5565365c 2024-03-27 op bufio2poll(struct bufio *bio)
820 5565365c 2024-03-27 op int f, ret = 0;
823 770f8d29 2024-04-26 op * If we have data queued up, retry for both POLLIN and POLLOUT
824 770f8d29 2024-04-26 op * since we want to push this data to the server while still
825 770f8d29 2024-04-26 op * processing an eventual reply. Otherwise, we could wait
826 770f8d29 2024-04-26 op * indefinitely for the server to reply without us having
827 770f8d29 2024-04-26 op * sent the HTTP request completely.
829 770f8d29 2024-04-26 op if (bio->wbuf.len)
830 770f8d29 2024-04-26 op return POLLIN|POLLOUT;
832 5565365c 2024-03-27 op f = bufio_ev(bio);
833 5565365c 2024-03-27 op if (f & BUFIO_WANT_READ)
834 5565365c 2024-03-27 op ret |= POLLIN;
835 5565365c 2024-03-27 op if (f & BUFIO_WANT_WRITE)
836 5565365c 2024-03-27 op ret |= POLLOUT;
841 5565365c 2024-03-27 op main(int argc, char **argv)
844 5565365c 2024-03-27 op struct bufio bio;
845 5565365c 2024-03-27 op struct pollfd pfd;
846 5565365c 2024-03-27 op struct timespec timeout;
847 5565365c 2024-03-27 op const char *username;
848 5565365c 2024-03-27 op const char *password;
849 5565365c 2024-03-27 op const char *timeoutstr;
850 5565365c 2024-03-27 op const char *errstr;
851 c1003102 2024-04-15 op const char *repo = NULL;
852 5565365c 2024-03-27 op const char *host = NULL, *port = NULL, *path = NULL;
853 d36998ae 2024-04-29 stsp const char *gotd_auth_user = NULL;
854 5565365c 2024-03-27 op char *auth, *line, *spc;
857 5565365c 2024-03-27 op off_t paylen;
859 5565365c 2024-03-27 op int response_code = 0, done = 0;
860 5565365c 2024-03-27 op int ch, flags, ret, nonstd = 0;
862 5565365c 2024-03-27 op #ifndef PROFILE
863 5565365c 2024-03-27 op if (pledge("stdio rpath tmppath dns inet", NULL) == -1)
864 5565365c 2024-03-27 op err(1, "pledge");
867 cb29e255 2024-04-18 stsp log_init(0, LOG_DAEMON);
869 d36998ae 2024-04-29 stsp while ((ch = getopt(argc, argv, "ch:p:r:u:")) != -1) {
870 5565365c 2024-03-27 op switch (ch) {
875 5565365c 2024-03-27 op host = optarg;
878 5565365c 2024-03-27 op port = optarg;
881 c1003102 2024-04-15 op repo = optarg;
884 d36998ae 2024-04-29 stsp gotd_auth_user = optarg;
890 5565365c 2024-03-27 op argc -= optind;
891 5565365c 2024-03-27 op argv += optind;
893 9d42388a 2024-04-29 stsp if (host == NULL || repo == NULL || gotd_auth_user == NULL || argc != 1)
895 5565365c 2024-03-27 op if (tls && port == NULL)
896 5565365c 2024-03-27 op port = "443";
897 5565365c 2024-03-27 op path = argv[0];
899 5565365c 2024-03-27 op username = getenv("GOT_NOTIFY_HTTP_USER");
900 5565365c 2024-03-27 op password = getenv("GOT_NOTIFY_HTTP_PASS");
901 5565365c 2024-03-27 op if ((username != NULL && password == NULL) ||
902 5565365c 2024-03-27 op (username == NULL && password != NULL))
903 cb29e255 2024-04-18 stsp fatalx("username or password are not specified");
904 5565365c 2024-03-27 op if (username && *password == '\0')
905 cb29e255 2024-04-18 stsp fatalx("password can't be empty");
907 5565365c 2024-03-27 op /* used by the regression test suite */
908 5565365c 2024-03-27 op timeoutstr = getenv("GOT_NOTIFY_TIMEOUT");
909 5565365c 2024-03-27 op if (timeoutstr) {
910 5565365c 2024-03-27 op http_timeout = strtonum(timeoutstr, 0, 600, &errstr);
911 5565365c 2024-03-27 op if (errstr != NULL)
912 cb29e255 2024-04-18 stsp fatalx("timeout in seconds is %s: %s",
913 5565365c 2024-03-27 op errstr, timeoutstr);
916 5565365c 2024-03-27 op memset(&timeout, 0, sizeof(timeout));
917 5565365c 2024-03-27 op timeout.tv_sec = http_timeout;
919 5565365c 2024-03-27 op tmpfp = got_opentemp();
920 5565365c 2024-03-27 op if (tmpfp == NULL)
921 cb29e255 2024-04-18 stsp fatal("opentemp");
923 d36998ae 2024-04-29 stsp jsonify(tmpfp, repo, gotd_auth_user);
925 5565365c 2024-03-27 op paylen = ftello(tmpfp);
926 5565365c 2024-03-27 op if (paylen == -1)
927 cb29e255 2024-04-18 stsp fatal("ftello");
928 5565365c 2024-03-27 op if (fseeko(tmpfp, 0, SEEK_SET) == -1)
929 cb29e255 2024-04-18 stsp fatal("fseeko");
931 5565365c 2024-03-27 op #ifndef PROFILE
932 5565365c 2024-03-27 op /* drop tmppath */
933 5565365c 2024-03-27 op if (pledge("stdio rpath dns inet", NULL) == -1)
934 5565365c 2024-03-27 op err(1, "pledge");
937 5565365c 2024-03-27 op memset(&pfd, 0, sizeof(pfd));
938 5565365c 2024-03-27 op pfd.fd = dial(host, port);
940 5565365c 2024-03-27 op if ((flags = fcntl(pfd.fd, F_GETFL)) == -1)
941 cb29e255 2024-04-18 stsp fatal("fcntl(F_GETFL)");
942 5565365c 2024-03-27 op if (fcntl(pfd.fd, F_SETFL, flags | O_NONBLOCK) == -1)
943 cb29e255 2024-04-18 stsp fatal("fcntl(F_SETFL)");
945 5565365c 2024-03-27 op if (bufio_init(&bio) == -1)
946 cb29e255 2024-04-18 stsp fatal("bufio_init");
947 5565365c 2024-03-27 op bufio_set_fd(&bio, pfd.fd);
948 5565365c 2024-03-27 op if (tls && bufio_starttls(&bio, host, 0, NULL, 0, NULL, 0) == -1)
949 cb29e255 2024-04-18 stsp fatal("bufio_starttls");
951 5565365c 2024-03-27 op #ifndef PROFILE
952 5565365c 2024-03-27 op /* drop rpath dns inet */
953 5565365c 2024-03-27 op if (pledge("stdio", NULL) == -1)
954 5565365c 2024-03-27 op err(1, "pledge");
957 5565365c 2024-03-27 op if ((!tls && strcmp(port, "80") != 0) ||
958 5565365c 2024-03-27 op (tls && strcmp(port, "443")) != 0)
961 5565365c 2024-03-27 op ret = bufio_compose_fmt(&bio,
962 5565365c 2024-03-27 op "POST %s HTTP/1.1\r\n"
963 5565365c 2024-03-27 op "Host: %s%s%s\r\n"
964 5565365c 2024-03-27 op "Content-Type: application/json\r\n"
965 5565365c 2024-03-27 op "Content-Length: %lld\r\n"
966 5565365c 2024-03-27 op "User-Agent: %s\r\n"
967 5565365c 2024-03-27 op "Connection: close\r\n",
969 5565365c 2024-03-27 op nonstd ? ":" : "", nonstd ? port : "",
970 5565365c 2024-03-27 op (long long)paylen, USERAGENT);
971 5565365c 2024-03-27 op if (ret == -1)
972 cb29e255 2024-04-18 stsp fatal("bufio_compose_fmt");
974 5565365c 2024-03-27 op if (username) {
975 5565365c 2024-03-27 op auth = basic_auth(username, password);
976 5565365c 2024-03-27 op ret = bufio_compose_fmt(&bio, "Authorization: basic %s\r\n",
978 5565365c 2024-03-27 op if (ret == -1)
979 cb29e255 2024-04-18 stsp fatal("bufio_compose_fmt");
983 5565365c 2024-03-27 op if (bufio_compose(&bio, "\r\n", 2) == -1)
984 cb29e255 2024-04-18 stsp fatal("bufio_compose");
986 5565365c 2024-03-27 op while (!done) {
987 5565365c 2024-03-27 op struct timespec elapsed, start, stop;
988 5565365c 2024-03-27 op char buf[BUFSIZ];
990 5565365c 2024-03-27 op pfd.events = bufio2poll(&bio);
991 5565365c 2024-03-27 op clock_gettime(CLOCK_MONOTONIC, &start);
992 5565365c 2024-03-27 op ret = ppoll(&pfd, 1, &timeout, NULL);
993 5565365c 2024-03-27 op if (ret == -1)
994 cb29e255 2024-04-18 stsp fatal("poll");
995 5565365c 2024-03-27 op clock_gettime(CLOCK_MONOTONIC, &stop);
996 5565365c 2024-03-27 op timespecsub(&stop, &start, &elapsed);
997 5565365c 2024-03-27 op timespecsub(&timeout, &elapsed, &timeout);
998 5565365c 2024-03-27 op if (ret == 0 || timeout.tv_sec <= 0)
999 cb29e255 2024-04-18 stsp fatalx("timeout");
1001 a9d9f6e4 2024-04-18 op if (bio.wbuf.len > 0) {
1002 5565365c 2024-03-27 op if (bufio_write(&bio) == -1 && errno != EAGAIN)
1003 cb29e255 2024-04-18 stsp fatalx("bufio_write: %s", bufio_io_err(&bio));
1006 a9d9f6e4 2024-04-18 op r = bufio_read(&bio);
1007 a9d9f6e4 2024-04-18 op if (r == -1 && errno != EAGAIN)
1008 a9d9f6e4 2024-04-18 op fatalx("bufio_read: %s", bufio_io_err(&bio));
1010 8aebbd0a 2024-04-26 op fatalx("unexpected EOF from upstream HTTP server");
1013 a9d9f6e4 2024-04-18 op line = buf_getdelim(&bio.rbuf, "\r\n", &len);
1014 a9d9f6e4 2024-04-18 op if (line == NULL)
1016 a9d9f6e4 2024-04-18 op if (response_code && *line == '\0') {
1018 a9d9f6e4 2024-04-18 op * end of headers, don't bother
1019 a9d9f6e4 2024-04-18 op * reading the body, if there is.
1024 a9d9f6e4 2024-04-18 op if (response_code) {
1025 5565365c 2024-03-27 op buf_drain(&bio.rbuf, len);
1028 a9d9f6e4 2024-04-18 op spc = strchr(line, ' ');
1029 a9d9f6e4 2024-04-18 op if (spc == NULL)
1030 a9d9f6e4 2024-04-18 op fatalx("bad HTTP response from server");
1031 a9d9f6e4 2024-04-18 op *spc++ = '\0';
1032 a9d9f6e4 2024-04-18 op if (strcasecmp(line, "HTTP/1.1") != 0)
1033 a9d9f6e4 2024-04-18 op log_warnx("unexpected protocol: %s", line);
1036 a9d9f6e4 2024-04-18 op spc = strchr(line, ' ');
1037 a9d9f6e4 2024-04-18 op if (spc == NULL)
1038 a9d9f6e4 2024-04-18 op fatalx("bad HTTP response from server");
1039 a9d9f6e4 2024-04-18 op *spc++ = '\0';
1041 a9d9f6e4 2024-04-18 op response_code = strtonum(line, 100, 599,
1043 a9d9f6e4 2024-04-18 op if (errstr != NULL)
1044 a9d9f6e4 2024-04-18 op log_warnx("response code is %s: %s",
1045 a9d9f6e4 2024-04-18 op errstr, line);
1047 a9d9f6e4 2024-04-18 op buf_drain(&bio.rbuf, len);
1052 5565365c 2024-03-27 op if (!feof(tmpfp) && bio.wbuf.len < sizeof(buf)) {
1053 5565365c 2024-03-27 op len = fread(buf, 1, sizeof(buf), tmpfp);
1054 5565365c 2024-03-27 op if (len == 0) {
1055 5565365c 2024-03-27 op if (ferror(tmpfp))
1056 cb29e255 2024-04-18 stsp fatal("fread");
1060 5565365c 2024-03-27 op if (bufio_compose(&bio, buf, len) == -1)
1061 cb29e255 2024-04-18 stsp fatal("buf_compose");
1065 3b44bdbe 2024-03-27 op if (response_code >= 200 && response_code < 300)
1067 9e2d0515 2024-04-25 op fatalx("request failed with code %d", response_code);