Mercurial > urweb
comparison src/c/request.c @ 853:19fdeef40ada
Factor out common request functionality, in preparation for supporting different protocols
author | Adam Chlipala <adamc@hcoop.net> |
---|---|
date | Tue, 23 Jun 2009 14:05:12 -0400 |
parents | |
children | 158d980889ac |
comparison
equal
deleted
inserted
replaced
852:4d4c62d95b9c | 853:19fdeef40ada |
---|---|
1 #define _GNU_SOURCE | |
2 | |
3 #include <stdio.h> | |
4 #include <string.h> | |
5 #include <stdlib.h> | |
6 #include <sys/types.h> | |
7 #include <sys/socket.h> | |
8 #include <netinet/in.h> | |
9 #include <unistd.h> | |
10 #include <signal.h> | |
11 | |
12 #include <pthread.h> | |
13 | |
14 #include <mhash.h> | |
15 | |
16 #include "urweb.h" | |
17 | |
18 #define MAX_RETRIES 5 | |
19 | |
20 static int try_rollback(uw_context ctx) { | |
21 int r = uw_rollback(ctx); | |
22 | |
23 if (r) { | |
24 printf("Error running SQL ROLLBACK\n"); | |
25 uw_reset(ctx); | |
26 uw_write(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); | |
27 uw_write(ctx, "Content-type: text/plain\r\n\r\n"); | |
28 uw_write(ctx, "Error running SQL ROLLBACK\n"); | |
29 } | |
30 | |
31 return r; | |
32 } | |
33 | |
34 uw_context uw_request_new_context() { | |
35 uw_context ctx = uw_init(); | |
36 int retries_left = MAX_RETRIES; | |
37 | |
38 while (1) { | |
39 failure_kind fk = uw_begin_init(ctx); | |
40 | |
41 if (fk == SUCCESS) { | |
42 printf("Database connection initialized.\n"); | |
43 break; | |
44 } else if (fk == BOUNDED_RETRY) { | |
45 if (retries_left) { | |
46 printf("Initialization error triggers bounded retry: %s\n", uw_error_message(ctx)); | |
47 --retries_left; | |
48 } else { | |
49 printf("Fatal initialization error (out of retries): %s\n", uw_error_message(ctx)); | |
50 uw_free(ctx); | |
51 return NULL; | |
52 } | |
53 } else if (fk == UNLIMITED_RETRY) | |
54 printf("Initialization error triggers unlimited retry: %s\n", uw_error_message(ctx)); | |
55 else if (fk == FATAL) { | |
56 printf("Fatal initialization error: %s\n", uw_error_message(ctx)); | |
57 uw_free(ctx); | |
58 return NULL; | |
59 } else { | |
60 printf("Unknown uw_begin_init return code!\n"); | |
61 uw_free(ctx); | |
62 return NULL; | |
63 } | |
64 } | |
65 | |
66 return ctx; | |
67 } | |
68 | |
69 #define KEYSIZE 16 | |
70 #define PASSSIZE 4 | |
71 | |
72 #define HASH_ALGORITHM MHASH_SHA256 | |
73 #define HASH_BLOCKSIZE 32 | |
74 #define KEYGEN_ALGORITHM KEYGEN_MCRYPT | |
75 | |
76 int uw_hash_blocksize = HASH_BLOCKSIZE; | |
77 | |
78 static int password[PASSSIZE]; | |
79 static unsigned char private_key[KEYSIZE]; | |
80 | |
81 static void init_crypto() { | |
82 KEYGEN kg = {{HASH_ALGORITHM, HASH_ALGORITHM}}; | |
83 int i; | |
84 | |
85 assert(mhash_get_block_size(HASH_ALGORITHM) == HASH_BLOCKSIZE); | |
86 | |
87 for (i = 0; i < PASSSIZE; ++i) | |
88 password[i] = rand(); | |
89 | |
90 if (mhash_keygen_ext(KEYGEN_ALGORITHM, kg, | |
91 private_key, sizeof(private_key), | |
92 (unsigned char*)password, sizeof(password)) < 0) { | |
93 printf("Key generation failed\n"); | |
94 exit(1); | |
95 } | |
96 } | |
97 | |
98 void uw_request_init() { | |
99 uw_context ctx; | |
100 failure_kind fk; | |
101 | |
102 uw_global_init(); | |
103 | |
104 ctx = uw_request_new_context(); | |
105 | |
106 if (!ctx) | |
107 exit(1); | |
108 | |
109 for (fk = uw_initialize(ctx); fk == UNLIMITED_RETRY; fk = uw_initialize(ctx)) { | |
110 printf("Unlimited retry during init: %s\n", uw_error_message(ctx)); | |
111 uw_db_rollback(ctx); | |
112 uw_reset(ctx); | |
113 } | |
114 | |
115 if (fk != SUCCESS) { | |
116 printf("Failed to initialize database! %s\n", uw_error_message(ctx)); | |
117 uw_db_rollback(ctx); | |
118 exit(1); | |
119 } | |
120 | |
121 uw_free(ctx); | |
122 | |
123 init_crypto(); | |
124 } | |
125 | |
126 void uw_sign(const char *in, char *out) { | |
127 MHASH td; | |
128 | |
129 td = mhash_hmac_init(HASH_ALGORITHM, private_key, sizeof(private_key), | |
130 mhash_get_hash_pblock(HASH_ALGORITHM)); | |
131 | |
132 mhash(td, in, strlen(in)); | |
133 if (mhash_hmac_deinit(td, out) < 0) | |
134 printf("Signing failed"); | |
135 } | |
136 | |
137 typedef struct uw_rc { | |
138 size_t path_copy_size; | |
139 char *path_copy; | |
140 } *uw_request_context; | |
141 | |
142 uw_request_context uw_new_request_context(void) { | |
143 uw_request_context r = malloc(sizeof(struct uw_rc)); | |
144 r->path_copy_size = 0; | |
145 r->path_copy = malloc(0); | |
146 return r; | |
147 } | |
148 | |
149 void uw_free_request_context(uw_request_context r) { | |
150 free(r->path_copy); | |
151 free(r); | |
152 } | |
153 | |
154 request_result uw_request(uw_request_context rc, uw_context ctx, char *request, size_t request_len, int sock) { | |
155 int retries_left = MAX_RETRIES; | |
156 char *s; | |
157 failure_kind fk; | |
158 int is_post = 0, do_normal_send = 1; | |
159 char *boundary = NULL; | |
160 size_t boundary_len; | |
161 char *cmd, *path, *headers, *inputs, *after_headers; | |
162 | |
163 if (!(s = strstr(request, "\r\n\r\n"))) { | |
164 fprintf(stderr, "No end of headers found in request\n"); | |
165 return FAILED; | |
166 } | |
167 | |
168 s[2] = 0; | |
169 after_headers = s + 4; | |
170 | |
171 if (!(s = strstr(request, "\r\n"))) { | |
172 fprintf(stderr, "No newline in request\n"); | |
173 return FAILED; | |
174 } | |
175 | |
176 *s = 0; | |
177 headers = s + 2; | |
178 cmd = s = request; | |
179 | |
180 if (!strsep(&s, " ")) { | |
181 fprintf(stderr, "No first space in HTTP command\n"); | |
182 return FAILED; | |
183 } | |
184 | |
185 uw_set_headers(ctx, headers); | |
186 | |
187 if (!strcmp(cmd, "POST")) { | |
188 char *clen_s = uw_Basis_requestHeader(ctx, "Content-length"); | |
189 if (!clen_s) { | |
190 fprintf(stderr, "No Content-length with POST\n"); | |
191 return FAILED; | |
192 } | |
193 int clen = atoi(clen_s); | |
194 if (clen < 0) { | |
195 fprintf(stderr, "Negative Content-length with POST\n"); | |
196 return FAILED; | |
197 } | |
198 | |
199 if (request + request_len - after_headers < clen) { | |
200 fprintf(stderr, "Request doesn't contain all POST data (according to Content-Length)\n"); | |
201 return FAILED; | |
202 } | |
203 | |
204 is_post = 1; | |
205 | |
206 clen_s = uw_Basis_requestHeader(ctx, "Content-type"); | |
207 if (clen_s && !strncasecmp(clen_s, "multipart/form-data", 19)) { | |
208 if (strncasecmp(clen_s + 19, "; boundary=", 11)) { | |
209 fprintf(stderr, "Bad multipart boundary spec"); | |
210 return FAILED; | |
211 } | |
212 | |
213 boundary = clen_s + 28; | |
214 boundary[0] = '-'; | |
215 boundary[1] = '-'; | |
216 boundary_len = strlen(boundary); | |
217 } | |
218 } else if (strcmp(cmd, "GET")) { | |
219 fprintf(stderr, "Not ready for non-GET/POST command: %s\n", cmd); | |
220 return FAILED; | |
221 } | |
222 | |
223 path = s; | |
224 if (!strsep(&s, " ")) { | |
225 fprintf(stderr, "No second space in HTTP command\n"); | |
226 return FAILED; | |
227 } | |
228 | |
229 if (!strcmp(path, "/.msgs")) { | |
230 char *id = uw_Basis_requestHeader(ctx, "UrWeb-Client"); | |
231 char *pass = uw_Basis_requestHeader(ctx, "UrWeb-Pass"); | |
232 | |
233 if (sock < 0) { | |
234 fprintf(stderr, ".msgs requested, but not socket supplied\n"); | |
235 return FAILED; | |
236 } | |
237 | |
238 if (id && pass) { | |
239 unsigned idn = atoi(id); | |
240 uw_client_connect(idn, atoi(pass), sock); | |
241 fprintf(stderr, "Processed request for messages by client %u\n\n", idn); | |
242 return KEEP_OPEN; | |
243 } | |
244 else { | |
245 fprintf(stderr, "Missing fields in .msgs request: %s, %s\n\n", id, pass); | |
246 return FAILED; | |
247 } | |
248 } | |
249 | |
250 if (boundary) { | |
251 char *part = after_headers, *after_sub_headers, *header, *after_header; | |
252 size_t part_len; | |
253 | |
254 part = strstr(part, boundary); | |
255 if (!part) { | |
256 fprintf(stderr, "Missing first multipart boundary\n"); | |
257 return FAILED; | |
258 } | |
259 part += boundary_len; | |
260 | |
261 while (1) { | |
262 char *name = NULL, *filename = NULL, *type = NULL; | |
263 | |
264 if (part[0] == '-' && part[1] == '-') | |
265 break; | |
266 | |
267 if (*part != '\r') { | |
268 fprintf(stderr, "No \\r after multipart boundary\n"); | |
269 return FAILED; | |
270 } | |
271 ++part; | |
272 if (*part != '\n') { | |
273 fprintf(stderr, "No \\n after multipart boundary\n"); | |
274 return FAILED; | |
275 } | |
276 ++part; | |
277 | |
278 if (!(after_sub_headers = strstr(part, "\r\n\r\n"))) { | |
279 fprintf(stderr, "Missing end of headers after multipart boundary\n"); | |
280 return FAILED; | |
281 } | |
282 after_sub_headers[2] = 0; | |
283 after_sub_headers += 4; | |
284 | |
285 for (header = part; after_header = strstr(header, "\r\n"); header = after_header + 2) { | |
286 char *colon, *after_colon; | |
287 | |
288 *after_header = 0; | |
289 if (!(colon = strchr(header, ':'))) { | |
290 fprintf(stderr, "Missing colon in multipart sub-header\n"); | |
291 return FAILED; | |
292 } | |
293 *colon++ = 0; | |
294 if (*colon++ != ' ') { | |
295 fprintf(stderr, "No space after colon in multipart sub-header\n"); | |
296 return FAILED; | |
297 } | |
298 | |
299 if (!strcasecmp(header, "Content-Disposition")) { | |
300 if (strncmp(colon, "form-data; ", 11)) { | |
301 fprintf(stderr, "Multipart data is not \"form-data\"\n"); | |
302 return FAILED; | |
303 } | |
304 | |
305 for (colon += 11; after_colon = strchr(colon, '='); colon = after_colon) { | |
306 char *data; | |
307 after_colon[0] = 0; | |
308 if (after_colon[1] != '"') { | |
309 fprintf(stderr, "Disposition setting is missing initial quote\n"); | |
310 return FAILED; | |
311 } | |
312 data = after_colon+2; | |
313 if (!(after_colon = strchr(data, '"'))) { | |
314 fprintf(stderr, "Disposition setting is missing final quote\n"); | |
315 return FAILED; | |
316 } | |
317 after_colon[0] = 0; | |
318 ++after_colon; | |
319 if (after_colon[0] == ';' && after_colon[1] == ' ') | |
320 after_colon += 2; | |
321 | |
322 if (!strcasecmp(colon, "name")) | |
323 name = data; | |
324 else if (!strcasecmp(colon, "filename")) | |
325 filename = data; | |
326 } | |
327 } else if (!strcasecmp(header, "Content-Type")) { | |
328 type = colon; | |
329 } | |
330 } | |
331 | |
332 part = memmem(after_sub_headers, request + request_len - after_sub_headers, boundary, boundary_len); | |
333 if (!part) { | |
334 fprintf(stderr, "Missing boundary after multipart payload\n"); | |
335 return FAILED; | |
336 } | |
337 part[-2] = 0; | |
338 part_len = part - after_sub_headers - 2; | |
339 part[0] = 0; | |
340 part += boundary_len; | |
341 | |
342 if (filename) { | |
343 uw_Basis_file f = {filename, type, {part_len, after_sub_headers}}; | |
344 | |
345 if (uw_set_file_input(ctx, name, f)) { | |
346 fprintf(stderr, "%s\n", uw_error_message(ctx)); | |
347 return FAILED; | |
348 } | |
349 } else if (uw_set_input(ctx, name, after_sub_headers)) { | |
350 fprintf(stderr, "%s\n", uw_error_message(ctx)); | |
351 return FAILED; | |
352 } | |
353 } | |
354 } | |
355 else { | |
356 if (is_post) | |
357 inputs = after_headers; | |
358 else if (inputs = strchr(path, '?')) | |
359 *inputs++ = 0; | |
360 | |
361 if (inputs) { | |
362 char *name, *value; | |
363 | |
364 while (*inputs) { | |
365 name = inputs; | |
366 if (inputs = strchr(inputs, '&')) | |
367 *inputs++ = 0; | |
368 else | |
369 inputs = strchr(name, 0); | |
370 | |
371 if (value = strchr(name, '=')) { | |
372 *value++ = 0; | |
373 if (uw_set_input(ctx, name, value)) { | |
374 fprintf(stderr, "%s\n", uw_error_message(ctx)); | |
375 return FAILED; | |
376 } | |
377 } | |
378 else if (uw_set_input(ctx, name, "")) { | |
379 fprintf(stderr, "%s\n", uw_error_message(ctx)); | |
380 return FAILED; | |
381 } | |
382 } | |
383 } | |
384 } | |
385 | |
386 printf("Serving URI %s....\n", path); | |
387 | |
388 while (1) { | |
389 size_t path_len = strlen(path); | |
390 | |
391 uw_write_header(ctx, "HTTP/1.1 200 OK\r\n"); | |
392 | |
393 if (path_len + 1 > rc->path_copy_size) { | |
394 rc->path_copy_size = path_len + 1; | |
395 rc->path_copy = realloc(rc->path_copy, rc->path_copy_size); | |
396 } | |
397 strcpy(rc->path_copy, path); | |
398 fk = uw_begin(ctx, rc->path_copy); | |
399 if (fk == SUCCESS || fk == RETURN_BLOB) { | |
400 uw_commit(ctx); | |
401 return SERVED; | |
402 } else if (fk == BOUNDED_RETRY) { | |
403 if (retries_left) { | |
404 printf("Error triggers bounded retry: %s\n", uw_error_message(ctx)); | |
405 --retries_left; | |
406 } | |
407 else { | |
408 printf("Fatal error (out of retries): %s\n", uw_error_message(ctx)); | |
409 | |
410 try_rollback(ctx); | |
411 | |
412 uw_reset_keep_error_message(ctx); | |
413 uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); | |
414 uw_write_header(ctx, "Content-type: text/plain\r\n"); | |
415 uw_write(ctx, "Fatal error (out of retries): "); | |
416 uw_write(ctx, uw_error_message(ctx)); | |
417 uw_write(ctx, "\n"); | |
418 | |
419 return FAILED; | |
420 } | |
421 } else if (fk == UNLIMITED_RETRY) | |
422 printf("Error triggers unlimited retry: %s\n", uw_error_message(ctx)); | |
423 else if (fk == FATAL) { | |
424 printf("Fatal error: %s\n", uw_error_message(ctx)); | |
425 | |
426 try_rollback(ctx); | |
427 | |
428 uw_reset_keep_error_message(ctx); | |
429 uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\r\n"); | |
430 uw_write_header(ctx, "Content-type: text/html\r\n"); | |
431 uw_write(ctx, "<html><head><title>Fatal Error</title></head><body>"); | |
432 uw_write(ctx, "Fatal error: "); | |
433 uw_write(ctx, uw_error_message(ctx)); | |
434 uw_write(ctx, "\n</body></html>"); | |
435 | |
436 return FAILED; | |
437 } else { | |
438 printf("Unknown uw_handle return code!\n"); | |
439 | |
440 try_rollback(ctx); | |
441 | |
442 uw_reset_keep_request(ctx); | |
443 uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); | |
444 uw_write_header(ctx, "Content-type: text/plain\r\n"); | |
445 uw_write(ctx, "Unknown uw_handle return code!\n"); | |
446 | |
447 return FAILED; | |
448 } | |
449 | |
450 if (try_rollback(ctx)) | |
451 return FAILED; | |
452 | |
453 uw_reset_keep_request(ctx); | |
454 } | |
455 } | |
456 | |
457 void *client_pruner(void *data) { | |
458 uw_context ctx = uw_request_new_context(); | |
459 | |
460 if (!ctx) | |
461 exit(1); | |
462 | |
463 while (1) { | |
464 uw_prune_clients(ctx); | |
465 sleep(5); | |
466 } | |
467 } |