add multiline input example
[gemrepl.git] / gemscgi.c
bloba8de79e7db449c31840a4e8ef1b83b19010b0e0e
1 /* Copyright 2021, Martin Bays <mbays@sdf.org>
2 * SPDX-License-Identifier: GPL-3.0-or-later */
3 #include <stdbool.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <sys/socket.h>
8 #include <sys/types.h>
9 #include <sys/un.h>
10 #include <unistd.h>
12 #include "gemscgi.h"
14 /* Decode percent-escapes in place */
15 static void urldecode(char *p) {
16 char *w = p;
17 bool escaped = false;
19 while (*p) {
20 if (*p == '%') {
21 escaped = true;
22 ++p;
23 if (sscanf(p, "%2hhx", w) == 1) {
24 ++w;
25 p+=2;
27 } else {
28 if (escaped) *w = *p;
29 ++p;
30 ++w;
33 *w = 0;
36 #define MAX_REQUEST_SZ 4096
37 /* Parse SCGI request with null body from fd s and place gemini-relevant
38 * header values in request_info.
39 * Return false on failure. */
40 static bool parse_SCGI(int s, Request_Info *request_info)
42 char buf[MAX_REQUEST_SZ];
43 int r = 0;
44 while (1) {
45 int r2 = read(s, buf + r, MAX_REQUEST_SZ - r);
46 if (r2 <= 0) {
47 perror("read");
48 return false;
50 r += r2;
52 int sz;
53 if (sscanf(buf, "%u:", &sz) == 1) {
54 char buf2[16];
55 snprintf(buf2, 16, "%u:,", sz);
56 const int full_sz = sz + strlen(buf2);
58 if (full_sz > MAX_REQUEST_SZ) {
59 fprintf(stderr, "Bad request: Oversized, %u\n", sz);
60 return false;
63 if (r == full_sz) {
64 break;
67 if (r > full_sz) {
68 fprintf(stderr, "Bad request: non-empty body.\n");
69 return false;
74 const char* end = &buf[r-1];
76 if (r <= 2) {
77 /* Null request */
78 } else if (buf[r-1] != ',' || buf[r-2] != 0) {
79 fprintf(stderr, "Bad request: improperly terminated header.\n");
80 return false;
81 } else {
82 /* Parse headers */
83 char *p = buf;
84 while (*p != ':') p++;
85 p++;
87 while (p != end) {
88 const char* key = p;
89 while (*p) ++p;
90 ++p;
91 if (p == end) {
92 fprintf(stderr, "Bad request: key with no value.\n");
93 return false;
96 char* val = p;
97 while (*p) ++p;
98 ++p;
100 if (strcmp(key, "QUERY_STRING") == 0) {
101 urldecode(val);
102 request_info->query_string_decoded = val;
103 } else if (strcmp(key, "SCRIPT_PATH") == 0) {
104 request_info->script_path = val;
105 } else if (strcmp(key, "PATH_INFO") == 0) {
106 request_info->path_info = val;
107 } else if (strcmp(key, "SERVER_NAME") == 0) {
108 request_info->server_name = val;
109 } else if (strcmp(key, "SERVER_PORT") == 0) {
110 request_info->server_port = val;
111 } else if (strcmp(key, "REMOTE_ADDR") == 0) {
112 request_info->remote_addr = val;
113 } else if (strcmp(key, "TLS_CLIENT_ISSUER") == 0) {
114 request_info->tls_client_issuer = val;
115 } else if (strcmp(key, "TLS_CLIENT_ISSUER_CN") == 0) {
116 request_info->tls_client_issuer_cn = val;
117 } else if (strcmp(key, "TLS_CLIENT_SUBJECT") == 0) {
118 request_info->tls_client_subject = val;
119 } else if (strcmp(key, "TLS_CLIENT_SUBJECT_CN") == 0) {
120 request_info->tls_client_subject_cn = val;
121 } else if (strcmp(key, "TLS_CLIENT_HASH") == 0) {
122 if (strncmp(val, "SHA256:", 7) == 0) val += 7;
123 request_info->tls_client_hash = val;
128 return true;
131 void runSCGI(const char *socket_path, respond_cb respond, void *respond_object)
133 // Unix socket gubbins based on
134 // https://beej.us/guide/bgipc/html/multi/unixsock.html
136 const int s = socket(AF_UNIX, SOCK_STREAM, 0);
137 if (s == -1) {
138 perror("socket");
139 exit(1);
142 struct sockaddr_un local;
143 local.sun_family = AF_UNIX;
144 strcpy(local.sun_path, socket_path);
145 unlink(local.sun_path);
146 if (bind(s, (struct sockaddr *)&local,
147 strlen(local.sun_path) + sizeof(local.sun_family)) == -1) {
148 perror("bind");
149 exit(1);
152 if (listen(s, 20) == -1) {
153 perror("listen");
154 exit(1);
157 while (1) {
158 const int s2 = accept(s, NULL, NULL);
159 if (s2 == -1) {
160 perror("accept");
161 exit(1);
165 Request_Info request_info = { };
166 if (parse_SCGI(s2, &request_info)) {
167 respond(respond_object, &request_info, s2);