Blame


1 94a3f4e9 2024-03-30 thomas /*
2 94a3f4e9 2024-03-30 thomas * Copyright (c) 2024 Omar Polo <op@openbsd.org>
3 94a3f4e9 2024-03-30 thomas *
4 94a3f4e9 2024-03-30 thomas * Permission to use, copy, modify, and distribute this software for any
5 94a3f4e9 2024-03-30 thomas * purpose with or without fee is hereby granted, provided that the above
6 94a3f4e9 2024-03-30 thomas * copyright notice and this permission notice appear in all copies.
7 94a3f4e9 2024-03-30 thomas *
8 94a3f4e9 2024-03-30 thomas * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 94a3f4e9 2024-03-30 thomas * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 94a3f4e9 2024-03-30 thomas * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 94a3f4e9 2024-03-30 thomas * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 94a3f4e9 2024-03-30 thomas * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 94a3f4e9 2024-03-30 thomas * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 94a3f4e9 2024-03-30 thomas * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 94a3f4e9 2024-03-30 thomas */
16 a58e44b0 2024-03-30 thomas
17 a58e44b0 2024-03-30 thomas #include "got_compat.h"
18 94a3f4e9 2024-03-30 thomas
19 94a3f4e9 2024-03-30 thomas #include <sys/time.h>
20 94a3f4e9 2024-03-30 thomas #include <sys/types.h>
21 94a3f4e9 2024-03-30 thomas #include <sys/socket.h>
22 94a3f4e9 2024-03-30 thomas
23 94a3f4e9 2024-03-30 thomas #include <err.h>
24 94a3f4e9 2024-03-30 thomas #include <errno.h>
25 94a3f4e9 2024-03-30 thomas #include <fcntl.h>
26 94a3f4e9 2024-03-30 thomas #include <limits.h>
27 94a3f4e9 2024-03-30 thomas #include <netdb.h>
28 94a3f4e9 2024-03-30 thomas #include <poll.h>
29 94a3f4e9 2024-03-30 thomas #include <stdio.h>
30 94a3f4e9 2024-03-30 thomas #include <stdlib.h>
31 94a3f4e9 2024-03-30 thomas #include <string.h>
32 94a3f4e9 2024-03-30 thomas #include <unistd.h>
33 94a3f4e9 2024-03-30 thomas
34 94a3f4e9 2024-03-30 thomas #include "got_opentemp.h"
35 94a3f4e9 2024-03-30 thomas #include "got_version.h"
36 94a3f4e9 2024-03-30 thomas
37 94a3f4e9 2024-03-30 thomas #include "bufio.h"
38 fcfdd0a1 2024-03-30 thomas #include "utf8d.h"
39 94a3f4e9 2024-03-30 thomas
40 94a3f4e9 2024-03-30 thomas #define USERAGENT "got-notify-http/" GOT_VERSION_STR
41 94a3f4e9 2024-03-30 thomas
42 94a3f4e9 2024-03-30 thomas static int http_timeout = 300; /* 5 minutes in seconds */
43 94a3f4e9 2024-03-30 thomas
44 94a3f4e9 2024-03-30 thomas __dead static void
45 94a3f4e9 2024-03-30 thomas usage(void)
46 94a3f4e9 2024-03-30 thomas {
47 94a3f4e9 2024-03-30 thomas fprintf(stderr, "usage: %s [-c] -h host -p port path\n",
48 94a3f4e9 2024-03-30 thomas getprogname());
49 94a3f4e9 2024-03-30 thomas exit(1);
50 94a3f4e9 2024-03-30 thomas }
51 94a3f4e9 2024-03-30 thomas
52 94a3f4e9 2024-03-30 thomas static int
53 94a3f4e9 2024-03-30 thomas dial(const char *host, const char *port)
54 94a3f4e9 2024-03-30 thomas {
55 94a3f4e9 2024-03-30 thomas struct addrinfo hints, *res, *res0;
56 94a3f4e9 2024-03-30 thomas const char *cause = NULL;
57 94a3f4e9 2024-03-30 thomas int s, error, save_errno;
58 94a3f4e9 2024-03-30 thomas
59 94a3f4e9 2024-03-30 thomas memset(&hints, 0, sizeof(hints));
60 94a3f4e9 2024-03-30 thomas hints.ai_family = AF_UNSPEC;
61 94a3f4e9 2024-03-30 thomas hints.ai_socktype = SOCK_STREAM;
62 94a3f4e9 2024-03-30 thomas error = getaddrinfo(host, port, &hints, &res0);
63 94a3f4e9 2024-03-30 thomas if (error)
64 94a3f4e9 2024-03-30 thomas errx(1, "failed to resolve %s:%s: %s", host, port,
65 94a3f4e9 2024-03-30 thomas gai_strerror(error));
66 94a3f4e9 2024-03-30 thomas
67 94a3f4e9 2024-03-30 thomas s = -1;
68 94a3f4e9 2024-03-30 thomas for (res = res0; res; res = res->ai_next) {
69 94a3f4e9 2024-03-30 thomas s = socket(res->ai_family, res->ai_socktype,
70 94a3f4e9 2024-03-30 thomas res->ai_protocol);
71 94a3f4e9 2024-03-30 thomas if (s == -1) {
72 94a3f4e9 2024-03-30 thomas cause = "socket";
73 94a3f4e9 2024-03-30 thomas continue;
74 94a3f4e9 2024-03-30 thomas }
75 94a3f4e9 2024-03-30 thomas
76 94a3f4e9 2024-03-30 thomas if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
77 94a3f4e9 2024-03-30 thomas cause = "connect";
78 94a3f4e9 2024-03-30 thomas save_errno = errno;
79 94a3f4e9 2024-03-30 thomas close(s);
80 94a3f4e9 2024-03-30 thomas errno = save_errno;
81 94a3f4e9 2024-03-30 thomas s = -1;
82 94a3f4e9 2024-03-30 thomas continue;
83 94a3f4e9 2024-03-30 thomas }
84 94a3f4e9 2024-03-30 thomas
85 94a3f4e9 2024-03-30 thomas break;
86 94a3f4e9 2024-03-30 thomas }
87 94a3f4e9 2024-03-30 thomas
88 94a3f4e9 2024-03-30 thomas freeaddrinfo(res0);
89 94a3f4e9 2024-03-30 thomas if (s == -1)
90 94a3f4e9 2024-03-30 thomas err(1, "%s", cause);
91 94a3f4e9 2024-03-30 thomas return s;
92 94a3f4e9 2024-03-30 thomas }
93 94a3f4e9 2024-03-30 thomas
94 94a3f4e9 2024-03-30 thomas static void
95 94a3f4e9 2024-03-30 thomas escape(FILE *fp, const uint8_t *s)
96 94a3f4e9 2024-03-30 thomas {
97 fcfdd0a1 2024-03-30 thomas uint32_t codepoint, state;
98 fcfdd0a1 2024-03-30 thomas const uint8_t *start = s;
99 94a3f4e9 2024-03-30 thomas
100 fcfdd0a1 2024-03-30 thomas state = 0;
101 fcfdd0a1 2024-03-30 thomas for (; *s; ++s) {
102 fcfdd0a1 2024-03-30 thomas switch (decode(&state, &codepoint, *s)) {
103 fcfdd0a1 2024-03-30 thomas case UTF8_ACCEPT:
104 fcfdd0a1 2024-03-30 thomas switch (codepoint) {
105 fcfdd0a1 2024-03-30 thomas case '"':
106 fcfdd0a1 2024-03-30 thomas case '\\':
107 fcfdd0a1 2024-03-30 thomas fprintf(fp, "\\%c", *s);
108 fcfdd0a1 2024-03-30 thomas break;
109 fcfdd0a1 2024-03-30 thomas case '\b':
110 fcfdd0a1 2024-03-30 thomas fprintf(fp, "\\b");
111 fcfdd0a1 2024-03-30 thomas break;
112 fcfdd0a1 2024-03-30 thomas case '\f':
113 fcfdd0a1 2024-03-30 thomas fprintf(fp, "\\f");
114 fcfdd0a1 2024-03-30 thomas break;
115 fcfdd0a1 2024-03-30 thomas case '\n':
116 fcfdd0a1 2024-03-30 thomas fprintf(fp, "\\n");
117 fcfdd0a1 2024-03-30 thomas break;
118 fcfdd0a1 2024-03-30 thomas case '\r':
119 fcfdd0a1 2024-03-30 thomas fprintf(fp, "\\r");
120 fcfdd0a1 2024-03-30 thomas break;
121 fcfdd0a1 2024-03-30 thomas case '\t':
122 fcfdd0a1 2024-03-30 thomas fprintf(fp, "\\t");
123 fcfdd0a1 2024-03-30 thomas break;
124 fcfdd0a1 2024-03-30 thomas default:
125 fcfdd0a1 2024-03-30 thomas /* other control characters */
126 fcfdd0a1 2024-03-30 thomas if (codepoint < ' ' || codepoint == 0x7F) {
127 fcfdd0a1 2024-03-30 thomas fprintf(fp, "\\u%04x", codepoint);
128 fcfdd0a1 2024-03-30 thomas break;
129 fcfdd0a1 2024-03-30 thomas }
130 fcfdd0a1 2024-03-30 thomas fwrite(start, 1, s - start + 1, fp);
131 fcfdd0a1 2024-03-30 thomas break;
132 fcfdd0a1 2024-03-30 thomas }
133 fcfdd0a1 2024-03-30 thomas start = s + 1;
134 94a3f4e9 2024-03-30 thomas break;
135 fcfdd0a1 2024-03-30 thomas
136 fcfdd0a1 2024-03-30 thomas case UTF8_REJECT:
137 fcfdd0a1 2024-03-30 thomas /* bad UTF-8 sequence; try to recover */
138 fcfdd0a1 2024-03-30 thomas fputs("\\uFFFD", fp);
139 fcfdd0a1 2024-03-30 thomas state = UTF8_ACCEPT;
140 fcfdd0a1 2024-03-30 thomas start = s + 1;
141 94a3f4e9 2024-03-30 thomas break;
142 94a3f4e9 2024-03-30 thomas }
143 94a3f4e9 2024-03-30 thomas }
144 94a3f4e9 2024-03-30 thomas }
145 94a3f4e9 2024-03-30 thomas
146 94a3f4e9 2024-03-30 thomas static void
147 94a3f4e9 2024-03-30 thomas json_field(FILE *fp, const char *key, const char *val, int comma)
148 94a3f4e9 2024-03-30 thomas {
149 94a3f4e9 2024-03-30 thomas fprintf(fp, "\"%s\":\"", key);
150 94a3f4e9 2024-03-30 thomas escape(fp, val);
151 94a3f4e9 2024-03-30 thomas fprintf(fp, "\"%s", comma ? "," : "");
152 94a3f4e9 2024-03-30 thomas }
153 94a3f4e9 2024-03-30 thomas
154 9c485a11 2024-03-30 thomas static void
155 9c485a11 2024-03-30 thomas json_author(FILE *fp, const char *type, char *address, int comma)
156 9c485a11 2024-03-30 thomas {
157 9c485a11 2024-03-30 thomas char *gt, *lt, *at, *email, *endname;
158 9c485a11 2024-03-30 thomas
159 9c485a11 2024-03-30 thomas fprintf(fp, "\"%s\":{", type);
160 9c485a11 2024-03-30 thomas
161 9c485a11 2024-03-30 thomas gt = strchr(address, '<');
162 9c485a11 2024-03-30 thomas if (gt != NULL) {
163 9c485a11 2024-03-30 thomas /* long format, e.g. "Omar Polo <op@openbsd.org>" */
164 9c485a11 2024-03-30 thomas
165 9c485a11 2024-03-30 thomas json_field(fp, "full", address, 1);
166 9c485a11 2024-03-30 thomas
167 9c485a11 2024-03-30 thomas endname = gt;
168 9c485a11 2024-03-30 thomas while (endname > address && endname[-1] == ' ')
169 9c485a11 2024-03-30 thomas endname--;
170 9c485a11 2024-03-30 thomas
171 9c485a11 2024-03-30 thomas *endname = '\0';
172 9c485a11 2024-03-30 thomas json_field(fp, "name", address, 1);
173 9c485a11 2024-03-30 thomas
174 9c485a11 2024-03-30 thomas email = gt + 1;
175 9c485a11 2024-03-30 thomas lt = strchr(email, '>');
176 9c485a11 2024-03-30 thomas if (lt)
177 9c485a11 2024-03-30 thomas *lt = '\0';
178 9c485a11 2024-03-30 thomas
179 9c485a11 2024-03-30 thomas json_field(fp, "mail", email, 1);
180 9c485a11 2024-03-30 thomas
181 9c485a11 2024-03-30 thomas at = strchr(email, '@');
182 9c485a11 2024-03-30 thomas if (at)
183 9c485a11 2024-03-30 thomas *at = '\0';
184 9c485a11 2024-03-30 thomas
185 9c485a11 2024-03-30 thomas json_field(fp, "user", email, 0);
186 9c485a11 2024-03-30 thomas } else {
187 9c485a11 2024-03-30 thomas /* short format only shows the username */
188 9c485a11 2024-03-30 thomas json_field(fp, "user", address, 0);
189 9c485a11 2024-03-30 thomas }
190 9c485a11 2024-03-30 thomas
191 9c485a11 2024-03-30 thomas fprintf(fp, "}%s", comma ? "," : "");
192 9c485a11 2024-03-30 thomas }
193 9c485a11 2024-03-30 thomas
194 94a3f4e9 2024-03-30 thomas static int
195 4808d170 2024-03-30 thomas jsonify_branch_rm(FILE *fp, char *line)
196 4808d170 2024-03-30 thomas {
197 4808d170 2024-03-30 thomas char *ref, *id;
198 4808d170 2024-03-30 thomas
199 4808d170 2024-03-30 thomas line = strchr(line, ' ');
200 4808d170 2024-03-30 thomas if (line == NULL)
201 4808d170 2024-03-30 thomas errx(1, "invalid branch rm line");
202 4808d170 2024-03-30 thomas line += strspn(line, " ");
203 4808d170 2024-03-30 thomas
204 4808d170 2024-03-30 thomas ref = line;
205 4808d170 2024-03-30 thomas
206 4808d170 2024-03-30 thomas line = strchr(line, ':');
207 4808d170 2024-03-30 thomas if (line == NULL)
208 4808d170 2024-03-30 thomas errx(1, "invalid branch rm line");
209 4808d170 2024-03-30 thomas *line++ = '\0';
210 4808d170 2024-03-30 thomas id = line + strspn(line, " ");
211 4808d170 2024-03-30 thomas
212 4808d170 2024-03-30 thomas fputc('{', fp);
213 4808d170 2024-03-30 thomas json_field(fp, "type", "branch-deleted", 1);
214 4808d170 2024-03-30 thomas json_field(fp, "ref", ref, 1);
215 4808d170 2024-03-30 thomas json_field(fp, "id", id, 0);
216 4808d170 2024-03-30 thomas fputc('}', fp);
217 4808d170 2024-03-30 thomas
218 4808d170 2024-03-30 thomas return 0;
219 4808d170 2024-03-30 thomas }
220 4808d170 2024-03-30 thomas
221 4808d170 2024-03-30 thomas static int
222 20d96a10 2024-03-30 thomas jsonify_commit_short(FILE *fp, char *line)
223 94a3f4e9 2024-03-30 thomas {
224 94a3f4e9 2024-03-30 thomas char *t, *date, *id, *author, *message;
225 94a3f4e9 2024-03-30 thomas
226 20d96a10 2024-03-30 thomas t = line;
227 20d96a10 2024-03-30 thomas date = t;
228 20d96a10 2024-03-30 thomas if ((t = strchr(t, ' ')) == NULL)
229 20d96a10 2024-03-30 thomas errx(1, "malformed line");
230 20d96a10 2024-03-30 thomas *t++ = '\0';
231 94a3f4e9 2024-03-30 thomas
232 20d96a10 2024-03-30 thomas id = t;
233 20d96a10 2024-03-30 thomas if ((t = strchr(t, ' ')) == NULL)
234 20d96a10 2024-03-30 thomas errx(1, "malformed line");
235 20d96a10 2024-03-30 thomas *t++ = '\0';
236 94a3f4e9 2024-03-30 thomas
237 20d96a10 2024-03-30 thomas author = t;
238 20d96a10 2024-03-30 thomas if ((t = strchr(t, ' ')) == NULL)
239 20d96a10 2024-03-30 thomas errx(1, "malformed line");
240 20d96a10 2024-03-30 thomas *t++ = '\0';
241 94a3f4e9 2024-03-30 thomas
242 20d96a10 2024-03-30 thomas message = t;
243 94a3f4e9 2024-03-30 thomas
244 71b7e0f5 2024-03-30 thomas fprintf(fp, "{\"type\":\"commit\",\"short\":true,");
245 20d96a10 2024-03-30 thomas json_field(fp, "id", id, 1);
246 20d96a10 2024-03-30 thomas json_author(fp, "committer", author, 1);
247 20d96a10 2024-03-30 thomas json_field(fp, "date", date, 1);
248 20d96a10 2024-03-30 thomas json_field(fp, "short_message", message, 0);
249 20d96a10 2024-03-30 thomas fprintf(fp, "}");
250 94a3f4e9 2024-03-30 thomas
251 94a3f4e9 2024-03-30 thomas return 0;
252 94a3f4e9 2024-03-30 thomas }
253 94a3f4e9 2024-03-30 thomas
254 94a3f4e9 2024-03-30 thomas static int
255 20d96a10 2024-03-30 thomas jsonify_commit(FILE *fp, char **line, ssize_t *linesize)
256 94a3f4e9 2024-03-30 thomas {
257 94a3f4e9 2024-03-30 thomas const char *errstr;
258 9c485a11 2024-03-30 thomas char *author = NULL;
259 9e56e3e7 2024-03-30 thomas char *filename, *t;
260 94a3f4e9 2024-03-30 thomas char *l;
261 94a3f4e9 2024-03-30 thomas ssize_t linelen;
262 94a3f4e9 2024-03-30 thomas int parent = 0;
263 94a3f4e9 2024-03-30 thomas int msglen = 0, msgwrote = 0;
264 9e56e3e7 2024-03-30 thomas int n, files = 0;
265 20d96a10 2024-03-30 thomas int done = 0;
266 94a3f4e9 2024-03-30 thomas enum {
267 94a3f4e9 2024-03-30 thomas P_FROM,
268 94a3f4e9 2024-03-30 thomas P_VIA,
269 94a3f4e9 2024-03-30 thomas P_DATE,
270 94a3f4e9 2024-03-30 thomas P_PARENT,
271 94a3f4e9 2024-03-30 thomas P_MSGLEN,
272 94a3f4e9 2024-03-30 thomas P_MSG,
273 94a3f4e9 2024-03-30 thomas P_DST,
274 94a3f4e9 2024-03-30 thomas P_SUM,
275 20d96a10 2024-03-30 thomas } phase = P_FROM;
276 94a3f4e9 2024-03-30 thomas
277 20d96a10 2024-03-30 thomas l = *line;
278 20d96a10 2024-03-30 thomas if (strncmp(l, "commit ", 7) != 0)
279 20d96a10 2024-03-30 thomas errx(1, "%s: unexpected line: %s", __func__, l);
280 20d96a10 2024-03-30 thomas l += 7;
281 9e56e3e7 2024-03-30 thomas
282 71b7e0f5 2024-03-30 thomas fprintf(fp, "{\"type\":\"commit\",\"short\":false,");
283 20d96a10 2024-03-30 thomas json_field(fp, "id", l, 1);
284 94a3f4e9 2024-03-30 thomas
285 20d96a10 2024-03-30 thomas while (!done) {
286 20d96a10 2024-03-30 thomas if ((linelen = getline(line, linesize, stdin)) == -1)
287 20d96a10 2024-03-30 thomas break;
288 94a3f4e9 2024-03-30 thomas
289 20d96a10 2024-03-30 thomas if ((*line)[linelen - 1] == '\n')
290 20d96a10 2024-03-30 thomas (*line)[--linelen] = '\0';
291 94a3f4e9 2024-03-30 thomas
292 20d96a10 2024-03-30 thomas l = *line;
293 20d96a10 2024-03-30 thomas switch (phase) {
294 94a3f4e9 2024-03-30 thomas case P_FROM:
295 94a3f4e9 2024-03-30 thomas if (strncmp(l, "from: ", 6) != 0)
296 94a3f4e9 2024-03-30 thomas errx(1, "unexpected from line");
297 94a3f4e9 2024-03-30 thomas l += 6;
298 9c485a11 2024-03-30 thomas
299 9c485a11 2024-03-30 thomas author = strdup(l);
300 9c485a11 2024-03-30 thomas if (author == NULL)
301 9c485a11 2024-03-30 thomas err(1, "strdup");
302 9c485a11 2024-03-30 thomas
303 9c485a11 2024-03-30 thomas json_author(fp, "author", l, 1);
304 9c485a11 2024-03-30 thomas
305 94a3f4e9 2024-03-30 thomas phase = P_VIA;
306 94a3f4e9 2024-03-30 thomas break;
307 94a3f4e9 2024-03-30 thomas
308 94a3f4e9 2024-03-30 thomas case P_VIA:
309 94a3f4e9 2024-03-30 thomas /* optional */
310 94a3f4e9 2024-03-30 thomas if (!strncmp(l, "via: ", 5)) {
311 94a3f4e9 2024-03-30 thomas l += 5;
312 9c485a11 2024-03-30 thomas json_author(fp, "committer", l, 1);
313 94a3f4e9 2024-03-30 thomas phase = P_DATE;
314 94a3f4e9 2024-03-30 thomas break;
315 94a3f4e9 2024-03-30 thomas }
316 9c485a11 2024-03-30 thomas
317 9c485a11 2024-03-30 thomas if (author == NULL) /* impossible */
318 9c485a11 2024-03-30 thomas err(1, "from not specified");
319 9c485a11 2024-03-30 thomas json_author(fp, "committer", author, 1);
320 9c485a11 2024-03-30 thomas free(author);
321 9c485a11 2024-03-30 thomas author = NULL;
322 9c485a11 2024-03-30 thomas
323 94a3f4e9 2024-03-30 thomas phase = P_DATE;
324 94a3f4e9 2024-03-30 thomas /* fallthrough */
325 94a3f4e9 2024-03-30 thomas
326 94a3f4e9 2024-03-30 thomas case P_DATE:
327 94a3f4e9 2024-03-30 thomas /* optional */
328 94a3f4e9 2024-03-30 thomas if (!strncmp(l, "date: ", 6)) {
329 94a3f4e9 2024-03-30 thomas l += 6;
330 94a3f4e9 2024-03-30 thomas json_field(fp, "date", l, 1);
331 94a3f4e9 2024-03-30 thomas phase = P_PARENT;
332 94a3f4e9 2024-03-30 thomas break;
333 94a3f4e9 2024-03-30 thomas }
334 94a3f4e9 2024-03-30 thomas phase = P_PARENT;
335 94a3f4e9 2024-03-30 thomas /* fallthough */
336 94a3f4e9 2024-03-30 thomas
337 94a3f4e9 2024-03-30 thomas case P_PARENT:
338 94a3f4e9 2024-03-30 thomas /* optional - more than one */
339 94a3f4e9 2024-03-30 thomas if (!strncmp(l, "parent ", 7)) {
340 94a3f4e9 2024-03-30 thomas l += 7;
341 94a3f4e9 2024-03-30 thomas l += strcspn(l, ":");
342 94a3f4e9 2024-03-30 thomas l += strspn(l, " ");
343 94a3f4e9 2024-03-30 thomas
344 94a3f4e9 2024-03-30 thomas if (parent == 0) {
345 94a3f4e9 2024-03-30 thomas parent = 1;
346 94a3f4e9 2024-03-30 thomas fprintf(fp, "\"parents\":[");
347 94a3f4e9 2024-03-30 thomas }
348 94a3f4e9 2024-03-30 thomas
349 94a3f4e9 2024-03-30 thomas fputc('"', fp);
350 94a3f4e9 2024-03-30 thomas escape(fp, l);
351 94a3f4e9 2024-03-30 thomas fputc('"', fp);
352 94a3f4e9 2024-03-30 thomas
353 94a3f4e9 2024-03-30 thomas break;
354 94a3f4e9 2024-03-30 thomas }
355 94a3f4e9 2024-03-30 thomas if (parent != 0) {
356 94a3f4e9 2024-03-30 thomas fprintf(fp, "],");
357 94a3f4e9 2024-03-30 thomas parent = 0;
358 94a3f4e9 2024-03-30 thomas }
359 94a3f4e9 2024-03-30 thomas phase = P_MSGLEN;
360 94a3f4e9 2024-03-30 thomas /* fallthrough */
361 94a3f4e9 2024-03-30 thomas
362 94a3f4e9 2024-03-30 thomas case P_MSGLEN:
363 94a3f4e9 2024-03-30 thomas if (strncmp(l, "messagelen: ", 12) != 0)
364 94a3f4e9 2024-03-30 thomas errx(1, "unexpected messagelen line");
365 94a3f4e9 2024-03-30 thomas l += 12;
366 94a3f4e9 2024-03-30 thomas msglen = strtonum(l, 1, INT_MAX, &errstr);
367 94a3f4e9 2024-03-30 thomas if (errstr)
368 94a3f4e9 2024-03-30 thomas errx(1, "message len is %s: %s", errstr, l);
369 94a3f4e9 2024-03-30 thomas
370 9e56e3e7 2024-03-30 thomas msglen++;
371 9e56e3e7 2024-03-30 thomas
372 94a3f4e9 2024-03-30 thomas phase = P_MSG;
373 94a3f4e9 2024-03-30 thomas break;
374 94a3f4e9 2024-03-30 thomas
375 94a3f4e9 2024-03-30 thomas case P_MSG:
376 94a3f4e9 2024-03-30 thomas /*
377 94a3f4e9 2024-03-30 thomas * The commit message is indented with one extra
378 94a3f4e9 2024-03-30 thomas * space which is not accounted for in messagelen,
379 94a3f4e9 2024-03-30 thomas * but we also strip the trailing \n so that
380 94a3f4e9 2024-03-30 thomas * accounts for it.
381 94a3f4e9 2024-03-30 thomas *
382 94a3f4e9 2024-03-30 thomas * Since we read line-by-line and there is always
383 94a3f4e9 2024-03-30 thomas * a \n added at the end of the message,
384 94a3f4e9 2024-03-30 thomas * tolerate one byte less than advertised.
385 94a3f4e9 2024-03-30 thomas */
386 9e56e3e7 2024-03-30 thomas if (*l != ' ')
387 9e56e3e7 2024-03-30 thomas errx(1, "unexpected line in commit message");
388 94a3f4e9 2024-03-30 thomas
389 9e56e3e7 2024-03-30 thomas l++; /* skip leading space */
390 9e56e3e7 2024-03-30 thomas linelen--;
391 9e56e3e7 2024-03-30 thomas
392 9e56e3e7 2024-03-30 thomas if (msgwrote == 0 && linelen != 0) {
393 9e56e3e7 2024-03-30 thomas json_field(fp, "short_message", l, 1);
394 9e56e3e7 2024-03-30 thomas fprintf(fp, "\"message\":\"");
395 9e56e3e7 2024-03-30 thomas escape(fp, l);
396 9e56e3e7 2024-03-30 thomas escape(fp, "\n");
397 9e56e3e7 2024-03-30 thomas msgwrote += linelen;
398 9e56e3e7 2024-03-30 thomas } else if (msgwrote != 0) {
399 9e56e3e7 2024-03-30 thomas escape(fp, l);
400 9e56e3e7 2024-03-30 thomas escape(fp, "\n");
401 94a3f4e9 2024-03-30 thomas }
402 9e56e3e7 2024-03-30 thomas
403 9c485a11 2024-03-30 thomas msglen -= linelen + 1;
404 94a3f4e9 2024-03-30 thomas if (msglen <= 1) {
405 94a3f4e9 2024-03-30 thomas fprintf(fp, "\",");
406 94a3f4e9 2024-03-30 thomas phase = P_DST;
407 9e56e3e7 2024-03-30 thomas break;
408 94a3f4e9 2024-03-30 thomas }
409 94a3f4e9 2024-03-30 thomas break;
410 94a3f4e9 2024-03-30 thomas
411 94a3f4e9 2024-03-30 thomas case P_DST:
412 9e56e3e7 2024-03-30 thomas if (files == 0 && !strcmp(l, " "))
413 9e56e3e7 2024-03-30 thomas break;
414 9e56e3e7 2024-03-30 thomas
415 9e56e3e7 2024-03-30 thomas if (files == 0)
416 9e56e3e7 2024-03-30 thomas fputs("\"diffstat\":{\"files\":[", fp);
417 9e56e3e7 2024-03-30 thomas
418 20d96a10 2024-03-30 thomas if (*l == '\0') {
419 9e56e3e7 2024-03-30 thomas fputs("],", fp);
420 94a3f4e9 2024-03-30 thomas phase = P_SUM;
421 94a3f4e9 2024-03-30 thomas break;
422 94a3f4e9 2024-03-30 thomas }
423 9e56e3e7 2024-03-30 thomas
424 9e56e3e7 2024-03-30 thomas if (*l != ' ')
425 9e56e3e7 2024-03-30 thomas errx(1, "bad diffstat line");
426 9e56e3e7 2024-03-30 thomas l++;
427 9e56e3e7 2024-03-30 thomas
428 9e56e3e7 2024-03-30 thomas if (files != 0)
429 9e56e3e7 2024-03-30 thomas fputc(',', fp);
430 9e56e3e7 2024-03-30 thomas fputc('{', fp);
431 9e56e3e7 2024-03-30 thomas
432 9e56e3e7 2024-03-30 thomas switch (*l) {
433 9e56e3e7 2024-03-30 thomas case 'A':
434 9e56e3e7 2024-03-30 thomas json_field(fp, "action", "added", 1);
435 9e56e3e7 2024-03-30 thomas break;
436 9e56e3e7 2024-03-30 thomas case 'D':
437 9e56e3e7 2024-03-30 thomas json_field(fp, "action", "deleted", 1);
438 9e56e3e7 2024-03-30 thomas break;
439 9e56e3e7 2024-03-30 thomas case 'M':
440 9e56e3e7 2024-03-30 thomas json_field(fp, "action", "modified", 1);
441 9e56e3e7 2024-03-30 thomas break;
442 9e56e3e7 2024-03-30 thomas case 'm':
443 9e56e3e7 2024-03-30 thomas json_field(fp, "action", "mode changed", 1);
444 9e56e3e7 2024-03-30 thomas break;
445 9e56e3e7 2024-03-30 thomas default:
446 9e56e3e7 2024-03-30 thomas json_field(fp, "action", "unknown", 1);
447 9e56e3e7 2024-03-30 thomas break;
448 9e56e3e7 2024-03-30 thomas }
449 9e56e3e7 2024-03-30 thomas
450 9e56e3e7 2024-03-30 thomas l++;
451 9e56e3e7 2024-03-30 thomas while (*l == ' ')
452 9e56e3e7 2024-03-30 thomas *l++ = '\0';
453 9e56e3e7 2024-03-30 thomas if (*l == '\0')
454 9e56e3e7 2024-03-30 thomas errx(1, "invalid diffstat: no filename");
455 9e56e3e7 2024-03-30 thomas
456 9e56e3e7 2024-03-30 thomas filename = l;
457 9e56e3e7 2024-03-30 thomas l = strrchr(l, '|');
458 9e56e3e7 2024-03-30 thomas if (l == NULL)
459 9e56e3e7 2024-03-30 thomas errx(1, "invalid diffstat: no separator");
460 9e56e3e7 2024-03-30 thomas t = l - 1;
461 9e56e3e7 2024-03-30 thomas while (t > filename && *t == ' ')
462 9e56e3e7 2024-03-30 thomas *t-- = '\0';
463 9e56e3e7 2024-03-30 thomas json_field(fp, "file", filename, 1);
464 9e56e3e7 2024-03-30 thomas
465 9e56e3e7 2024-03-30 thomas l++;
466 9e56e3e7 2024-03-30 thomas while (*l == ' ')
467 9e56e3e7 2024-03-30 thomas l++;
468 9e56e3e7 2024-03-30 thomas
469 9e56e3e7 2024-03-30 thomas t = strchr(l, '+');
470 9e56e3e7 2024-03-30 thomas if (t == NULL)
471 9e56e3e7 2024-03-30 thomas errx(1, "invalid diffstat: no added counter");
472 9e56e3e7 2024-03-30 thomas *t++ = '\0';
473 9e56e3e7 2024-03-30 thomas
474 9e56e3e7 2024-03-30 thomas n = strtonum(l, 0, INT_MAX, &errstr);
475 9e56e3e7 2024-03-30 thomas if (errstr)
476 9e56e3e7 2024-03-30 thomas errx(1, "added counter is %s: %s", errstr, l);
477 9e56e3e7 2024-03-30 thomas fprintf(fp, "\"added\":%d,", n);
478 9e56e3e7 2024-03-30 thomas
479 9e56e3e7 2024-03-30 thomas l = ++t;
480 9e56e3e7 2024-03-30 thomas while (*l == ' ')
481 9e56e3e7 2024-03-30 thomas l++;
482 9e56e3e7 2024-03-30 thomas
483 9e56e3e7 2024-03-30 thomas t = strchr(l, '-');
484 9e56e3e7 2024-03-30 thomas if (t == NULL)
485 9e56e3e7 2024-03-30 thomas errx(1, "invalid diffstat: no del counter");
486 9e56e3e7 2024-03-30 thomas *t = '\0';
487 9e56e3e7 2024-03-30 thomas
488 9e56e3e7 2024-03-30 thomas n = strtonum(l, 0, INT_MAX, &errstr);
489 9e56e3e7 2024-03-30 thomas if (errstr)
490 9e56e3e7 2024-03-30 thomas errx(1, "del counter is %s: %s", errstr, l);
491 9e56e3e7 2024-03-30 thomas fprintf(fp, "\"removed\":%d", n);
492 9e56e3e7 2024-03-30 thomas
493 9e56e3e7 2024-03-30 thomas fputc('}', fp);
494 9e56e3e7 2024-03-30 thomas
495 9e56e3e7 2024-03-30 thomas files++;
496 9e56e3e7 2024-03-30 thomas
497 94a3f4e9 2024-03-30 thomas break;
498 94a3f4e9 2024-03-30 thomas
499 94a3f4e9 2024-03-30 thomas case P_SUM:
500 9e56e3e7 2024-03-30 thomas fputs("\"total\":{", fp);
501 9e56e3e7 2024-03-30 thomas
502 9e56e3e7 2024-03-30 thomas t = l;
503 9e56e3e7 2024-03-30 thomas l = strchr(l, ' ');
504 9e56e3e7 2024-03-30 thomas if (l == NULL)
505 9e56e3e7 2024-03-30 thomas errx(1, "missing number of additions");
506 9e56e3e7 2024-03-30 thomas *l++ = '\0';
507 9e56e3e7 2024-03-30 thomas
508 9e56e3e7 2024-03-30 thomas n = strtonum(t, 0, INT_MAX, &errstr);
509 9e56e3e7 2024-03-30 thomas if (errstr)
510 9e56e3e7 2024-03-30 thomas errx(1, "add counter is %s: %s", errstr, t);
511 9e56e3e7 2024-03-30 thomas fprintf(fp, "\"added\":%d,", n);
512 9e56e3e7 2024-03-30 thomas
513 9e56e3e7 2024-03-30 thomas l = strchr(l, ',');
514 9e56e3e7 2024-03-30 thomas if (l == NULL)
515 9e56e3e7 2024-03-30 thomas errx(1, "missing number of deletions");
516 9e56e3e7 2024-03-30 thomas l++;
517 9e56e3e7 2024-03-30 thomas while (*l == ' ')
518 9e56e3e7 2024-03-30 thomas l++;
519 9e56e3e7 2024-03-30 thomas
520 9e56e3e7 2024-03-30 thomas t = strchr(l, ' ');
521 9e56e3e7 2024-03-30 thomas if (t == NULL)
522 9e56e3e7 2024-03-30 thomas errx(1, "malformed diffstat sum line");
523 9e56e3e7 2024-03-30 thomas *t = '\0';
524 9e56e3e7 2024-03-30 thomas
525 9e56e3e7 2024-03-30 thomas n = strtonum(l, 0, INT_MAX, &errstr);
526 9e56e3e7 2024-03-30 thomas if (errstr)
527 9e56e3e7 2024-03-30 thomas errx(1, "del counter is %s: %s", errstr, l);
528 9e56e3e7 2024-03-30 thomas fprintf(fp, "\"removed\":%d", n);
529 9e56e3e7 2024-03-30 thomas
530 9e56e3e7 2024-03-30 thomas fputs("}}", fp);
531 20d96a10 2024-03-30 thomas done = 1;
532 94a3f4e9 2024-03-30 thomas break;
533 94a3f4e9 2024-03-30 thomas
534 94a3f4e9 2024-03-30 thomas default:
535 20d96a10 2024-03-30 thomas /* unreachable */
536 20d96a10 2024-03-30 thomas errx(1, "unexpected line: %s", *line);
537 94a3f4e9 2024-03-30 thomas }
538 94a3f4e9 2024-03-30 thomas }
539 94a3f4e9 2024-03-30 thomas if (ferror(stdin))
540 94a3f4e9 2024-03-30 thomas err(1, "getline");
541 20d96a10 2024-03-30 thomas if (!done)
542 94a3f4e9 2024-03-30 thomas errx(1, "unexpected EOF");
543 9e56e3e7 2024-03-30 thomas fputc('}', fp);
544 20d96a10 2024-03-30 thomas
545 20d96a10 2024-03-30 thomas return 0;
546 20d96a10 2024-03-30 thomas }
547 20d96a10 2024-03-30 thomas
548 20d96a10 2024-03-30 thomas static int
549 e0d15dee 2024-03-30 thomas jsonify_tag(FILE *fp, char **line, ssize_t *linesize)
550 e0d15dee 2024-03-30 thomas {
551 e0d15dee 2024-03-30 thomas const char *errstr;
552 e0d15dee 2024-03-30 thomas char *l;
553 e0d15dee 2024-03-30 thomas ssize_t linelen;
554 e0d15dee 2024-03-30 thomas int msglen = 0, msgwrote = 0;
555 e0d15dee 2024-03-30 thomas int done = 0;
556 e0d15dee 2024-03-30 thomas enum {
557 e0d15dee 2024-03-30 thomas P_FROM,
558 e0d15dee 2024-03-30 thomas P_DATE,
559 e0d15dee 2024-03-30 thomas P_OBJECT,
560 e0d15dee 2024-03-30 thomas P_MSGLEN,
561 e0d15dee 2024-03-30 thomas P_MSG,
562 e0d15dee 2024-03-30 thomas } phase = P_FROM;
563 e0d15dee 2024-03-30 thomas
564 e0d15dee 2024-03-30 thomas l = *line;
565 e0d15dee 2024-03-30 thomas if (strncmp(l, "tag ", 4) != 0)
566 e0d15dee 2024-03-30 thomas errx(1, "%s: unexpected line: %s", __func__, l);
567 e0d15dee 2024-03-30 thomas l += 4;
568 e0d15dee 2024-03-30 thomas
569 e0d15dee 2024-03-30 thomas fputc('{', fp);
570 e0d15dee 2024-03-30 thomas json_field(fp, "type", "tag", 1);
571 e0d15dee 2024-03-30 thomas json_field(fp, "tag", l, 1);
572 e0d15dee 2024-03-30 thomas
573 e0d15dee 2024-03-30 thomas while (!done) {
574 e0d15dee 2024-03-30 thomas if ((linelen = getline(line, linesize, stdin)) == -1)
575 e0d15dee 2024-03-30 thomas break;
576 e0d15dee 2024-03-30 thomas
577 e0d15dee 2024-03-30 thomas if ((*line)[linelen - 1] == '\n')
578 e0d15dee 2024-03-30 thomas (*line)[--linelen] = '\0';
579 e0d15dee 2024-03-30 thomas
580 e0d15dee 2024-03-30 thomas l = *line;
581 e0d15dee 2024-03-30 thomas switch (phase) {
582 e0d15dee 2024-03-30 thomas case P_FROM:
583 e0d15dee 2024-03-30 thomas if (strncmp(l, "from: ", 6) != 0)
584 e0d15dee 2024-03-30 thomas errx(1, "unexpected from line");
585 e0d15dee 2024-03-30 thomas l += 6;
586 e0d15dee 2024-03-30 thomas
587 e0d15dee 2024-03-30 thomas json_author(fp, "tagger", l, 1);
588 e0d15dee 2024-03-30 thomas
589 e0d15dee 2024-03-30 thomas phase = P_DATE;
590 e0d15dee 2024-03-30 thomas break;
591 e0d15dee 2024-03-30 thomas
592 e0d15dee 2024-03-30 thomas case P_DATE:
593 e0d15dee 2024-03-30 thomas /* optional */
594 e0d15dee 2024-03-30 thomas if (!strncmp(l, "date: ", 6)) {
595 e0d15dee 2024-03-30 thomas l += 6;
596 e0d15dee 2024-03-30 thomas json_field(fp, "date", l, 1);
597 e0d15dee 2024-03-30 thomas phase = P_OBJECT;
598 e0d15dee 2024-03-30 thomas break;
599 e0d15dee 2024-03-30 thomas }
600 e0d15dee 2024-03-30 thomas phase = P_OBJECT;
601 e0d15dee 2024-03-30 thomas /* fallthough */
602 e0d15dee 2024-03-30 thomas
603 e0d15dee 2024-03-30 thomas case P_OBJECT:
604 e0d15dee 2024-03-30 thomas /* optional */
605 e0d15dee 2024-03-30 thomas if (!strncmp(l, "object: ", 8)) {
606 e0d15dee 2024-03-30 thomas char *type, *id;
607 e0d15dee 2024-03-30 thomas
608 e0d15dee 2024-03-30 thomas l += 8;
609 e0d15dee 2024-03-30 thomas type = l;
610 e0d15dee 2024-03-30 thomas id = strchr(l, ' ');
611 e0d15dee 2024-03-30 thomas if (id == NULL)
612 e0d15dee 2024-03-30 thomas errx(1, "malformed tag object line");
613 e0d15dee 2024-03-30 thomas *id++ = '\0';
614 e0d15dee 2024-03-30 thomas
615 e0d15dee 2024-03-30 thomas fputs("\"object\":{", fp);
616 e0d15dee 2024-03-30 thomas json_field(fp, "type", type, 1);
617 e0d15dee 2024-03-30 thomas json_field(fp, "id", id, 0);
618 e0d15dee 2024-03-30 thomas fputs("},", fp);
619 e0d15dee 2024-03-30 thomas
620 e0d15dee 2024-03-30 thomas phase = P_MSGLEN;
621 e0d15dee 2024-03-30 thomas break;
622 e0d15dee 2024-03-30 thomas }
623 e0d15dee 2024-03-30 thomas phase = P_MSGLEN;
624 e0d15dee 2024-03-30 thomas /* fallthrough */
625 e0d15dee 2024-03-30 thomas
626 e0d15dee 2024-03-30 thomas case P_MSGLEN:
627 e0d15dee 2024-03-30 thomas if (strncmp(l, "messagelen: ", 12) != 0)
628 e0d15dee 2024-03-30 thomas errx(1, "unexpected messagelen line");
629 e0d15dee 2024-03-30 thomas l += 12;
630 e0d15dee 2024-03-30 thomas msglen = strtonum(l, 1, INT_MAX, &errstr);
631 e0d15dee 2024-03-30 thomas if (errstr)
632 e0d15dee 2024-03-30 thomas errx(1, "message len is %s: %s", errstr, l);
633 e0d15dee 2024-03-30 thomas
634 e0d15dee 2024-03-30 thomas msglen++;
635 e0d15dee 2024-03-30 thomas
636 e0d15dee 2024-03-30 thomas phase = P_MSG;
637 e0d15dee 2024-03-30 thomas break;
638 e0d15dee 2024-03-30 thomas
639 e0d15dee 2024-03-30 thomas case P_MSG:
640 9e56e3e7 2024-03-30 thomas if (*l != ' ')
641 9e56e3e7 2024-03-30 thomas errx(1, "unexpected line in tag message");
642 e0d15dee 2024-03-30 thomas
643 9e56e3e7 2024-03-30 thomas l++; /* skip leading space */
644 9e56e3e7 2024-03-30 thomas linelen--;
645 9e56e3e7 2024-03-30 thomas
646 9e56e3e7 2024-03-30 thomas if (msgwrote == 0 && linelen != 0) {
647 9e56e3e7 2024-03-30 thomas fprintf(fp, "\"message\":\"");
648 9e56e3e7 2024-03-30 thomas escape(fp, l);
649 9e56e3e7 2024-03-30 thomas escape(fp, "\n");
650 9e56e3e7 2024-03-30 thomas msgwrote += linelen;
651 9e56e3e7 2024-03-30 thomas } else if (msgwrote != 0) {
652 9e56e3e7 2024-03-30 thomas escape(fp, l);
653 9e56e3e7 2024-03-30 thomas escape(fp, "\n");
654 e0d15dee 2024-03-30 thomas }
655 9e56e3e7 2024-03-30 thomas
656 e0d15dee 2024-03-30 thomas msglen -= linelen + 1;
657 7e9b6c63 2024-03-30 thomas if (msglen <= 0) {
658 e0d15dee 2024-03-30 thomas fprintf(fp, "\"");
659 e0d15dee 2024-03-30 thomas done = 1;
660 e0d15dee 2024-03-30 thomas break;
661 e0d15dee 2024-03-30 thomas }
662 e0d15dee 2024-03-30 thomas break;
663 e0d15dee 2024-03-30 thomas
664 e0d15dee 2024-03-30 thomas default:
665 e0d15dee 2024-03-30 thomas /* unreachable */
666 e0d15dee 2024-03-30 thomas errx(1, "unexpected line: %s", *line);
667 e0d15dee 2024-03-30 thomas }
668 e0d15dee 2024-03-30 thomas }
669 e0d15dee 2024-03-30 thomas if (ferror(stdin))
670 e0d15dee 2024-03-30 thomas err(1, "getline");
671 e0d15dee 2024-03-30 thomas if (!done)
672 e0d15dee 2024-03-30 thomas errx(1, "unexpected EOF");
673 e0d15dee 2024-03-30 thomas fputc('}', fp);
674 e0d15dee 2024-03-30 thomas
675 e0d15dee 2024-03-30 thomas return 0;
676 e0d15dee 2024-03-30 thomas }
677 e0d15dee 2024-03-30 thomas
678 e0d15dee 2024-03-30 thomas static int
679 20d96a10 2024-03-30 thomas jsonify(FILE *fp)
680 20d96a10 2024-03-30 thomas {
681 20d96a10 2024-03-30 thomas char *line = NULL;
682 20d96a10 2024-03-30 thomas size_t linesize = 0;
683 20d96a10 2024-03-30 thomas ssize_t linelen;
684 20d96a10 2024-03-30 thomas int needcomma = 0;
685 20d96a10 2024-03-30 thomas
686 20d96a10 2024-03-30 thomas fprintf(fp, "{\"notifications\":[");
687 20d96a10 2024-03-30 thomas while ((linelen = getline(&line, &linesize, stdin)) != -1) {
688 20d96a10 2024-03-30 thomas if (line[linelen - 1] == '\n')
689 20d96a10 2024-03-30 thomas line[--linelen] = '\0';
690 94a3f4e9 2024-03-30 thomas
691 20d96a10 2024-03-30 thomas if (*line == '\0')
692 20d96a10 2024-03-30 thomas continue;
693 20d96a10 2024-03-30 thomas
694 20d96a10 2024-03-30 thomas if (needcomma)
695 20d96a10 2024-03-30 thomas fputc(',', fp);
696 20d96a10 2024-03-30 thomas needcomma = 1;
697 20d96a10 2024-03-30 thomas
698 4808d170 2024-03-30 thomas if (strncmp(line, "Removed refs/heads/", 19) == 0) {
699 4808d170 2024-03-30 thomas if (jsonify_branch_rm(fp, line) == -1)
700 4808d170 2024-03-30 thomas err(1, "jsonify_branch_rm");
701 4808d170 2024-03-30 thomas continue;
702 4808d170 2024-03-30 thomas }
703 4808d170 2024-03-30 thomas
704 20d96a10 2024-03-30 thomas if (strncmp(line, "commit ", 7) == 0) {
705 20d96a10 2024-03-30 thomas if (jsonify_commit(fp, &line, &linesize) == -1)
706 20d96a10 2024-03-30 thomas err(1, "jsonify_commit");
707 20d96a10 2024-03-30 thomas continue;
708 20d96a10 2024-03-30 thomas }
709 20d96a10 2024-03-30 thomas
710 20d96a10 2024-03-30 thomas if (*line >= '0' && *line <= '9') {
711 20d96a10 2024-03-30 thomas if (jsonify_commit_short(fp, line) == -1)
712 20d96a10 2024-03-30 thomas err(1, "jsonify_commit_short");
713 20d96a10 2024-03-30 thomas continue;
714 20d96a10 2024-03-30 thomas }
715 20d96a10 2024-03-30 thomas
716 e0d15dee 2024-03-30 thomas if (strncmp(line, "tag ", 4) == 0) {
717 e0d15dee 2024-03-30 thomas if (jsonify_tag(fp, &line, &linesize) == -1)
718 e0d15dee 2024-03-30 thomas err(1, "jsonify_tag");
719 e0d15dee 2024-03-30 thomas continue;
720 e0d15dee 2024-03-30 thomas }
721 e0d15dee 2024-03-30 thomas
722 20d96a10 2024-03-30 thomas errx(1, "unexpected line: %s", line);
723 20d96a10 2024-03-30 thomas }
724 20d96a10 2024-03-30 thomas if (ferror(stdin))
725 20d96a10 2024-03-30 thomas err(1, "getline");
726 94a3f4e9 2024-03-30 thomas fprintf(fp, "]}");
727 94a3f4e9 2024-03-30 thomas
728 94a3f4e9 2024-03-30 thomas return 0;
729 94a3f4e9 2024-03-30 thomas }
730 94a3f4e9 2024-03-30 thomas
731 94a3f4e9 2024-03-30 thomas static char *
732 94a3f4e9 2024-03-30 thomas basic_auth(const char *username, const char *password)
733 94a3f4e9 2024-03-30 thomas {
734 94a3f4e9 2024-03-30 thomas char *tmp;
735 94a3f4e9 2024-03-30 thomas int len;
736 94a3f4e9 2024-03-30 thomas
737 94a3f4e9 2024-03-30 thomas len = asprintf(&tmp, "%s:%s", username, password);
738 94a3f4e9 2024-03-30 thomas if (len == -1)
739 94a3f4e9 2024-03-30 thomas err(1, "asprintf");
740 94a3f4e9 2024-03-30 thomas
741 94a3f4e9 2024-03-30 thomas /* XXX base64-ify */
742 94a3f4e9 2024-03-30 thomas return tmp;
743 94a3f4e9 2024-03-30 thomas }
744 94a3f4e9 2024-03-30 thomas
745 94a3f4e9 2024-03-30 thomas static inline int
746 94a3f4e9 2024-03-30 thomas bufio2poll(struct bufio *bio)
747 94a3f4e9 2024-03-30 thomas {
748 94a3f4e9 2024-03-30 thomas int f, ret = 0;
749 94a3f4e9 2024-03-30 thomas
750 94a3f4e9 2024-03-30 thomas f = bufio_ev(bio);
751 94a3f4e9 2024-03-30 thomas if (f & BUFIO_WANT_READ)
752 94a3f4e9 2024-03-30 thomas ret |= POLLIN;
753 94a3f4e9 2024-03-30 thomas if (f & BUFIO_WANT_WRITE)
754 94a3f4e9 2024-03-30 thomas ret |= POLLOUT;
755 94a3f4e9 2024-03-30 thomas return ret;
756 94a3f4e9 2024-03-30 thomas }
757 94a3f4e9 2024-03-30 thomas
758 94a3f4e9 2024-03-30 thomas int
759 94a3f4e9 2024-03-30 thomas main(int argc, char **argv)
760 94a3f4e9 2024-03-30 thomas {
761 94a3f4e9 2024-03-30 thomas FILE *tmpfp;
762 94a3f4e9 2024-03-30 thomas struct bufio bio;
763 94a3f4e9 2024-03-30 thomas struct pollfd pfd;
764 94a3f4e9 2024-03-30 thomas struct timespec timeout;
765 94a3f4e9 2024-03-30 thomas const char *username;
766 94a3f4e9 2024-03-30 thomas const char *password;
767 94a3f4e9 2024-03-30 thomas const char *timeoutstr;
768 94a3f4e9 2024-03-30 thomas const char *errstr;
769 94a3f4e9 2024-03-30 thomas const char *host = NULL, *port = NULL, *path = NULL;
770 94a3f4e9 2024-03-30 thomas char *auth, *line, *spc;
771 94a3f4e9 2024-03-30 thomas size_t len;
772 94a3f4e9 2024-03-30 thomas ssize_t r;
773 94a3f4e9 2024-03-30 thomas off_t paylen;
774 94a3f4e9 2024-03-30 thomas int tls = 0;
775 94a3f4e9 2024-03-30 thomas int response_code = 0, done = 0;
776 94a3f4e9 2024-03-30 thomas int ch, flags, ret, nonstd = 0;
777 94a3f4e9 2024-03-30 thomas
778 94a3f4e9 2024-03-30 thomas #ifndef PROFILE
779 94a3f4e9 2024-03-30 thomas if (pledge("stdio rpath tmppath dns inet", NULL) == -1)
780 94a3f4e9 2024-03-30 thomas err(1, "pledge");
781 94a3f4e9 2024-03-30 thomas #endif
782 94a3f4e9 2024-03-30 thomas
783 94a3f4e9 2024-03-30 thomas while ((ch = getopt(argc, argv, "ch:p:")) != -1) {
784 94a3f4e9 2024-03-30 thomas switch (ch) {
785 94a3f4e9 2024-03-30 thomas case 'c':
786 94a3f4e9 2024-03-30 thomas tls = 1;
787 94a3f4e9 2024-03-30 thomas break;
788 94a3f4e9 2024-03-30 thomas case 'h':
789 94a3f4e9 2024-03-30 thomas host = optarg;
790 94a3f4e9 2024-03-30 thomas break;
791 94a3f4e9 2024-03-30 thomas case 'p':
792 94a3f4e9 2024-03-30 thomas port = optarg;
793 94a3f4e9 2024-03-30 thomas break;
794 94a3f4e9 2024-03-30 thomas default:
795 94a3f4e9 2024-03-30 thomas usage();
796 94a3f4e9 2024-03-30 thomas }
797 94a3f4e9 2024-03-30 thomas }
798 94a3f4e9 2024-03-30 thomas argc -= optind;
799 94a3f4e9 2024-03-30 thomas argv += optind;
800 94a3f4e9 2024-03-30 thomas
801 94a3f4e9 2024-03-30 thomas if (host == NULL || argc != 1)
802 94a3f4e9 2024-03-30 thomas usage();
803 94a3f4e9 2024-03-30 thomas if (tls && port == NULL)
804 94a3f4e9 2024-03-30 thomas port = "443";
805 94a3f4e9 2024-03-30 thomas path = argv[0];
806 94a3f4e9 2024-03-30 thomas
807 94a3f4e9 2024-03-30 thomas username = getenv("GOT_NOTIFY_HTTP_USER");
808 94a3f4e9 2024-03-30 thomas password = getenv("GOT_NOTIFY_HTTP_PASS");
809 94a3f4e9 2024-03-30 thomas if ((username != NULL && password == NULL) ||
810 94a3f4e9 2024-03-30 thomas (username == NULL && password != NULL))
811 94a3f4e9 2024-03-30 thomas errx(1, "username or password are not specified");
812 94a3f4e9 2024-03-30 thomas if (username && *password == '\0')
813 94a3f4e9 2024-03-30 thomas errx(1, "password can't be empty");
814 94a3f4e9 2024-03-30 thomas
815 94a3f4e9 2024-03-30 thomas /* used by the regression test suite */
816 94a3f4e9 2024-03-30 thomas timeoutstr = getenv("GOT_NOTIFY_TIMEOUT");
817 94a3f4e9 2024-03-30 thomas if (timeoutstr) {
818 94a3f4e9 2024-03-30 thomas http_timeout = strtonum(timeoutstr, 0, 600, &errstr);
819 94a3f4e9 2024-03-30 thomas if (errstr != NULL)
820 94a3f4e9 2024-03-30 thomas errx(1, "timeout in seconds is %s: %s",
821 94a3f4e9 2024-03-30 thomas errstr, timeoutstr);
822 94a3f4e9 2024-03-30 thomas }
823 94a3f4e9 2024-03-30 thomas
824 94a3f4e9 2024-03-30 thomas memset(&timeout, 0, sizeof(timeout));
825 94a3f4e9 2024-03-30 thomas timeout.tv_sec = http_timeout;
826 94a3f4e9 2024-03-30 thomas
827 94a3f4e9 2024-03-30 thomas tmpfp = got_opentemp();
828 94a3f4e9 2024-03-30 thomas if (tmpfp == NULL)
829 94a3f4e9 2024-03-30 thomas err(1, "opentemp");
830 94a3f4e9 2024-03-30 thomas
831 20d96a10 2024-03-30 thomas jsonify(tmpfp);
832 94a3f4e9 2024-03-30 thomas
833 94a3f4e9 2024-03-30 thomas paylen = ftello(tmpfp);
834 94a3f4e9 2024-03-30 thomas if (paylen == -1)
835 94a3f4e9 2024-03-30 thomas err(1, "ftello");
836 94a3f4e9 2024-03-30 thomas if (fseeko(tmpfp, 0, SEEK_SET) == -1)
837 94a3f4e9 2024-03-30 thomas err(1, "fseeko");
838 94a3f4e9 2024-03-30 thomas
839 94a3f4e9 2024-03-30 thomas #ifndef PROFILE
840 94a3f4e9 2024-03-30 thomas /* drop tmppath */
841 94a3f4e9 2024-03-30 thomas if (pledge("stdio rpath dns inet", NULL) == -1)
842 94a3f4e9 2024-03-30 thomas err(1, "pledge");
843 94a3f4e9 2024-03-30 thomas #endif
844 94a3f4e9 2024-03-30 thomas
845 94a3f4e9 2024-03-30 thomas memset(&pfd, 0, sizeof(pfd));
846 94a3f4e9 2024-03-30 thomas pfd.fd = dial(host, port);
847 94a3f4e9 2024-03-30 thomas
848 94a3f4e9 2024-03-30 thomas if ((flags = fcntl(pfd.fd, F_GETFL)) == -1)
849 94a3f4e9 2024-03-30 thomas err(1, "fcntl(F_GETFL)");
850 94a3f4e9 2024-03-30 thomas if (fcntl(pfd.fd, F_SETFL, flags | O_NONBLOCK) == -1)
851 94a3f4e9 2024-03-30 thomas err(1, "fcntl(F_SETFL)");
852 94a3f4e9 2024-03-30 thomas
853 94a3f4e9 2024-03-30 thomas if (bufio_init(&bio) == -1)
854 94a3f4e9 2024-03-30 thomas err(1, "bufio_init");
855 94a3f4e9 2024-03-30 thomas bufio_set_fd(&bio, pfd.fd);
856 94a3f4e9 2024-03-30 thomas if (tls && bufio_starttls(&bio, host, 0, NULL, 0, NULL, 0) == -1)
857 94a3f4e9 2024-03-30 thomas err(1, "bufio_starttls");
858 94a3f4e9 2024-03-30 thomas
859 94a3f4e9 2024-03-30 thomas #ifndef PROFILE
860 94a3f4e9 2024-03-30 thomas /* drop rpath dns inet */
861 94a3f4e9 2024-03-30 thomas if (pledge("stdio", NULL) == -1)
862 94a3f4e9 2024-03-30 thomas err(1, "pledge");
863 94a3f4e9 2024-03-30 thomas #endif
864 94a3f4e9 2024-03-30 thomas
865 94a3f4e9 2024-03-30 thomas if ((!tls && strcmp(port, "80") != 0) ||
866 94a3f4e9 2024-03-30 thomas (tls && strcmp(port, "443")) != 0)
867 94a3f4e9 2024-03-30 thomas nonstd = 1;
868 94a3f4e9 2024-03-30 thomas
869 94a3f4e9 2024-03-30 thomas ret = bufio_compose_fmt(&bio,
870 94a3f4e9 2024-03-30 thomas "POST %s HTTP/1.1\r\n"
871 94a3f4e9 2024-03-30 thomas "Host: %s%s%s\r\n"
872 94a3f4e9 2024-03-30 thomas "Content-Type: application/json\r\n"
873 94a3f4e9 2024-03-30 thomas "Content-Length: %lld\r\n"
874 94a3f4e9 2024-03-30 thomas "User-Agent: %s\r\n"
875 94a3f4e9 2024-03-30 thomas "Connection: close\r\n",
876 94a3f4e9 2024-03-30 thomas path, host,
877 94a3f4e9 2024-03-30 thomas nonstd ? ":" : "", nonstd ? port : "",
878 94a3f4e9 2024-03-30 thomas (long long)paylen, USERAGENT);
879 94a3f4e9 2024-03-30 thomas if (ret == -1)
880 94a3f4e9 2024-03-30 thomas err(1, "bufio_compose_fmt");
881 94a3f4e9 2024-03-30 thomas
882 94a3f4e9 2024-03-30 thomas if (username) {
883 94a3f4e9 2024-03-30 thomas auth = basic_auth(username, password);
884 94a3f4e9 2024-03-30 thomas ret = bufio_compose_fmt(&bio, "Authorization: basic %s\r\n",
885 94a3f4e9 2024-03-30 thomas auth);
886 94a3f4e9 2024-03-30 thomas if (ret == -1)
887 94a3f4e9 2024-03-30 thomas err(1, "bufio_compose_fmt");
888 94a3f4e9 2024-03-30 thomas free(auth);
889 94a3f4e9 2024-03-30 thomas }
890 94a3f4e9 2024-03-30 thomas
891 94a3f4e9 2024-03-30 thomas if (bufio_compose(&bio, "\r\n", 2) == -1)
892 94a3f4e9 2024-03-30 thomas err(1, "bufio_compose");
893 94a3f4e9 2024-03-30 thomas
894 94a3f4e9 2024-03-30 thomas while (!done) {
895 94a3f4e9 2024-03-30 thomas struct timespec elapsed, start, stop;
896 94a3f4e9 2024-03-30 thomas char buf[BUFSIZ];
897 94a3f4e9 2024-03-30 thomas
898 94a3f4e9 2024-03-30 thomas pfd.events = bufio2poll(&bio);
899 94a3f4e9 2024-03-30 thomas clock_gettime(CLOCK_MONOTONIC, &start);
900 94a3f4e9 2024-03-30 thomas ret = ppoll(&pfd, 1, &timeout, NULL);
901 94a3f4e9 2024-03-30 thomas if (ret == -1)
902 94a3f4e9 2024-03-30 thomas err(1, "poll");
903 94a3f4e9 2024-03-30 thomas clock_gettime(CLOCK_MONOTONIC, &stop);
904 94a3f4e9 2024-03-30 thomas timespecsub(&stop, &start, &elapsed);
905 94a3f4e9 2024-03-30 thomas timespecsub(&timeout, &elapsed, &timeout);
906 94a3f4e9 2024-03-30 thomas if (ret == 0 || timeout.tv_sec <= 0)
907 94a3f4e9 2024-03-30 thomas errx(1, "timeout");
908 94a3f4e9 2024-03-30 thomas
909 94a3f4e9 2024-03-30 thomas if (bio.wbuf.len > 0 && (pfd.revents & POLLOUT)) {
910 94a3f4e9 2024-03-30 thomas if (bufio_write(&bio) == -1 && errno != EAGAIN)
911 94a3f4e9 2024-03-30 thomas errx(1, "bufio_write: %s", bufio_io_err(&bio));
912 94a3f4e9 2024-03-30 thomas }
913 94a3f4e9 2024-03-30 thomas if (pfd.revents & POLLIN) {
914 94a3f4e9 2024-03-30 thomas r = bufio_read(&bio);
915 94a3f4e9 2024-03-30 thomas if (r == -1 && errno != EAGAIN)
916 94a3f4e9 2024-03-30 thomas errx(1, "bufio_read: %s", bufio_io_err(&bio));
917 94a3f4e9 2024-03-30 thomas if (r == 0)
918 94a3f4e9 2024-03-30 thomas errx(1, "unexpected EOF");
919 94a3f4e9 2024-03-30 thomas
920 94a3f4e9 2024-03-30 thomas for (;;) {
921 94a3f4e9 2024-03-30 thomas line = buf_getdelim(&bio.rbuf, "\r\n", &len);
922 94a3f4e9 2024-03-30 thomas if (line == NULL)
923 94a3f4e9 2024-03-30 thomas break;
924 94a3f4e9 2024-03-30 thomas if (response_code && *line == '\0') {
925 94a3f4e9 2024-03-30 thomas /*
926 94a3f4e9 2024-03-30 thomas * end of headers, don't bother
927 94a3f4e9 2024-03-30 thomas * reading the body, if there is.
928 94a3f4e9 2024-03-30 thomas */
929 94a3f4e9 2024-03-30 thomas done = 1;
930 94a3f4e9 2024-03-30 thomas break;
931 94a3f4e9 2024-03-30 thomas }
932 94a3f4e9 2024-03-30 thomas if (response_code) {
933 94a3f4e9 2024-03-30 thomas buf_drain(&bio.rbuf, len);
934 94a3f4e9 2024-03-30 thomas continue;
935 94a3f4e9 2024-03-30 thomas }
936 94a3f4e9 2024-03-30 thomas spc = strchr(line, ' ');
937 94a3f4e9 2024-03-30 thomas if (spc == NULL)
938 94a3f4e9 2024-03-30 thomas errx(1, "bad reply");
939 94a3f4e9 2024-03-30 thomas *spc++ = '\0';
940 94a3f4e9 2024-03-30 thomas if (strcasecmp(line, "HTTP/1.1") != 0)
941 94a3f4e9 2024-03-30 thomas errx(1, "unexpected protocol: %s",
942 94a3f4e9 2024-03-30 thomas line);
943 94a3f4e9 2024-03-30 thomas line = spc;
944 94a3f4e9 2024-03-30 thomas
945 94a3f4e9 2024-03-30 thomas spc = strchr(line, ' ');
946 94a3f4e9 2024-03-30 thomas if (spc == NULL)
947 94a3f4e9 2024-03-30 thomas errx(1, "bad reply");
948 94a3f4e9 2024-03-30 thomas *spc++ = '\0';
949 94a3f4e9 2024-03-30 thomas
950 94a3f4e9 2024-03-30 thomas response_code = strtonum(line, 100, 599,
951 94a3f4e9 2024-03-30 thomas &errstr);
952 94a3f4e9 2024-03-30 thomas if (errstr != NULL)
953 94a3f4e9 2024-03-30 thomas errx(1, "response code is %s: %s",
954 94a3f4e9 2024-03-30 thomas errstr, line);
955 94a3f4e9 2024-03-30 thomas
956 94a3f4e9 2024-03-30 thomas buf_drain(&bio.rbuf, len);
957 94a3f4e9 2024-03-30 thomas }
958 94a3f4e9 2024-03-30 thomas if (done)
959 94a3f4e9 2024-03-30 thomas break;
960 94a3f4e9 2024-03-30 thomas }
961 94a3f4e9 2024-03-30 thomas
962 94a3f4e9 2024-03-30 thomas if (!feof(tmpfp) && bio.wbuf.len < sizeof(buf)) {
963 94a3f4e9 2024-03-30 thomas len = fread(buf, 1, sizeof(buf), tmpfp);
964 94a3f4e9 2024-03-30 thomas if (len == 0) {
965 94a3f4e9 2024-03-30 thomas if (ferror(tmpfp))
966 94a3f4e9 2024-03-30 thomas err(1, "fread");
967 94a3f4e9 2024-03-30 thomas continue;
968 94a3f4e9 2024-03-30 thomas }
969 94a3f4e9 2024-03-30 thomas
970 94a3f4e9 2024-03-30 thomas if (bufio_compose(&bio, buf, len) == -1)
971 94a3f4e9 2024-03-30 thomas err(1, "buf_compose");
972 94a3f4e9 2024-03-30 thomas }
973 94a3f4e9 2024-03-30 thomas }
974 94a3f4e9 2024-03-30 thomas
975 37038afa 2024-03-30 thomas if (response_code >= 200 && response_code < 300)
976 94a3f4e9 2024-03-30 thomas return 0;
977 94a3f4e9 2024-03-30 thomas errx(1, "request failed with code %d", response_code);
978 94a3f4e9 2024-03-30 thomas }