root/src/noit_http.c

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

remove some debugging, register with the eventer for sane output in the capability set, refs #171

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