implement !restart, !nolink
[gemrepl.git] / gemscgi.c
blobd4ab555f606fae2ed914812df56852f9fd45de8f
1 #include <stdbool.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <sys/socket.h>
6 #include <sys/types.h>
7 #include <sys/un.h>
8 #include <unistd.h>
10 #include "gemscgi.h"
12 /* Decode percent-escapes in place */
13 static void urldecode(char *p) {
14 char *w = p;
15 bool escaped = false;
17 while (*p) {
18 if (*p == '%') {
19 escaped = true;
20 ++p;
21 if (sscanf(p, "%2hhx", w) == 1) {
22 ++w;
23 p+=2;
25 } else {
26 if (escaped) *w = *p;
27 ++p;
28 ++w;
31 *w = 0;
34 #define MAX_REQUEST_SZ 4096
35 static void handle_connection(int s, respond_cb respond, void *respond_object)
37 char buf[MAX_REQUEST_SZ];
38 int r = 0;
39 while (1) {
40 int r2 = read(s, buf + r, MAX_REQUEST_SZ - r);
41 if (r2 <= 0) {
42 perror("read");
43 return;
45 r += r2;
47 int sz;
48 if (sscanf(buf, "%u:", &sz) == 1) {
49 char buf2[16];
50 snprintf(buf2, 16, "%u:,", sz);
51 const int full_sz = sz + strlen(buf2);
53 if (full_sz > MAX_REQUEST_SZ) {
54 fprintf(stderr, "Oversized request %u", sz);
55 return;
58 if (r == full_sz) {
59 break;
64 Request_Info requestInfo = { };
66 if (buf[r-1] == ',' && buf[r-2] == ':') {
67 /* Null request */
68 } else if (buf[r-1] != ',' || buf[r-2] != 0) {
69 fprintf(stderr, "Bad request.");
70 return;
71 } else {
72 /* Parse headers */
73 char *p = buf;
74 while (*p != ':') p++;
75 p++;
77 while (*p != ',') {
78 const char* key = p;
79 while (*p) ++p;
80 ++p;
81 if (*p == ',') {
82 fprintf(stderr, "Bad request.");
83 return;
86 char* val = p;
87 while (*p) ++p;
88 ++p;
90 if (strcmp(key, "QUERY_STRING") == 0) {
91 urldecode(val);
92 requestInfo.query_string_decoded = val;
94 else if (strcmp(key, "SCRIPT_PATH") == 0) requestInfo.script_path = val;
95 else if (strcmp(key, "PATH_INFO") == 0) requestInfo.path_info = val;
96 else if (strcmp(key, "SERVER_NAME") == 0) requestInfo.server_name = val;
97 else if (strcmp(key, "SERVER_PORT") == 0) requestInfo.server_port = val;
98 else if (strcmp(key, "REMOTE_ADDR") == 0) requestInfo.remote_addr = val;
99 else if (strcmp(key, "TLS_CLIENT_HASH") == 0) requestInfo.tls_client_hash = val;
100 else if (strcmp(key, "TLS_CLIENT_ISSUER") == 0) requestInfo.tls_client_issuer = val;
101 else if (strcmp(key, "TLS_CLIENT_ISSUER_CN") == 0) requestInfo.tls_client_issuer_cn = val;
102 else if (strcmp(key, "TLS_CLIENT_SUBJECT") == 0) requestInfo.tls_client_subject = val;
103 else if (strcmp(key, "TLS_CLIENT_SUBJECT_CN") == 0) requestInfo.tls_client_subject_cn = val;
107 respond(respond_object, &requestInfo, s);
110 void runSCGI(const char *socket_path, respond_cb respond, void *respond_object)
112 // Unix socket gubbins based on
113 // https://beej.us/guide/bgipc/html/multi/unixsock.html
115 const int s = socket(AF_UNIX, SOCK_STREAM, 0);
116 if (s == -1) {
117 perror("socket");
118 exit(1);
121 struct sockaddr_un local;
122 local.sun_family = AF_UNIX;
123 strcpy(local.sun_path, socket_path);
124 unlink(local.sun_path);
125 if (bind(s, (struct sockaddr *)&local,
126 strlen(local.sun_path) + sizeof(local.sun_family)) == -1) {
127 perror("bind");
128 exit(1);
131 if (listen(s, 20) == -1) {
132 perror("listen");
133 exit(1);
136 while (1) {
137 const int s2 = accept(s, NULL, NULL);
138 if (s2 == -1) {
139 perror("accept");
140 exit(1);
143 handle_connection(s2, respond, respond_object);
144 close(s2);