root/src/noit_http.c

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

fixes... holy crap, it works. This things leaks and persists when it shouldn't... but we have data flow! refs #71

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