root/src/noit_http.c

Revision ce0c8f24f23924ae8bccfbb84992fa8d8d534245, 34.4 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 5 years ago)

simple static handler, closes #235

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34 #include "noit_http.h"
35 #include "utils/noit_str.h"
36
37 #include <errno.h>
38 #include <ctype.h>
39 #include <assert.h>
40 #include <zlib.h>
41 #include <libxml/tree.h>
42
43 #define REQ_PAT "\r\n\r\n"
44 #define REQ_PATSIZE 4
45 #define HEADER_CONTENT_LENGTH "content-length"
46 #define HEADER_EXPECT "expect"
47
48 #define CTX_ADD_HEADER(a,b) \
49     noit_hash_replace(&ctx->res.headers, \
50                       strdup(a), strlen(a), strdup(b), free, free)
51 static const char _hexchars[16] =
52   {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
53 static void inplace_urldecode(char *c) {
54   char *o = c;
55   while(*c) {
56     if(*c == '%') {
57       int i, ord = 0;
58       for(i = 0; i < 2; i++) {
59         if(c[i] >= '0' && c[i] <= '9') ord = (ord << 4) | (c[i] - '0');
60         else if (c[i] >= 'a' && c[i] <= 'f') ord = (ord << 4) | (c[i] - 'a');
61         else if (c[i] >= 'A' && c[i] <= 'F') ord = (ord << 4) | (c[i] - 'A');
62         else break;
63       }
64       if(i==2) {
65         *((unsigned char *)o++) = ord;
66         c+=3;
67         continue;
68       }
69     }
70     *o++ = *c++;
71   }
72   *o = '\0';
73 }
74
75 struct bchain *bchain_alloc(size_t size) {
76   struct bchain *n;
77   n = malloc(size + ((char *)n->buff - (char *)n));
78   if(!n) return NULL;
79   n->prev = n->next = NULL;
80   n->start = n->size = 0;
81   n->allocd = size;
82   return n;
83 }
84 #define RELEASE_BCHAIN(a) do { \
85   while(a) { \
86     struct bchain *__b; \
87     __b = a; \
88     a = __b->next; \
89     bchain_free(__b); \
90   } \
91 } while(0)
92 struct bchain *bchain_from_data(const void *d, size_t size) {
93   struct bchain *n;
94   n = bchain_alloc(size);
95   if(!n) return NULL;
96   memcpy(n->buff, d, size);
97   n->size = size;
98   return n;
99 }
100 void bchain_free(struct bchain *b) {
101   free(b);
102 }
103
104 static noit_http_method
105 _method_enum(const char *s) {
106   switch(*s) {
107    case 'G':
108     if(!strcasecmp(s, "GET")) return NOIT_HTTP_GET;
109     break;
110    case 'H':
111     if(!strcasecmp(s, "HEAD")) return NOIT_HTTP_HEAD;
112     break;
113    case 'P':
114     if(!strcasecmp(s, "POST")) return NOIT_HTTP_POST;
115     break;
116    default:
117     break;
118   }
119   return NOIT_HTTP_OTHER;
120 }
121 static noit_http_protocol
122 _protocol_enum(const char *s) {
123   if(!strcasecmp(s, "HTTP/1.1")) return NOIT_HTTP11;
124   if(!strcasecmp(s, "HTTP/1.0")) return NOIT_HTTP10;
125   return NOIT_HTTP09;
126 }
127 static noit_boolean
128 _fixup_bchain(struct bchain *b) {
129   /* make sure lines (CRLF terminated) don't cross chain boundaries */
130   while(b) {
131     struct bchain *f;
132     int start_in_b, end_in_f;
133     size_t new_size;
134     const char *str_in_f;
135
136     start_in_b = b->start;
137     if(b->size > 2) {
138       if(memcmp(b->buff + b->start + b->size - 2, "\r\n", 2) == 0) {
139         b = b->next;
140         continue;
141       }
142       start_in_b = b->start + b->size - 3; /* we already checked -2 */
143       while(start_in_b >= b->start) {
144         if(memcmp(b->buff + start_in_b, "\r\n", 2) == 0) {
145           start_in_b += 2;
146           break;
147         }
148       }
149     }
150
151     /* start_in_b points to the beginning of the string we need to build
152      * into a new buffer.
153      */
154     f = b->next;
155     if(!f) return noit_false; /* Nothing left, can't complete the line */
156     str_in_f = strnstrn("\r\n", 2, f->buff + f->start, f->size);
157     if(!str_in_f) return noit_false; /* nothing in next chain -- too long */
158     str_in_f += 2;
159     end_in_f = (str_in_f - f->buff - f->start);
160     new_size = end_in_f + (b->start + b->size - start_in_b);
161     if(new_size > DEFAULT_BCHAINSIZE) return noit_false; /* string too long */
162     f = bchain_alloc(new_size);
163     f->prev = b;
164     f->next = b->next;
165     f->start = 0;
166     f->size = new_size;
167     memcpy(f->buff, b->buff + start_in_b, b->start + b->size - start_in_b);
168     memcpy(f->buff + b->start + b->size - start_in_b,
169            f->buff + f->start, end_in_f);
170     f->next->prev = f;
171     f->prev->next = f;
172     f->prev->size -= start_in_b - b->start;
173     f->next->size -= end_in_f;
174     f->next->start += end_in_f;
175     b = f->next; /* skip f, we know it is right */
176   }
177   return noit_true;
178 }
179 static noit_boolean
180 _extract_header(char *l, const char **n, const char **v) {
181   *n = NULL;
182   if(*l == ' ' || *l == '\t') {
183     while(*l == ' ' || *l == '\t') l++;
184     *v = l;
185     return noit_true;
186   }
187   *n = l;
188   while(*l != ':' && *l) { *l = tolower(*l); l++; }
189   if(!*l) return noit_false;
190   *v = l+1;
191   /* Right trim the name */
192   *l-- = '\0';
193   while(*l == ' ' || *l == '\t') *l-- = '\0';
194   while(**v == ' ' || **v == '\t') (*v)++;
195   return noit_true;
196 }
197 static int
198 _http_perform_write(noit_http_session_ctx *ctx, int *mask) {
199   int len, tlen = 0;
200   struct bchain **head, *b;
201  choose_bucket:
202   head = ctx->res.leader ? &ctx->res.leader : &ctx->res.output_raw;
203   b = *head;
204
205   if(!ctx->conn.e) return 0;
206 #if 0
207   if(ctx->res.output_started == noit_false) return EVENTER_EXCEPTION;
208 #endif
209   if(!b) {
210     if(ctx->res.closed) ctx->res.complete = noit_true;
211     *mask = EVENTER_EXCEPTION;
212     return tlen;
213   }
214
215   if(ctx->res.output_raw_offset >= b->size) {
216     *head = b->next;
217     bchain_free(b);
218     b = *head;
219     if(b) b->prev = NULL;
220     ctx->res.output_raw_offset = 0;
221     goto choose_bucket;
222   }
223
224   len = ctx->conn.e->opset->
225           write(ctx->conn.e->fd,
226                 b->buff + b->start + ctx->res.output_raw_offset,
227                 b->size - ctx->res.output_raw_offset,
228                 mask, ctx->conn.e);
229   if(len == -1 && errno == EAGAIN) {
230     *mask |= EVENTER_EXCEPTION;
231     return tlen;
232   }
233   if(len == -1) {
234     /* socket error */
235     ctx->conn.e->opset->close(ctx->conn.e->fd, mask, ctx->conn.e);
236     ctx->conn.e = NULL;
237     return -1;
238   }
239   ctx->res.output_raw_offset += len;
240   tlen += len;
241   goto choose_bucket;
242 }
243 static noit_boolean
244 noit_http_request_finalize_headers(noit_http_request *req, noit_boolean *err) {
245   int start;
246   void *vval;
247   const char *mstr, *last_name = NULL;
248   struct bchain *b;
249
250   if(req->state != NOIT_HTTP_REQ_HEADERS) return noit_false;
251   if(!req->current_input) req->current_input = req->first_input;
252   if(!req->current_input) return noit_false;
253  restart:
254   while(req->current_input->prev &&
255         (req->current_offset < (req->current_input->start + REQ_PATSIZE - 1))) {
256     int inset;
257     /* cross bucket */
258     if(req->current_input == req->last_input &&
259        req->current_offset >= (req->last_input->start + req->last_input->size))
260       return noit_false;
261     req->current_offset++;
262     inset = req->current_offset - req->current_input->start;
263     if(memcmp(req->current_input->buff + req->current_input->start,
264               REQ_PAT + (REQ_PATSIZE - inset), inset) == 0 &&
265        memcmp(req->current_input->prev->buff +
266                 req->current_input->prev->start +
267                 req->current_input->prev->size - REQ_PATSIZE + inset,
268               REQ_PAT + inset,
269               REQ_PATSIZE - inset) == 0) goto match;
270   }
271   start = MAX(req->current_offset - REQ_PATSIZE, req->current_input->start);
272   mstr = strnstrn(REQ_PAT, REQ_PATSIZE,
273                   req->current_input->buff + start,
274                   req->current_input->size -
275                     (start - req->current_input->start));
276   if(!mstr && req->current_input->next) {
277     req->current_input = req->current_input->next;
278     req->current_offset = req->current_input->start;
279     goto restart;
280   }
281   if(!mstr) return noit_false;
282   req->current_offset = mstr - req->current_input->buff + REQ_PATSIZE;
283  match:
284   req->current_request_chain = req->first_input;
285   noitL(noit_debug, " noit_http_request_finalize : match(%d in %d)\n",
286         (int)(req->current_offset - req->current_input->start),
287         (int)req->current_input->size);
288   if(req->current_offset <
289      req->current_input->start + req->current_input->size) {
290     /* There are left-overs */
291     int lsize = req->current_input->size - req->current_offset;
292     noitL(noit_debug, " noit_http_request_finalize -- leftovers: %d\n", lsize);
293     req->first_input = bchain_alloc(lsize);
294     req->first_input->prev = NULL;
295     req->first_input->next = req->current_input->next;
296     req->first_input->start = 0;
297     req->first_input->size = lsize;
298     memcpy(req->first_input->buff,
299            req->current_input->buff + req->current_offset,
300            req->first_input->size);
301     if(req->last_input == req->current_input)
302       req->last_input = req->first_input;
303     else
304       bchain_free(req->current_input);
305   }
306   else {
307     req->first_input = req->last_input = NULL;
308   }
309   req->current_input = NULL;
310   req->current_offset = 0;
311
312   /* Now we need to dissect the current_request_chain into an HTTP request */
313   /* First step: make sure that no line crosses a chain boundary by
314    * inserting new chains as necessary.
315    */
316   if(!_fixup_bchain(req->current_request_chain)) {
317     *err = noit_true;
318     return noit_false;
319   }
320   /* Second step is to parse out the request itself */
321   for(b = req->current_request_chain; b; b = b->next) {
322     char *curr_str, *next_str;
323     b->buff[b->start + b->size - 2] = '\0';
324     curr_str = b->buff + b->start;
325     do {
326       next_str = strstr(curr_str, "\r\n");
327       if(next_str) {
328         *((char *)next_str) = '\0';
329         next_str += 2;
330       }
331       if(req->method_str && *curr_str == '\0')
332         break; /* our CRLFCRLF... end of req */
333 #define FAIL do { *err = noit_true; return noit_false; } while(0)
334       if(!req->method_str) { /* request line */
335         req->method_str = (char *)curr_str;
336         req->uri_str = strchr(curr_str, ' ');
337         if(!req->uri_str) FAIL;
338         *(req->uri_str) = '\0';
339         req->uri_str++;
340         req->protocol_str = strchr(req->uri_str, ' ');
341         if(!req->protocol_str) FAIL;
342         *(req->protocol_str) = '\0';
343         req->protocol_str++;
344         req->method = _method_enum(req->method_str);
345         req->protocol = _protocol_enum(req->protocol_str);
346         if(req->protocol == NOIT_HTTP11) req->opts |= NOIT_HTTP_CHUNKED;
347       }
348       else { /* request headers */
349         const char *name, *value;
350         if(_extract_header(curr_str, &name, &value) == noit_false) FAIL;
351         if(!name && !last_name) FAIL;
352         if(!strcmp(name ? name : last_name, "accept-encoding")) {
353           if(strstr(value, "gzip")) req->opts |= NOIT_HTTP_GZIP;
354           if(strstr(value, "deflate")) req->opts |= NOIT_HTTP_DEFLATE;
355         }
356         if(name)
357           noit_hash_replace(&req->headers, name, strlen(name), (void *)value,
358                             NULL, NULL);
359         else {
360           struct bchain *b;
361           const char *prefix = NULL;
362           int l1, l2;
363           noit_hash_retr_str(&req->headers, last_name, strlen(last_name),
364                              &prefix);
365           if(!prefix) FAIL;
366           l1 = strlen(prefix);
367           l2 = strlen(value);
368           b = bchain_alloc(l1 + l2 + 2);
369           b->next = req->current_request_chain;
370           b->next->prev = b;
371           req->current_request_chain = b;
372           b->size = l1 + l2 + 2;
373           memcpy(b->buff, prefix, l1);
374           b->buff[l1] = ' ';
375           memcpy(b->buff + l1 + 1, value, l2);
376           b->buff[l1 + 1 + l2] = '\0';
377           noit_hash_replace(&req->headers, last_name, strlen(last_name),
378                             b->buff, NULL, NULL);
379         }
380         if(name) last_name = name;
381       }
382       curr_str = next_str;
383     } while(next_str);
384   }
385
386   /* headers are done... we could need to read a payload */
387   if(noit_hash_retrieve(&req->headers,
388                         HEADER_CONTENT_LENGTH,
389                         sizeof(HEADER_CONTENT_LENGTH)-1, &vval)) {
390     const char *val = vval;
391     req->has_payload = noit_true;
392     req->content_length = strtoll(val, NULL, 10);
393   }
394   if(noit_hash_retrieve(&req->headers, HEADER_EXPECT,
395                         sizeof(HEADER_EXPECT)-1, &vval)) {
396     const char *val = vval;
397     if(strncmp(val, "100-", 4) || /* Bad expect header */
398        req->has_payload == noit_false) /* expect, but no content length */
399       FAIL;
400     /* We need to tell the client to "go-ahead" -- HTTP sucks */
401     req->state = NOIT_HTTP_REQ_EXPECT;
402     return noit_false;
403   }
404   if(req->content_length > 0) {
405     /* switch modes... let's go read the payload */
406     req->state = NOIT_HTTP_REQ_PAYLOAD;
407     return noit_false;
408   }
409
410   req->complete = noit_true;
411   return noit_true;
412 }
413 void
414 noit_http_process_querystring(noit_http_request *req) {
415   char *cp, *copy, *interest, *brk;
416   cp = strchr(req->uri_str, '?');
417   if(!cp) return;
418   *cp++ = '\0';
419   for (interest = strtok_r(cp, "&", &brk);
420        interest;
421        interest = strtok_r(NULL, "&", &brk)) {
422     char *eq;
423     eq = strchr(interest, '=');
424     if(!eq) {
425       inplace_urldecode(interest);
426       noit_hash_store(&req->querystring, interest, strlen(interest), NULL);
427     }
428     else {
429       *eq++ = '\0';
430       inplace_urldecode(interest);
431       inplace_urldecode(eq);
432       noit_hash_store(&req->querystring, interest, strlen(interest), eq);
433     }
434   }
435 }
436 static noit_boolean
437 noit_http_request_finalize_payload(noit_http_request *req, noit_boolean *err) {
438   req->complete = noit_true;
439   return noit_true;
440 }
441 static noit_boolean
442 noit_http_request_finalize(noit_http_request *req, noit_boolean *err) {
443   if(req->state == NOIT_HTTP_REQ_HEADERS)
444     if(noit_http_request_finalize_headers(req, err)) return noit_true;
445   if(req->state == NOIT_HTTP_REQ_EXPECT) return noit_false;
446   if(req->state == NOIT_HTTP_REQ_PAYLOAD)
447     if(noit_http_request_finalize_payload(req, err)) return noit_true;
448   return noit_false;
449 }
450 int
451 noit_http_complete_request(noit_http_session_ctx *ctx, int mask) {
452   struct bchain *in;
453   noit_boolean rv, err = noit_false;
454
455   if(mask & EVENTER_EXCEPTION) {
456    full_error:
457     ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
458     ctx->conn.e = NULL;
459     return 0;
460   }
461   if(ctx->req.complete == noit_true) return EVENTER_EXCEPTION;
462
463   /* We could have a complete request in the tail of a previous request */
464   rv = noit_http_request_finalize(&ctx->req, &err);
465   if(rv == noit_true) return EVENTER_WRITE | EVENTER_EXCEPTION;
466   if(err == noit_true) goto full_error;
467
468   while(1) {
469     int len;
470
471     in = ctx->req.last_input;
472     if(!in) {
473       in = ctx->req.first_input = ctx->req.last_input =
474         bchain_alloc(DEFAULT_BCHAINSIZE);
475       if(!in) goto full_error;
476     }
477     if(in->size > 0 && /* we've read something */
478        DEFAULT_BCHAINMINREAD > BCHAIN_SPACE(in) && /* we'd like read more */
479        DEFAULT_BCHAINMINREAD < DEFAULT_BCHAINSIZE) { /* and we can */
480       in->next = ctx->req.last_input =
481         bchain_alloc(DEFAULT_BCHAINSIZE);
482       in->next->prev = in;
483       in = in->next;
484       if(!in) goto full_error;
485     }
486
487     len = ctx->conn.e->opset->read(ctx->conn.e->fd,
488                                    in->buff + in->start + in->size,
489                                    in->allocd - in->size - in->start,
490                                    &mask, ctx->conn.e);
491     noitL(noit_debug, " noit_http -> read(%d) = %d\n", ctx->conn.e->fd, len);
492     if(len == -1 && errno == EAGAIN) return mask;
493     if(len <= 0) goto full_error;
494     if(len > 0) in->size += len;
495     rv = noit_http_request_finalize(&ctx->req, &err);
496     if(len == -1 || err == noit_true) goto full_error;
497     if(ctx->req.state == NOIT_HTTP_REQ_EXPECT) {
498       const char *expect;
499       ctx->req.state = NOIT_HTTP_REQ_PAYLOAD;
500       assert(ctx->res.leader == NULL);
501       expect = "HTTP/1.1 100 Continue\r\n\r\n";
502       ctx->res.leader = bchain_from_data(expect, strlen(expect));
503       _http_perform_write(ctx, &mask);
504       if(ctx->res.leader != NULL) return mask;
505     }
506     if(rv == noit_true) return mask | EVENTER_WRITE | EVENTER_EXCEPTION;
507   }
508   /* Not reached:
509    * return EVENTER_READ | EVENTER_EXCEPTION;
510    */
511 }
512 noit_boolean
513 noit_http_session_prime_input(noit_http_session_ctx *ctx,
514                               const void *data, size_t len) {
515   if(ctx->req.first_input != NULL) return noit_false;
516   if(len > DEFAULT_BCHAINSIZE) return noit_false;
517   ctx->req.first_input = ctx->req.last_input =
518       bchain_alloc(DEFAULT_BCHAINSIZE);
519   memcpy(ctx->req.first_input->buff, data, len);
520   ctx->req.first_input->size = len;
521   return noit_true;
522 }
523
524 void
525 noit_http_request_release(noit_http_session_ctx *ctx) {
526   noit_hash_destroy(&ctx->req.querystring, NULL, NULL);
527   noit_hash_destroy(&ctx->req.headers, NULL, NULL);
528   /* If we expected a payload, we expect a trailing \r\n */
529   if(ctx->req.has_payload) {
530     int drained, mask;
531     ctx->drainage = ctx->req.content_length - ctx->req.content_length_read;
532     /* best effort, we'll drain it before the next request anyway */
533     drained = noit_http_session_req_consume(ctx, NULL, ctx->drainage, &mask);
534     ctx->drainage -= drained;
535   }
536   RELEASE_BCHAIN(ctx->req.current_request_chain);
537   memset(&ctx->req, 0, sizeof(ctx->req));
538 }
539 void
540 noit_http_response_release(noit_http_session_ctx *ctx) {
541   noit_hash_destroy(&ctx->res.headers, free, free);
542   if(ctx->res.status_reason) free(ctx->res.status_reason);
543   RELEASE_BCHAIN(ctx->res.leader);
544   RELEASE_BCHAIN(ctx->res.output);
545   RELEASE_BCHAIN(ctx->res.output_raw);
546   memset(&ctx->res, 0, sizeof(ctx->res));
547 }
548 void
549 noit_http_ctx_session_release(noit_http_session_ctx *ctx) {
550   if(noit_atomic_dec32(&ctx->ref_cnt) == 0) {
551     noit_http_request_release(ctx);
552     noit_http_response_release(ctx);
553     free(ctx);
554   }
555 }
556 int
557 noit_http_session_req_consume(noit_http_session_ctx *ctx,
558                               void *buf, size_t len, int *mask) {
559   size_t bytes_read = 0;
560   /* We attempt to consume from the first_input */
561   struct bchain *in, *tofree;
562   noitL(noit_debug, " ... noit_http_session_req_consume(%d) %d of %d\n",
563         ctx->conn.e->fd, (int)len,
564         (int)(ctx->req.content_length - ctx->req.content_length_read));
565   len = MIN(len, ctx->req.content_length - ctx->req.content_length_read);
566   while(bytes_read < len) {
567     int crlen = 0;
568     in = ctx->req.first_input;
569     while(in && bytes_read < len) {
570       int partial_len = MIN(in->size, len - bytes_read);
571       if(buf) memcpy((char *)buf+bytes_read, in->buff+in->start, partial_len);
572       bytes_read += partial_len;
573       ctx->req.content_length_read += partial_len;
574       noitL(noit_debug, " ... filling %d bytes (read through %d/%d)\n",
575             (int)bytes_read, (int)ctx->req.content_length_read,
576             (int)ctx->req.content_length);
577       in->start += partial_len;
578       in->size -= partial_len;
579       if(in->size == 0) {
580         tofree = in;
581         ctx->req.first_input = in = in->next;
582         tofree->next = NULL;
583         RELEASE_BCHAIN(tofree);
584         if(in == NULL) {
585           ctx->req.last_input = NULL;
586           noitL(noit_debug, " ... noit_http_session_req_consume = %d\n",
587                 (int)bytes_read);
588           return bytes_read;
589         }
590       }
591     }
592     while(bytes_read + crlen < len) {
593       int rlen;
594       in = ctx->req.last_input;
595       if(!in)
596         in = ctx->req.first_input = ctx->req.last_input =
597             bchain_alloc(DEFAULT_BCHAINSIZE);
598       else if(in->start + in->size >= in->allocd) {
599         in->next = bchain_alloc(DEFAULT_BCHAINSIZE);
600         in = ctx->req.last_input = in->next;
601       }
602       /* pull next chunk */
603       rlen = ctx->conn.e->opset->read(ctx->conn.e->fd,
604                                       in->buff + in->start + in->size,
605                                       in->allocd - in->size - in->start,
606                                       mask, ctx->conn.e);
607       noitL(noit_debug, " noit_http -> read(%d) = %d\n", ctx->conn.e->fd, rlen);
608       if(rlen == -1 && errno == EAGAIN) {
609         /* We'd block to read more, but we have data,
610          * so do a short read */
611         if(ctx->req.first_input->size) break;
612         /* We've got nothing... */
613         noitL(noit_debug, " ... noit_http_session_req_consume = -1 (EAGAIN)\n");
614         return -1;
615       }
616       if(rlen <= 0) {
617         noitL(noit_debug, " ... noit_http_session_req_consume = -1 (error)\n");
618         return -1;
619       }
620       in->size += rlen;
621       crlen += rlen;
622     }
623   }
624   /* NOT REACHED */
625   return bytes_read;
626 }
627 int
628 noit_http_session_drive(eventer_t e, int origmask, void *closure,
629                         struct timeval *now) {
630   noit_http_session_ctx *ctx = closure;
631   int rv = 0;
632   int mask = origmask;
633
634   if(origmask & EVENTER_EXCEPTION)
635     goto abort_drive;
636
637   /* Drainage -- this is as nasty as it sounds
638    * The last request could have unread upload content, we would have
639    * noted that in noit_http_request_release.
640    */
641   noitL(noit_debug, " -> noit_http_session_drive(%d) [%x]\n", e->fd, origmask);
642   while(ctx->drainage > 0) {
643     int len;
644     noitL(noit_debug, "   ... draining last request(%d)\n", e->fd);
645     len = noit_http_session_req_consume(ctx, NULL, ctx->drainage, &mask);
646     if(len == -1 && errno == EAGAIN) {
647       noitL(noit_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, mask);
648       return mask;
649     }
650     if(len <= 0) goto abort_drive;
651     ctx->drainage -= len;
652   }
653
654  next_req:
655   if(ctx->req.complete != noit_true) {
656     int maybe_write_mask;
657     noitL(noit_debug, "   -> noit_http_complete_request(%d)\n", e->fd);
658     mask = noit_http_complete_request(ctx, origmask);
659     noitL(noit_debug, "   <- noit_http_complete_request(%d) = %d\n",
660           e->fd, mask);
661     _http_perform_write(ctx, &maybe_write_mask);
662     if(ctx->conn.e == NULL) goto release;
663     if(ctx->req.complete != noit_true) {
664       noitL(noit_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd,
665             mask|maybe_write_mask);
666       return mask | maybe_write_mask;
667     }
668     noitL(noit_debug, "HTTP start request (%s)\n", ctx->req.uri_str);
669   }
670
671   /* only dispatch if the response is not complete */
672   if(ctx->res.complete == noit_false) {
673     noitL(noit_debug, "   -> dispatch(%d)\n", e->fd);
674     rv = ctx->dispatcher(ctx);
675     noitL(noit_debug, "   <- dispatch(%d) = %d\n", e->fd, rv);
676   }
677
678   _http_perform_write(ctx, &mask);
679   if(ctx->res.complete == noit_true &&
680      ctx->conn.e &&
681      ctx->conn.needs_close == noit_true) {
682    abort_drive:
683     ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
684     ctx->conn.e = NULL;
685     goto release;
686   }
687   if(ctx->res.complete == noit_true) {
688     noit_http_request_release(ctx);
689     noit_http_response_release(ctx);
690   }
691   if(ctx->req.complete == noit_false) goto next_req;
692   if(ctx->conn.e) {
693     noitL(noit_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, mask|rv);
694     return mask | rv;
695   }
696   noitL(noit_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, 0);
697   return 0;
698  release:
699   noit_http_ctx_session_release(ctx);
700   noitL(noit_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, 0);
701   return 0;
702 }
703
704 noit_http_session_ctx *
705 noit_http_session_ctx_new(noit_http_dispatch_func f, void *c, eventer_t e) {
706   noit_http_session_ctx *ctx;
707   ctx = calloc(1, sizeof(*ctx));
708   ctx->ref_cnt = 1;
709   ctx->req.complete = noit_false;
710   ctx->conn.e = e;
711   ctx->dispatcher = f;
712   ctx->dispatcher_closure = c;
713   ctx->drive = noit_http_session_drive;
714   return ctx;
715 }
716
717 noit_boolean
718 noit_http_response_status_set(noit_http_session_ctx *ctx,
719                               int code, const char *reason) {
720   if(ctx->res.output_started == noit_true) return noit_false;
721   ctx->res.protocol = ctx->req.protocol;
722   if(code < 100 || code > 999) return noit_false;
723   ctx->res.status_code = code;
724   if(ctx->res.status_reason) free(ctx->res.status_reason);
725   ctx->res.status_reason = strdup(reason);
726   return noit_true;
727 }
728 noit_boolean
729 noit_http_response_header_set(noit_http_session_ctx *ctx,
730                               const char *name, const char *value) {
731   if(ctx->res.output_started == noit_true) return noit_false;
732   noit_hash_replace(&ctx->res.headers, strdup(name), strlen(name),
733                     strdup(value), free, free);
734   return noit_true;
735 }
736 noit_boolean
737 noit_http_response_option_set(noit_http_session_ctx *ctx, u_int32_t opt) {
738   if(ctx->res.output_started == noit_true) return noit_false;
739   /* transfer and content encodings only allowed in HTTP/1.1 */
740   if(ctx->res.protocol != NOIT_HTTP11 &&
741      (opt & NOIT_HTTP_CHUNKED))
742     return noit_false;
743   if(ctx->res.protocol != NOIT_HTTP11 &&
744      (opt & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)))
745     return noit_false;
746   if(((ctx->res.output_options | opt) &
747       (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) ==
748         (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE))
749     return noit_false;
750
751   /* Check out "accept" set */
752   if(!(opt & ctx->req.opts)) return noit_false;
753
754   ctx->res.output_options |= opt;
755   if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
756     CTX_ADD_HEADER("Transfer-Encoding", "chunked");
757   if(ctx->res.output_options & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) {
758     CTX_ADD_HEADER("Vary", "Accept-Encoding");
759     if(ctx->res.output_options & NOIT_HTTP_GZIP)
760       CTX_ADD_HEADER("Content-Encoding", "gzip");
761     else if(ctx->res.output_options & NOIT_HTTP_DEFLATE)
762       CTX_ADD_HEADER("Content-Encoding", "deflate");
763   }
764   if(ctx->res.output_options & NOIT_HTTP_CLOSE) {
765     CTX_ADD_HEADER("Connection", "close");
766     ctx->conn.needs_close = noit_true;
767   }
768   return noit_true;
769 }
770 noit_boolean
771 noit_http_response_append(noit_http_session_ctx *ctx,
772                           const void *b, size_t l) {
773   struct bchain *o;
774   int boff = 0;
775   if(ctx->res.closed == noit_true) return noit_false;
776   if(ctx->res.output_started == noit_true &&
777      !(ctx->res.output_options & (NOIT_HTTP_CLOSE | NOIT_HTTP_CHUNKED)))
778     return noit_false;
779   if(!ctx->res.output)
780     assert(ctx->res.output = bchain_alloc(DEFAULT_BCHAINSIZE));
781   o = ctx->res.output;
782   while(o->next) o = o->next;
783   while(l > 0) {
784     if(o->allocd == o->start + o->size) {
785       /* Filled up, need another */
786       o->next = bchain_alloc(DEFAULT_BCHAINSIZE);
787       o->next->prev = o->next;
788       o = o->next;
789     }
790     if(o->allocd > o->start + o->size) {
791       int tocopy = MIN(l, o->allocd - o->start - o->size);
792       memcpy(o->buff + o->start + o->size, (const char *)b + boff, tocopy);
793       o->size += tocopy;
794       boff += tocopy;
795       l -= tocopy;
796     }
797   }
798   return noit_true;
799 }
800 noit_boolean
801 noit_http_response_append_bchain(noit_http_session_ctx *ctx,
802                                  struct bchain *b) {
803   struct bchain *o;
804   if(ctx->res.closed == noit_true) return noit_false;
805   if(ctx->res.output_started == noit_true &&
806      !(ctx->res.output_options & (NOIT_HTTP_CHUNKED | NOIT_HTTP_CLOSE)))
807     return noit_false;
808   if(!ctx->res.output)
809     ctx->res.output = b;
810   else {
811     o = ctx->res.output;
812     while(o->next) o = o->next;
813     o->next = b;
814     b->prev = o;
815   }
816   return noit_true;
817 }
818 static int
819 _http_construct_leader(noit_http_session_ctx *ctx) {
820   int len = 0, tlen;
821   struct bchain *b;
822   const char *protocol_str;
823   const char *key, *value;
824   int klen;
825   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
826
827   assert(!ctx->res.leader);
828   ctx->res.leader = b = bchain_alloc(DEFAULT_BCHAINSIZE);
829
830   protocol_str = ctx->res.protocol == NOIT_HTTP11 ?
831                    "HTTP/1.1" :
832                    (ctx->res.protocol == NOIT_HTTP10 ?
833                      "HTTP/1.0" :
834                      "HTTP/0.9");
835   tlen = snprintf(b->buff, b->allocd, "%s %03d %s\r\n",
836                   protocol_str, ctx->res.status_code, ctx->res.status_reason);
837   if(tlen < 0) return -1;
838   len = b->size = tlen;
839
840 #define CTX_LEADER_APPEND(s, slen) do { \
841   if(b->size + slen > DEFAULT_BCHAINSIZE) { \
842     b->next = bchain_alloc(DEFAULT_BCHAINSIZE); \
843     assert(b->next); \
844     b->next->prev = b; \
845     b = b->next; \
846   } \
847   assert(DEFAULT_BCHAINSIZE >= b->size + slen); \
848   memcpy(b->buff + b->start + b->size, s, slen); \
849   b->size += slen; \
850 } while(0)
851   while(noit_hash_next_str(&ctx->res.headers, &iter,
852                            &key, &klen, &value)) {
853     int vlen = strlen(value);
854     CTX_LEADER_APPEND(key, klen);
855     CTX_LEADER_APPEND(": ", 2);
856     CTX_LEADER_APPEND(value, vlen);
857     CTX_LEADER_APPEND("\r\n", 2);
858   }
859   CTX_LEADER_APPEND("\r\n", 2);
860   return len;
861 }
862 static int memgzip2(Bytef *dest, uLongf *destLen,
863                     const Bytef *source, uLong sourceLen, int level) {
864   z_stream stream;
865   int err;
866
867   memset(&stream, 0, sizeof(stream));
868   stream.next_in = (Bytef*)source;
869   stream.avail_in = (uInt)sourceLen;
870   stream.next_out = dest;
871   stream.avail_out = (uInt)*destLen;
872   if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
873
874   err = deflateInit2(&stream, level, Z_DEFLATED, 15+16, 8,
875                      Z_DEFAULT_STRATEGY);
876   if (err != Z_OK) return err;
877
878   err = deflate(&stream, Z_FINISH);
879   if (err != Z_STREAM_END) {
880     deflateEnd(&stream);
881     return err == Z_OK ? Z_BUF_ERROR : err;
882   }
883   *destLen = stream.total_out;
884
885   err = deflateEnd(&stream);
886   return err;
887 }
888 static noit_boolean
889 _http_encode_chain(struct bchain *out, struct bchain *in, int opts) {
890   /* implement gzip and deflate! */
891   if(opts & NOIT_HTTP_GZIP) {
892     uLongf olen;
893     olen = out->allocd - out->start;
894     if(Z_OK != memgzip2((Bytef *)(out->buff + out->start), &olen,
895                         (Bytef *)(in->buff + in->start), (uLong)in->size,
896                         9)) {
897       noitL(noit_error, "zlib compress2 error\n");
898       return noit_false;
899     }
900     out->size += olen;
901   }
902   else if(opts & NOIT_HTTP_DEFLATE) {
903     uLongf olen;
904     olen = out->allocd - out->start;
905     if(Z_OK != compress2((Bytef *)(out->buff + out->start), &olen,
906                          (Bytef *)(in->buff + in->start), (uLong)in->size,
907                          9)) {
908       noitL(noit_error, "zlib compress2 error\n");
909       return noit_false;
910     }
911     out->size += olen;
912   }
913   else {
914     if(in->size > out->allocd - out->start) return noit_false;
915     memcpy(out->buff + out->start, in->buff + in->start, in->size);
916     out->size += in->size;
917   }
918   return noit_true;
919 }
920 struct bchain *
921 noit_http_process_output_bchain(noit_http_session_ctx *ctx,
922                                 struct bchain *in) {
923   struct bchain *out;
924   int ilen, maxlen = in->size, hexlen;
925   int opts = ctx->res.output_options;
926
927   /* a chunked header looks like: hex*\r\ndata\r\n */
928   /* let's assume that content never gets "larger" */
929   if(opts & NOIT_HTTP_GZIP) maxlen = deflateBound(NULL, in->size);
930   else if(opts & NOIT_HTTP_DEFLATE) maxlen = compressBound(in->size);
931
932   /* So, the link size is the len(data) + 4 + ceil(log(len(data))/log(16)) */
933   ilen = maxlen;
934   hexlen = 0;
935   while(ilen) { ilen >>= 4; hexlen++; }
936   if(hexlen == 0) hexlen = 1;
937
938   out = bchain_alloc(hexlen + 4 + maxlen);
939   /* if we're chunked, let's give outselved hexlen + 2 prefix space */
940   if(opts & NOIT_HTTP_CHUNKED) out->start = hexlen + 2;
941   if(_http_encode_chain(out, in, opts) == noit_false) {
942     free(out);
943     return NULL;
944   }
945   /* Too long! Out "larger" assumption is bad */
946   if(opts & NOIT_HTTP_CHUNKED) {
947     ilen = out->size;
948     assert(out->start+out->size+2 <= out->allocd);
949     out->buff[out->start + out->size++] = '\r';
950     out->buff[out->start + out->size++] = '\n';
951     out->start = 0;
952     /* terminate */
953     out->size += 2;
954     out->buff[hexlen] = '\r';
955     out->buff[hexlen+1] = '\n';
956     /* backfill */
957     out->size += hexlen;
958     while(hexlen > 0) {
959       out->buff[hexlen - 1] = _hexchars[ilen & 0xf];
960       ilen >>= 4;
961       hexlen--;
962     }
963     while(out->buff[out->start] == '0') {
964       out->start++;
965       out->size--;
966     }
967   }
968   return out;
969 }
970 noit_boolean
971 noit_http_response_flush(noit_http_session_ctx *ctx, noit_boolean final) {
972   struct bchain *o, *r;
973   int mask;
974
975   if(ctx->res.closed == noit_true) return noit_false;
976   if(ctx->res.output_started == noit_false) {
977     _http_construct_leader(ctx);
978     ctx->res.output_started = noit_true;
979   }
980   /* encode output to output_raw */
981   r = ctx->res.output_raw;
982   while(r && r->next) r = r->next;
983   /* r is the last raw output link */
984   o = ctx->res.output;
985   /* o is the first output link to process */
986   while(o) {
987     struct bchain *tofree, *n;
988     n = noit_http_process_output_bchain(ctx, o);
989     if(!n) {
990       /* Bad, response stops here! */
991       noitL(noit_error, "noit_http_process_output_bchain: NULL\n");
992       while(o) { tofree = o; o = o->next; free(tofree); }
993       final = noit_true;
994       break;
995     }
996     if(r) {
997       r->next = n;
998       n->prev = r;
999       r = n;
1000     }
1001     else {
1002       r = ctx->res.output_raw = n;
1003     }
1004     tofree = o; o = o->next; free(tofree); /* advance and free */
1005   }
1006   ctx->res.output = NULL;
1007   if(final) {
1008     struct bchain *n;
1009     ctx->res.closed = noit_true;
1010     if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
1011       n = bchain_from_data("0\r\n\r\n", 5);
1012     else
1013       n = bchain_from_data("\r\n", 2);
1014     if(r) {
1015       r->next = n;
1016       n->prev = r;
1017       r = n;
1018     }
1019     else {
1020       r = ctx->res.output_raw = n;
1021     }
1022   }
1023
1024   _http_perform_write(ctx, &mask);
1025   if(ctx->conn.e) {
1026     eventer_update(ctx->conn.e, mask);
1027   }
1028   /* If the write fails completely, the event will be closed, freed and NULL */
1029   return ctx->conn.e ? noit_true : noit_false;
1030 }
1031 noit_boolean
1032 noit_http_response_end(noit_http_session_ctx *ctx) {
1033   if(ctx->res.output)
1034     noitL(noit_debug, "HTTP finished request (%s)\n", ctx->req.uri_str);
1035   if(!noit_http_response_flush(ctx, noit_true)) return noit_false;
1036   return noit_true;
1037 }
1038
1039
1040 /* Helper functions */
1041
1042 static int
1043 noit_http_write_xml(void *vctx, const char *buffer, int len) {
1044   if(noit_http_response_append((noit_http_session_ctx *)vctx, buffer, len))
1045     return len;
1046   return -1;
1047 }
1048 static int
1049 noit_http_close_xml(void *vctx) {
1050   noit_http_response_end((noit_http_session_ctx *)vctx);
1051   return 0;
1052 }
1053 void
1054 noit_http_response_xml(noit_http_session_ctx *ctx, xmlDocPtr doc) {
1055   xmlOutputBufferPtr out;
1056   xmlCharEncodingHandlerPtr enc;
1057   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
1058   out = xmlOutputBufferCreateIO(noit_http_write_xml,
1059                                 noit_http_close_xml,
1060                                 ctx, enc);
1061   xmlSaveFormatFileTo(out, doc, "utf8", 1);
1062 }
1063
Note: See TracBrowser for help on using the browser.