root/src/noit_http.c

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

fixes #126

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