PageRenderTime 40ms CodeModel.GetById 16ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/http.c

http://github.com/nicolasff/webdis
C | 330 lines | 214 code | 75 blank | 41 comment | 34 complexity | 87cae5e94adf28f24042b797bd778352 MD5 | raw file
  1#include "http.h"
  2#include "server.h"
  3#include "worker.h"
  4#include "client.h"
  5
  6#include <string.h>
  7#include <stdlib.h>
  8#include <unistd.h>
  9#include <stdio.h>
 10
 11/* HTTP Response */
 12
 13struct http_response *
 14http_response_init(struct worker *w, int code, const char *msg) {
 15
 16	/* create object */
 17	struct http_response *r = calloc(1, sizeof(struct http_response));
 18
 19	r->code = code;
 20	r->msg = msg;
 21	r->w = w;
 22	r->keep_alive = 0; /* default */
 23
 24	http_response_set_header(r, "Server", "Webdis");
 25
 26	/* Cross-Origin Resource Sharing, CORS. */
 27	http_response_set_header(r, "Allow", "GET,POST,PUT,OPTIONS");
 28	/*
 29	Chrome doesn't support Allow and requires 
 30	Access-Control-Allow-Methods
 31	*/
 32	http_response_set_header(r, "Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS");
 33	http_response_set_header(r, "Access-Control-Allow-Origin", "*");
 34	/* 
 35	According to 
 36	http://www.w3.org/TR/cors/#access-control-allow-headers-response-header
 37	Access-Control-Allow-Headers cannot be a wildcard and must be set
 38	with explicit names
 39	*/
 40	http_response_set_header(r, "Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization");
 41
 42	return r;
 43}
 44
 45
 46void
 47http_response_set_header(struct http_response *r, const char *k, const char *v) {
 48
 49	int i, pos = r->header_count;
 50	size_t key_sz = strlen(k);
 51	size_t val_sz = strlen(v);
 52
 53	for(i = 0; i < r->header_count; ++i) {
 54		if(strncmp(r->headers[i].key, k, key_sz) == 0) {
 55			pos = i;
 56			/* free old value before replacing it. */
 57			free(r->headers[i].key);
 58			free(r->headers[i].val);
 59			break;
 60		}
 61	}
 62
 63	/* extend array */
 64	if(pos == r->header_count) {
 65		r->headers = realloc(r->headers,
 66				sizeof(struct http_header)*(r->header_count + 1));
 67		r->header_count++;
 68	}
 69
 70	/* copy key */
 71	r->headers[pos].key = calloc(key_sz + 1, 1);
 72	memcpy(r->headers[pos].key, k, key_sz);
 73	r->headers[pos].key_sz = key_sz;
 74
 75	/* copy val */
 76	r->headers[pos].val = calloc(val_sz + 1, 1);
 77	memcpy(r->headers[pos].val, v, val_sz);
 78	r->headers[pos].val_sz = val_sz;
 79
 80	if(!r->chunked && !strcmp(k, "Transfer-Encoding") && !strcmp(v, "chunked")) {
 81		r->chunked = 1;
 82	}
 83}
 84
 85void
 86http_response_set_body(struct http_response *r, const char *body, size_t body_len) {
 87
 88	r->body = body;
 89	r->body_len = body_len;
 90}
 91
 92static void
 93http_response_cleanup(struct http_response *r, int fd, int success) {
 94
 95	int i;
 96
 97	/* cleanup buffer */
 98	free(r->out);
 99	if(!r->keep_alive || !success) {
100		/* Close fd is client doesn't support Keep-Alive. */
101		close(fd);
102	}
103
104	/* cleanup response object */
105	for(i = 0; i < r->header_count; ++i) {
106		free(r->headers[i].key);
107		free(r->headers[i].val);
108	}
109	free(r->headers);
110
111	free(r);
112}
113
114static void
115http_can_write(int fd, short event, void *p) {
116
117	int ret;
118	struct http_response *r = p;
119
120	(void)event;
121	
122	ret = write(fd, r->out + r->sent, r->out_sz - r->sent);
123	
124	if(ret > 0)
125		r->sent += ret;
126
127	if(ret <= 0 || r->out_sz - r->sent == 0) { /* error or done */
128		http_response_cleanup(r, fd, (int)r->out_sz == r->sent ? 1 : 0);
129	} else { /* reschedule write */
130		http_schedule_write(fd, r);
131	}
132}
133
134void
135http_schedule_write(int fd, struct http_response *r) {
136
137	if(r->w) { /* async */
138		event_set(&r->ev, fd, EV_WRITE, http_can_write, r);
139		event_base_set(r->w->base, &r->ev);
140		event_add(&r->ev, NULL);
141	} else { /* blocking */
142		http_can_write(fd, 0, r);
143	}
144
145}
146
147static char *
148format_chunk(const char *p, size_t sz, size_t *out_sz) {
149
150	char *out, tmp[64];
151	int chunk_size;
152
153	/* calculate format size */
154	chunk_size = sprintf(tmp, "%x\r\n", (int)sz);
155	
156	*out_sz = chunk_size + sz + 2;
157	out = malloc(*out_sz);
158	memcpy(out, tmp, chunk_size);
159	memcpy(out + chunk_size, p, sz);
160	memcpy(out + chunk_size + sz, "\r\n", 2);
161
162	return out;
163}
164
165void
166http_response_write(struct http_response *r, int fd) {
167
168	char *p;
169	int i, ret;
170
171	/*r->keep_alive = 0;*/
172	r->out_sz = sizeof("HTTP/1.x xxx ")-1 + strlen(r->msg) + 2;
173	r->out = calloc(r->out_sz + 1, 1);
174
175	ret = sprintf(r->out, "HTTP/1.%d %d %s\r\n", (r->http_version?1:0), r->code, r->msg);
176	(void)ret;
177	p = r->out;
178
179	if(!r->chunked) {
180		if(r->code == 200 && r->body) {
181			char content_length[10];
182			sprintf(content_length, "%zd", r->body_len);
183			http_response_set_header(r, "Content-Length", content_length);
184		} else {
185			http_response_set_header(r, "Content-Length", "0");
186		}
187	}
188
189	for(i = 0; i < r->header_count; ++i) {
190		/* "Key: Value\r\n" */
191		size_t header_sz = r->headers[i].key_sz + 2 + r->headers[i].val_sz + 2;
192		r->out = realloc(r->out, r->out_sz + header_sz);
193		p = r->out + r->out_sz;
194
195		/* add key */
196		memcpy(p, r->headers[i].key, r->headers[i].key_sz);
197		p += r->headers[i].key_sz;
198
199		/* add ": " */
200		*(p++) = ':';
201		*(p++) = ' ';
202
203		/* add value */
204		memcpy(p, r->headers[i].val, r->headers[i].val_sz);
205		p += r->headers[i].val_sz;
206
207		/* add "\r\n" */
208		*(p++) = '\r';
209		*(p++) = '\n';
210
211		r->out_sz += header_sz;
212
213		if(strncasecmp("Connection", r->headers[i].key, r->headers[i].key_sz) == 0 &&
214			strncasecmp("Keep-Alive", r->headers[i].val, r->headers[i].val_sz) == 0) {
215			r->keep_alive = 1;
216		}
217	}
218
219	/* end of headers */
220	r->out = realloc(r->out, r->out_sz + 2);
221	memcpy(r->out + r->out_sz, "\r\n", 2);
222	r->out_sz += 2;
223
224	/* append body if there is one. */
225	if(r->body && r->body_len) {
226
227		char *tmp = (char*)r->body;
228		size_t tmp_len = r->body_len;
229		if(r->chunked) { /* replace body with formatted chunk */
230			tmp = format_chunk(r->body, r->body_len, &tmp_len);
231		}
232
233		r->out = realloc(r->out, r->out_sz + tmp_len);
234		memcpy(r->out + r->out_sz, tmp, tmp_len);
235		r->out_sz += tmp_len;
236
237		if(r->chunked) { /* need to free the chunk */
238			free(tmp);
239		}
240	}
241
242	/* send buffer to client */
243	r->sent = 0;
244	http_schedule_write(fd, r);
245}
246
247static void
248http_response_set_connection_header(struct http_client *c, struct http_response *r) {
249	http_response_set_keep_alive(r, c->keep_alive);
250}
251
252
253
254/* Adobe flash cross-domain request */
255void
256http_crossdomain(struct http_client *c) {
257
258	struct http_response *resp = http_response_init(NULL, 200, "OK");
259	char out[] = "<?xml version=\"1.0\"?>\n"
260"<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
261"<cross-domain-policy>\n"
262  "<allow-access-from domain=\"*\" />\n"
263"</cross-domain-policy>\n";
264
265	resp->http_version = c->http_version;
266	http_response_set_connection_header(c, resp);
267	http_response_set_header(resp, "Content-Type", "application/xml");
268	http_response_set_body(resp, out, sizeof(out)-1);
269
270	http_response_write(resp, c->fd);
271	http_client_reset(c);
272}
273
274/* Simple error response */
275void
276http_send_error(struct http_client *c, short code, const char *msg) {
277
278	struct http_response *resp = http_response_init(NULL, code, msg);
279	resp->http_version = c->http_version;
280	http_response_set_connection_header(c, resp);
281	http_response_set_body(resp, NULL, 0);
282
283	http_response_write(resp, c->fd);
284	http_client_reset(c);
285}
286
287/**
288 * Set Connection field, either Keep-Alive or Close.
289 */
290void
291http_response_set_keep_alive(struct http_response *r, int enabled) {
292	r->keep_alive = enabled;
293	if(enabled) {
294		http_response_set_header(r, "Connection", "Keep-Alive");
295	} else {
296		http_response_set_header(r, "Connection", "Close");
297	}
298}
299
300/* Response to HTTP OPTIONS */
301void
302http_send_options(struct http_client *c) {
303
304	struct http_response *resp = http_response_init(NULL, 200, "OK");
305	resp->http_version = c->http_version;
306	http_response_set_connection_header(c, resp);
307
308	http_response_set_header(resp, "Content-Type", "text/html");
309	http_response_set_header(resp, "Content-Length", "0");
310
311	http_response_write(resp, c->fd);
312	http_client_reset(c);
313}
314
315/**
316 * Write HTTP chunk.
317 */
318void
319http_response_write_chunk(int fd, struct worker *w, const char *p, size_t sz) {
320
321	struct http_response *r = http_response_init(w, 0, NULL);
322	r->keep_alive = 1; /* chunks are always keep-alive */
323
324	/* format packet */
325	r->out = format_chunk(p, sz, &r->out_sz);
326
327	/* send async write */
328	http_schedule_write(fd, r);
329}
330