root/src/noit_http.c

Revision fddf7172a39575dd9460f878ba73e3b42e8f8154, 34.5 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 4 years ago)

various http bugs

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