remove unnecessary newline conversion
[gemrepl.git] / gemscgi.c
blob4f978ab13f723b1c2fdb14c50112bd22fd1695df
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, respond_cb respond, void *respond_object)
42 Request_Info request_info = { };
43 char buf[MAX_REQUEST_SZ];
44 int r = 0;
45 while (1) {
46 int r2 = read(s, buf + r, MAX_REQUEST_SZ - r);
47 if (r2 <= 0) {
48 perror("read");
49 return false;
51 r += r2;
53 int sz;
54 if (sscanf(buf, "%u:", &sz) == 1) {
55 char buf2[16];
56 snprintf(buf2, 16, "%u:,", sz);
57 const int full_sz = sz + strlen(buf2);
59 if (full_sz > MAX_REQUEST_SZ) {
60 fprintf(stderr, "Bad request: Oversized, %u\n", sz);
61 return false;
64 if (r == full_sz) {
65 break;
68 if (r > full_sz) {
69 fprintf(stderr, "Bad request: non-empty body.\n");
70 return false;
75 const char* end = &buf[r-1];
77 if (r <= 2) {
78 /* Null request */
79 } else if (buf[r-1] != ',' || buf[r-2] != 0) {
80 fprintf(stderr, "Bad request: improperly terminated header.\n");
81 return false;
82 } else {
83 /* Parse headers */
84 char *p = buf;
85 while (*p != ':') p++;
86 p++;
88 while (p != end) {
89 const char* key = p;
90 while (*p) ++p;
91 ++p;
92 if (p == end) {
93 fprintf(stderr, "Bad request: key with no value.\n");
94 return false;
97 char* val = p;
98 while (*p) ++p;
99 ++p;
101 if (strcmp(key, "QUERY_STRING") == 0) {
102 urldecode(val);
103 request_info.query_string_decoded = val;
104 } else if (strcmp(key, "SCRIPT_PATH") == 0) {
105 request_info.script_path = val;
106 } else if (strcmp(key, "PATH_INFO") == 0) {
107 request_info.path_info = val;
108 } else if (strcmp(key, "SERVER_NAME") == 0) {
109 request_info.server_name = val;
110 } else if (strcmp(key, "SERVER_PORT") == 0) {
111 request_info.server_port = val;
112 } else if (strcmp(key, "REMOTE_ADDR") == 0) {
113 request_info.remote_addr = val;
114 } else if (strcmp(key, "TLS_CLIENT_ISSUER") == 0) {
115 request_info.tls_client_issuer = val;
116 } else if (strcmp(key, "TLS_CLIENT_ISSUER_CN") == 0) {
117 request_info.tls_client_issuer_cn = val;
118 } else if (strcmp(key, "TLS_CLIENT_SUBJECT") == 0) {
119 request_info.tls_client_subject = val;
120 } else if (strcmp(key, "TLS_CLIENT_SUBJECT_CN") == 0) {
121 request_info.tls_client_subject_cn = val;
122 } else if (strcmp(key, "TLS_CLIENT_HASH") == 0) {
123 if (strncmp(val, "SHA256:", 7) == 0) val += 7;
124 request_info.tls_client_hash = val;
129 respond(respond_object, &request_info, s);
131 return true;
134 void runSCGI(const char *socket_path, respond_cb respond, void *respond_object)
136 // Unix socket gubbins based on
137 // https://beej.us/guide/bgipc/html/multi/unixsock.html
139 const int s = socket(AF_UNIX, SOCK_STREAM, 0);
140 if (s == -1) {
141 perror("socket");
142 exit(1);
145 struct sockaddr_un local;
146 local.sun_family = AF_UNIX;
147 strcpy(local.sun_path, socket_path);
148 unlink(local.sun_path);
149 if (bind(s, (struct sockaddr *)&local,
150 strlen(local.sun_path) + sizeof(local.sun_family)) == -1) {
151 perror("bind");
152 exit(1);
155 if (listen(s, 20) == -1) {
156 perror("listen");
157 exit(1);
160 while (1) {
161 const int s2 = accept(s, NULL, NULL);
162 if (s2 == -1) {
163 perror("accept");
164 exit(1);
167 parse_SCGI(s2, respond, respond_object);