Blame


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