A small http server written in C
git clone git://git.janzachar.dev/shttp.git
0#include "super.h"1#include <string.h>2#include <ctype.h>3#include <stdio.h>456#define RULE(name) int (name)(struct HTTP_request *rq, char **buff)7#define rule(r) (r)(rq, buff)8#define try_rule(r) { \9 int o = rule(r); \10 if (o) return o; \11}12#define if_rule(r) if (rule(r))13#define match(str) (**buff != '\0' && strchr((str), **buff))1415#define CH_RULE(name, chr) \16inline RULE(name) { \17 if (**buff != (chr)) return 1; \18 (*buff)++; \19 return 0; \20}2122#define CHS_RULE(name, str) \23RULE(name) { \24 if (!match(str)) return 1; \25 (*buff)++; \26 return 0; \27}2829// ----------------------------------------------------------------------------30// Primitives31// ----------------------------------------------------------------------------3233CH_RULE(SP, ' ');34CH_RULE(HTAB, '\t');35CH_RULE(CR, '\n');3637RULE(OWS)38{39 while (!rule(SP) || !rule(HTAB)) {}40 return 0;41}42RULE(RWS)43{44 if (rule(SP) && rule(HTAB)) return 1;45 return rule(OWS);46}4748RULE(CRLF)49{50 // Recognise '\n' as '\r\n'51 // rfc9112 Section 2.2-352 if (!rule(CR)) return 0;5354 if (**buff != '\r') return 1;5556 // Replace '\r' with ' '57 // rfc9112 Section 2.2-458 if (*(*buff+1) != '\n') {59 **buff = ' ';60 return 1;61 }6263 (*buff) += 2;64 return 0;65}6667#define ishex(c) (isdigit(c) || ('A' <= (c) && (c) <= 'F') || ('a' <= (c) && (c) <= 'f'))68RULE(pct_encoded)69{70 if (**buff != '%') return 1;71 if (!ishex(*(*buff+1)) || !ishex(*(*buff+2))) return 1;7273 (*buff) += 3;74 return 0;75}7677RULE(pchar)78{79 if (isalnum(**buff) || match("-._~!$&'()*,;=:@")) {80 (*buff)++;81 return 0;82 }83 if (!rule(pct_encoded)) return 0;84 return 1;85}868788// ----------------------------------------------------------------------------89// Request line90// ----------------------------------------------------------------------------9192RULE(method)93{94 if (!strncmp("GET", *buff, 3)) {95 rq->method = "GET";96 (*buff) += 3;97 return 0;98 }99100 if (!strncmp("HEAD", *buff, 4)) {101 rq->method = "HEAD";102 (*buff) += 4;103 return 0;104 }105106 return 501;107}108109110#define to_int(chr) ( \111 '0' <= (chr) && (chr) <= '9' ? (chr)-'0' : \112 'a' <= (chr) && (chr) <= 'z' ? (chr)-'a'+10 : \113 'A' <= (chr) && (chr) <= 'Z' ? (chr)-'A'+10 : -1\114)115int url_decode(char *url)116{117 char *ptr = url;118 for (; *url; ptr++) {119 if (*url != '%') {120 *ptr = *(url++);121 continue;122 }123124 if (*(ptr+1) == '%') {125 *ptr = '%';126 url += 2;127 continue;128 }129130 *ptr = to_int(*(url+1))*16 + to_int(*(url+2));131 if (*ptr == '/' || *ptr == '\0') return 1;132 url += 3;133 }134 *ptr = '\0';135 return 0;136}137138RULE(absolute_path)139{140 if (**buff != '/') return 400;141 rq->path = *buff;142143 while (1) {144 if (**buff != '/') break;145 (*buff)++;146147 while (!rule(pchar)) {}148 }149 if (**buff == '\0') return 414;150151152 if (rule(SP)) return 400;153 *(*buff-1) = '\0';154155 if (url_decode(rq->path)) return 400;156 return 0;157}158159RULE(query)160{161 while (1) {162 if (!rule(pchar)) continue;163 if (**buff == '/' || **buff == '?') {164 (*buff)++;165 continue;166 }167 break;168 }169 return 0;170}171172RULE (origin_form)173{174 try_rule(absolute_path);175 if (**buff == '?')176 try_rule(query);177178 return 0;179}180181RULE(HTTP_version)182{183 if (strncmp("HTTP", *buff, 4))184 return 400;185186 (*buff) += 4;187188 if (**buff != '/') return 400;189 (*buff)++;190191192 if (!isdigit(**buff) || *(*buff+1) != '.' || !isdigit(*(*buff+2)))193 return 400;194195 // Only support HTTP/1.1196 if (strncmp("1.1", *buff, 3))197 return 505;198 (*buff) += 3;199200201 rq->version = "HTTP/1.1";202 return 0;203}204205RULE(request_line)206{207 try_rule(method);208 try_rule(SP);209 try_rule(origin_form);210 try_rule(HTTP_version);211 return 0;212}213214215// ----------------------------------------------------------------------------216// Headers217// ----------------------------------------------------------------------------218219RULE(token)220{221 int ret = 1;222 while (isalnum(**buff) || match("!#$%&'*+-.^_`|~")) {223 (*buff)++;224 ret = 0;225 }226 return ret;227}228229RULE(field_vchar)230{231 if (**buff < 32 || **buff == 127) return 1;232 (*buff)++;233 return 0;234}235236RULE(field_value)237{238 try_rule(field_vchar);239240 // field_value may include SP or HTAB, but should start and end with `field_vchar`241 int space = 0;242 while (1) {243 if (!rule(RWS)) space = 1;244 else if (!rule(field_vchar)) space = 0;245 else break;246 }247248 return space;249}250251252RULE(headers)253{254 // check for whitespaced lines255 // Section 2.2-8256 if (match("\t\v\f \xa0")) return 400;257258 while (rule(CRLF)) {259 char *name = *buff;260 if (rule(token)) return 400;261262 if (**buff != ':') return 400;263 **buff = '\0';264 (*buff)++;265266 rule(OWS);267268 char *value = *buff;269 if (rule(field_value)) return 400;270 char *end = *buff;271272 rule(OWS);273 rule(CRLF);274275 *end = '\0';276277 header_push(&rq->headers, name, value);278 }279 return 0;280}281282283// ----------------------------------------------------------------------------284// Starting point285// ----------------------------------------------------------------------------286RULE(request)287{288 // Ignore empty lines preceeding the request289 // rfc9112 Section 2.2-7290 while (!rule(CRLF)) {}291292293 try_rule(request_line);294 try_rule(CRLF);295296 try_rule(headers);297 return 0;298}299300