2 * Copyright (c) 2024 Mark Jamsek <mark@jamsek.dev>
3 * Copyright (c) 2014 Florian Obser <florian@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <sys/socket.h>
27 #include "got_error.h"
28 #include "got_lib_poll.h"
31 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
34 #define GOTWEBD_TEST_HARNESS "gotwebd_test_harness"
37 * Socket path should be passed on the command line or set as an envvar.
38 * Query string and request method can be passed on the command line;
39 * if not provided, use the index summary page and GET request method.
41 #define GOTWEBD_TEST_QUERYSTRING "action=summary&path=repo.git"
43 #define GOTWEBD_TEST_PATH_INFO "/"GOTWEBD_TEST_HARNESS"/"
44 #define GOTWEBD_TEST_REMOTE_ADDR "::1"
45 #define GOTWEBD_TEST_REMOTE_PORT "32768"
46 #define GOTWEBD_TEST_SERVER_ADDR "::1"
47 #define GOTWEBD_TEST_SERVER_PORT "80"
48 #define GOTWEBD_TEST_SERVER_NAME "gotwebd"
49 #define GOTWEBD_TEST_SCRIPT_NAME GOTWEBD_TEST_HARNESS
50 #define GOTWEBD_TEST_REQUEST_URI "/"GOTWEBD_TEST_HARNESS"/"
51 #define GOTWEBD_TEST_DOCUMENT_URI "/"GOTWEBD_TEST_HARNESS"/"
52 #define GOTWEBD_TEST_DOCUMENT_ROOT "/cgi-bin/"GOTWEBD_TEST_HARNESS
53 #define GOTWEBD_TEST_REQUEST_METHOD "GET"
54 #define GOTWEBD_TEST_SCRIPT_FILENAME "/cgi-bin/"GOTWEBD_TEST_HARNESS
55 #define GOTWEBD_TEST_SERVER_PROTOCOL "HTTP/1.1"
56 #define GOTWEBD_TEST_SERVER_SOFTWARE GOTWEBD_TEST_HARNESS
57 #define GOTWEBD_TEST_GATEWAY_INTERFACE "CGI/1.1"
59 #define PARAM(_p) { #_p, GOTWEBD_TEST_##_p }
61 static const char *mock_params
[][2] = {
72 PARAM(REQUEST_METHOD
),
73 PARAM(SCRIPT_FILENAME
),
74 PARAM(SERVER_PROTOCOL
),
75 PARAM(SERVER_SOFTWARE
),
76 PARAM(GATEWAY_INTERFACE
)
81 #define FCGI_CONTENT_SIZE 65535
82 #define FCGI_PADDING_SIZE 255
83 #define FCGI_RECORD_SIZE \
84 (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE)
86 #define FCGI_BEGIN_REQUEST 1
87 #define FCGI_ABORT_REQUEST 2
88 #define FCGI_END_REQUEST 3
94 #define FCGI_GET_VALUES 9
95 #define FCGI_GET_VALUES_RESULT 10
96 #define FCGI_UNKNOWN_TYPE 11
97 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
99 #define FCGI_RESPONDER 1
101 struct fcgi_record_header
{
105 uint16_t content_len
;
108 }__attribute__((__packed__
));
110 struct fcgi_begin_request_body
{
114 }__attribute__((__packed__
));
116 struct server_fcgi_param
{
118 uint8_t buf
[FCGI_RECORD_SIZE
];
128 enum fcgistate state
;
138 fprintf(stderr
, "usage: %s [-m method] [-q query] [-s socket]\n",
143 static const struct got_error
*
144 fcgi_writechunk(int type
, uint8_t *dat
, size_t datlen
)
146 if (type
== FCGI_END_REQUEST
)
150 if (write(STDOUT_FILENO
, dat
, datlen
) == -1)
151 return got_error_from_errno("write");
152 } else if (fputs("\r\n", stdout
) == EOF
)
153 return got_error_from_errno("fputs");
158 static const struct got_error
*
159 fcgi_read(int fd
, struct fcgi_data
*fcgi
)
161 const struct got_error
*err
;
162 struct fcgi_record_header
*h
;
163 char buf
[FCGI_RECORD_SIZE
];
167 if (fcgi
->toread
> sizeof(buf
)) {
168 /* cannot happen with gotwebd response */
169 return got_error_msg(GOT_ERR_NO_SPACE
,
170 "bad fcgi response size");
173 err
= got_poll_read_full(fd
, &len
, buf
,
174 fcgi
->toread
, fcgi
->toread
);
176 if (err
->code
!= GOT_ERR_EOF
)
183 if (fcgi
->toread
!= 0)
184 return got_error_msg(GOT_ERR_BAD_PACKET
,
185 "short fcgi response");
187 switch (fcgi
->state
) {
188 case FCGI_READ_HEADER
:
189 h
= (struct fcgi_record_header
*)buf
;
190 fcgi
->type
= h
->type
;
191 fcgi
->state
= FCGI_READ_CONTENT
;
192 fcgi
->padding_len
= h
->padding_len
;
193 fcgi
->toread
= ntohs(h
->content_len
);
195 if (fcgi
->toread
!= 0)
198 /* fallthrough if content_len == 0 */
199 case FCGI_READ_CONTENT
:
200 switch (fcgi
->type
) {
201 case FCGI_STDERR
: /* gotwebd doesn't send STDERR */
203 case FCGI_END_REQUEST
:
204 err
= fcgi_writechunk(fcgi
->type
, buf
, len
);
209 if (fcgi
->padding_len
== 0) {
210 fcgi
->state
= FCGI_READ_HEADER
;
211 fcgi
->toread
= sizeof(*h
);
213 fcgi
->state
= FCGI_READ_PADDING
;
214 fcgi
->toread
= fcgi
->padding_len
;
217 case FCGI_READ_PADDING
:
218 fcgi
->state
= FCGI_READ_HEADER
;
219 fcgi
->toread
= sizeof(*h
);
222 /* should not happen with gotwebd */
223 return got_error_msg(GOT_ERR_RANGE
, "bad fcgi state");
230 static const struct got_error
*
231 fcgi_add_stdin(int fd
)
233 struct fcgi_record_header h
;
235 memset(&h
, 0, sizeof(h
));
242 return got_poll_write_full(fd
, &h
, sizeof(h
));
245 static const struct got_error
*
246 fcgi_add_param(int fd
, struct server_fcgi_param
*p
,
247 const char *key
, const char *val
)
249 struct fcgi_record_header
*h
;
250 int len
, key_len
, val_len
;
253 key_len
= strlen(key
);
254 val_len
= strlen(val
);
255 len
= key_len
+ val_len
;
256 len
+= key_len
> 127 ? 4 : 1;
257 len
+= val_len
> 127 ? 4 : 1;
259 if (len
> FCGI_CONTENT_SIZE
)
260 return got_error_msg(GOT_ERR_RANGE
, "parameter too large");
262 if (p
->total_len
+ len
> FCGI_CONTENT_SIZE
) {
263 const struct got_error
*err
;
265 err
= got_poll_write_full(fd
, p
->buf
,
266 sizeof(*h
) + p
->total_len
);
272 h
= (struct fcgi_record_header
*)p
->buf
;
273 param
= p
->buf
+ sizeof(*h
) + p
->total_len
;
276 *param
++ = ((key_len
>> 24) & 0xff) | 0x80;
277 *param
++ = ((key_len
>> 16) & 0xff);
278 *param
++ = ((key_len
>> 8) & 0xff);
279 *param
++ = (key_len
& 0xff);
284 *param
++ = ((val_len
>> 24) & 0xff) | 0x80;
285 *param
++ = ((val_len
>> 16) & 0xff);
286 *param
++ = ((val_len
>> 8) & 0xff);
287 *param
++ = (val_len
& 0xff);
291 memcpy(param
, key
, key_len
);
293 memcpy(param
, val
, val_len
);
297 h
->content_len
= htons(p
->total_len
);
301 static const struct got_error
*
302 fcgi_send_params(int fd
, struct server_fcgi_param
*param
,
303 const char *meth
, const char *qs
)
305 const struct got_error
*err
;
306 struct fcgi_record_header
*h
;
310 h
= (struct fcgi_record_header
*)¶m
->buf
;
311 h
->type
= FCGI_PARAMS
;
314 for (i
= 0; i
< nitems(mock_params
); ++i
) {
315 k
= mock_params
[i
][0];
316 v
= mock_params
[i
][1];
317 if ((err
= fcgi_add_param(fd
, param
, k
, v
)) != NULL
)
321 qs
= GOTWEBD_TEST_QUERYSTRING
;
322 if ((err
= fcgi_add_param(fd
, param
, "QUERY_STRING", qs
)) != NULL
)
325 meth
= GOTWEBD_TEST_REQUEST_METHOD
;
326 if ((err
= fcgi_add_param(fd
, param
, "REQUEST_METHOD", meth
)) != NULL
)
329 err
= got_poll_write_full(fd
, param
->buf
,
330 sizeof(*h
) + ntohs(h
->content_len
));
334 /* send "no more params" message */
336 return got_poll_write_full(fd
, param
->buf
, sizeof(*h
));
339 static const struct got_error
*
340 fcgi(const char *sock
, const char *meth
, const char *qs
)
342 const struct got_error
*err
;
343 struct server_fcgi_param param
;
344 struct fcgi_record_header
*h
;
345 struct fcgi_begin_request_body
*begin
;
346 struct fcgi_data fcgi
;
347 struct sockaddr_un sun
;
350 if ((fd
= socket(AF_UNIX
, SOCK_STREAM
| SOCK_NONBLOCK
, 0)) == -1)
351 return got_error_from_errno("socket");
353 memset(&sun
, 0, sizeof(sun
));
354 sun
.sun_family
= AF_UNIX
;
356 if (strlcpy(sun
.sun_path
, sock
, sizeof(sun
.sun_path
))
357 >= sizeof(sun
.sun_path
)) {
358 err
= got_error_fmt(GOT_ERR_NO_SPACE
,
359 "socket path too long: %s", sock
);
363 if ((connect(fd
, (struct sockaddr
*)&sun
, sizeof(sun
))) == -1) {
364 err
= got_error_from_errno_fmt("connect: %s", sock
);
368 if (pledge("stdio", NULL
) == -1) {
369 err
= got_error_from_errno("pledge");
373 memset(&fcgi
, 0, sizeof(fcgi
));
375 fcgi
.state
= FCGI_READ_HEADER
;
376 fcgi
.toread
= sizeof(*h
);
379 memset(¶m
, 0, sizeof(param
));
381 h
= (struct fcgi_record_header
*)¶m
.buf
;
383 h
->type
= FCGI_BEGIN_REQUEST
;
385 h
->content_len
= htons(sizeof(*begin
));
388 begin
= (struct fcgi_begin_request_body
*)¶m
.buf
[sizeof(*h
)];
389 begin
->role
= htons(FCGI_RESPONDER
);
391 err
= got_poll_write_full(fd
, param
.buf
, sizeof(*h
) + sizeof(*begin
));
395 if ((err
= fcgi_send_params(fd
, ¶m
, meth
, qs
)) != NULL
)
398 if ((err
= fcgi_add_stdin(fd
)) != NULL
)
401 err
= fcgi_read(fd
, &fcgi
);
404 if (fd
!= -1 && close(fd
) == EOF
&& err
== NULL
)
405 err
= got_error_from_errno("close");
410 main(int argc
, char *argv
[])
412 const struct got_error
*error
;
413 const char *meth
= NULL
, *qs
= NULL
, *sock
= NULL
;
416 while ((ch
= getopt(argc
, argv
, "m:q:s:")) != -1) {
440 sock
= getenv("GOTWEBD_TEST_SOCK");
442 errx(1, "socket path not provided");
445 if (unveil(sock
, "rw") != 0)
447 if (pledge("stdio unix", NULL
) == -1)
450 error
= fcgi(sock
, meth
, qs
);
452 errx(1, "%s", error
->msg
);