initial sketch
[gemrepl.git] / gemscgi.c
blob29d6e26210d1ba6776ce3465da26d5553671bd6f
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 void write_response(const void *object, const char* buf, size_t n)
14 int sockfd = *(int *)object;
15 write(sockfd, buf, n);
16 // TODO: error checking; repeat until all written.
19 /* Decode percent-escapes in place */
20 static void urldecode(char *p) {
21 char *w = p;
22 bool escaped = false;
24 while (*p) {
25 if (*p == '%') {
26 escaped = true;
27 ++p;
28 if (sscanf(p, "%2hhx", w) == 1) {
29 ++w;
30 p+=2;
32 } else {
33 if (escaped) *w = *p;
34 ++p;
35 ++w;
38 *w = 0;
41 #define MAX_REQUEST_SZ 4096
42 static void handle_connection(int s, respond_cb respond, void *respond_object)
44 char buf[MAX_REQUEST_SZ];
45 int r = 0;
46 while (1) {
47 int r2 = read(s, buf + r, MAX_REQUEST_SZ - r);
48 if (r2 <= 0) {
49 perror("read");
50 return;
52 r += r2;
54 int sz;
55 if (sscanf(buf, "%u:", &sz) == 1) {
56 char buf2[16];
57 snprintf(buf2, 16, "%u:,", sz);
58 const int full_sz = sz + strlen(buf2);
60 if (full_sz > MAX_REQUEST_SZ) {
61 fprintf(stderr, "Oversized request %u", sz);
62 return;
65 if (r == full_sz) {
66 break;
71 Request_Info requestInfo = { };
73 if (buf[r-1] == ',' && buf[r-2] == ':') {
74 /* Null request */
75 } else if (buf[r-1] != ',' || buf[r-2] != 0) {
76 fprintf(stderr, "Bad request.");
77 return;
78 } else {
79 /* Parse headers */
80 char *p = buf;
81 while (*p != ':') p++;
82 p++;
84 while (*p != ',') {
85 const char* key = p;
86 while (*p) ++p;
87 ++p;
88 if (*p == ',') {
89 fprintf(stderr, "Bad request.");
90 return;
93 char* val = p;
94 while (*p) ++p;
95 ++p;
97 if (strcmp(key, "QUERY_STRING") == 0) {
98 urldecode(val);
99 requestInfo.query_string_decoded = val;
101 else if (strcmp(key, "SCRIPT_PATH") == 0) requestInfo.script_path = val;
102 else if (strcmp(key, "PATH_INFO") == 0) requestInfo.path_info = val;
103 else if (strcmp(key, "SERVER_NAME") == 0) requestInfo.server_name = val;
104 else if (strcmp(key, "SERVER_PORT") == 0) requestInfo.server_port = val;
105 else if (strcmp(key, "REMOTE_ADDR") == 0) requestInfo.remote_addr = val;
106 else if (strcmp(key, "TLS_CLIENT_HASH") == 0) requestInfo.tls_client_hash = val;
107 else if (strcmp(key, "TLS_CLIENT_ISSUER") == 0) requestInfo.tls_client_issuer = val;
108 else if (strcmp(key, "TLS_CLIENT_ISSUER_CN") == 0) requestInfo.tls_client_issuer_cn = val;
109 else if (strcmp(key, "TLS_CLIENT_SUBJECT") == 0) requestInfo.tls_client_subject = val;
110 else if (strcmp(key, "TLS_CLIENT_SUBJECT_CN") == 0) requestInfo.tls_client_subject_cn = val;
114 respond(respond_object, &requestInfo, write_response, &s);
117 void runSCGI(const char *socket_path, respond_cb respond, void *respond_object)
119 // Unix socket gubbins based on
120 // https://beej.us/guide/bgipc/html/multi/unixsock.html
122 const int s = socket(AF_UNIX, SOCK_STREAM, 0);
123 if (s == -1) {
124 perror("socket");
125 exit(1);
128 struct sockaddr_un local;
129 local.sun_family = AF_UNIX;
130 strcpy(local.sun_path, socket_path);
131 unlink(local.sun_path);
132 if (bind(s, (struct sockaddr *)&local,
133 strlen(local.sun_path) + sizeof(local.sun_family)) == -1) {
134 perror("bind");
135 exit(1);
138 if (listen(s, 20) == -1) {
139 perror("listen");
140 exit(1);
143 while (1) {
144 const int s2 = accept(s, NULL, NULL);
145 if (s2 == -1) {
146 perror("accept");
147 exit(1);
150 handle_connection(s2, respond, respond_object);
151 close(s2);