headers_buckets.c revision 262339
1/* Copyright 2004 Justin Erenkrantz and Greg Stein
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include <stdlib.h>
17
18#include <apr_general.h>  /* for strcasecmp() */
19
20#include "serf.h"
21#include "serf_bucket_util.h"
22
23#include "serf_private.h" /* for serf__bucket_headers_remove */
24
25
26typedef struct header_list {
27    const char *header;
28    const char *value;
29
30    apr_size_t header_size;
31    apr_size_t value_size;
32
33    int alloc_flags;
34#define ALLOC_HEADER 0x0001  /* header lives in our allocator */
35#define ALLOC_VALUE  0x0002  /* value lives in our allocator */
36
37    struct header_list *next;
38} header_list_t;
39
40typedef struct {
41    header_list_t *list;
42    header_list_t *last;
43
44    header_list_t *cur_read;
45    enum {
46        READ_START,     /* haven't started reading yet */
47        READ_HEADER,    /* reading cur_read->header */
48        READ_SEP,       /* reading ": " */
49        READ_VALUE,     /* reading cur_read->value */
50        READ_CRLF,      /* reading "\r\n" */
51        READ_TERM,      /* reading the final "\r\n" */
52        READ_DONE       /* no more data to read */
53    } state;
54    apr_size_t amt_read; /* how much of the current state we've read */
55
56} headers_context_t;
57
58
59serf_bucket_t *serf_bucket_headers_create(
60    serf_bucket_alloc_t *allocator)
61{
62    headers_context_t *ctx;
63
64    ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx));
65    ctx->list = NULL;
66    ctx->last = NULL;
67    ctx->state = READ_START;
68
69    return serf_bucket_create(&serf_bucket_type_headers, allocator, ctx);
70}
71
72void serf_bucket_headers_setx(
73    serf_bucket_t *bkt,
74    const char *header, apr_size_t header_size, int header_copy,
75    const char *value, apr_size_t value_size, int value_copy)
76{
77    headers_context_t *ctx = bkt->data;
78    header_list_t *hdr;
79
80#if 0
81    /* ### include this? */
82    if (ctx->cur_read) {
83        /* we started reading. can't change now. */
84        abort();
85    }
86#endif
87
88    hdr = serf_bucket_mem_alloc(bkt->allocator, sizeof(*hdr));
89    hdr->header_size = header_size;
90    hdr->value_size = value_size;
91    hdr->alloc_flags = 0;
92    hdr->next = NULL;
93
94    if (header_copy) {
95        hdr->header = serf_bstrmemdup(bkt->allocator, header, header_size);
96        hdr->alloc_flags |= ALLOC_HEADER;
97    }
98    else {
99        hdr->header = header;
100    }
101
102    if (value_copy) {
103        hdr->value = serf_bstrmemdup(bkt->allocator, value, value_size);
104        hdr->alloc_flags |= ALLOC_VALUE;
105    }
106    else {
107        hdr->value = value;
108    }
109
110    /* Add the new header at the end of the list. */
111    if (ctx->last)
112        ctx->last->next = hdr;
113    else
114        ctx->list = hdr;
115
116    ctx->last = hdr;
117}
118
119void serf_bucket_headers_set(
120    serf_bucket_t *headers_bucket,
121    const char *header,
122    const char *value)
123{
124    serf_bucket_headers_setx(headers_bucket,
125                             header, strlen(header), 0,
126                             value, strlen(value), 1);
127}
128
129void serf_bucket_headers_setc(
130    serf_bucket_t *headers_bucket,
131    const char *header,
132    const char *value)
133{
134    serf_bucket_headers_setx(headers_bucket,
135                             header, strlen(header), 1,
136                             value, strlen(value), 1);
137}
138
139void serf_bucket_headers_setn(
140    serf_bucket_t *headers_bucket,
141    const char *header,
142    const char *value)
143{
144    serf_bucket_headers_setx(headers_bucket,
145                             header, strlen(header), 0,
146                             value, strlen(value), 0);
147}
148
149const char *serf_bucket_headers_get(
150    serf_bucket_t *headers_bucket,
151    const char *header)
152{
153    headers_context_t *ctx = headers_bucket->data;
154    header_list_t *found = ctx->list;
155    const char *val = NULL;
156    int value_size = 0;
157    int val_alloc = 0;
158
159    while (found) {
160        if (strcasecmp(found->header, header) == 0) {
161            if (val) {
162                /* The header is already present.  RFC 2616, section 4.2
163                   indicates that we should append the new value, separated by
164                   a comma.  Reasoning: for headers whose values are known to
165                   be comma-separated, that is clearly the correct behavior;
166                   for others, the correct behavior is undefined anyway. */
167
168                /* The "+1" is for the comma; the +1 in the alloc
169                   call is for the terminating '\0' */
170                apr_size_t new_size = found->value_size + value_size + 1;
171                char *new_val = serf_bucket_mem_alloc(headers_bucket->allocator,
172                                                      new_size + 1);
173                memcpy(new_val, val, value_size);
174                new_val[value_size] = ',';
175                memcpy(new_val + value_size + 1, found->value,
176                       found->value_size);
177                new_val[new_size] = '\0';
178                /* Copy the new value over the already existing value. */
179                if (val_alloc)
180                    serf_bucket_mem_free(headers_bucket->allocator, (void*)val);
181                val_alloc |= ALLOC_VALUE;
182                val = new_val;
183                value_size = new_size;
184            }
185            else {
186                val = found->value;
187                value_size = found->value_size;
188            }
189        }
190        found = found->next;
191    }
192
193    return val;
194}
195
196void serf__bucket_headers_remove(serf_bucket_t *bucket, const char *header)
197{
198    headers_context_t *ctx = bucket->data;
199    header_list_t *scan = ctx->list, *prev = NULL;
200
201    /* Find and delete all items with the same header (case insensitive) */
202    while (scan) {
203        if (strcasecmp(scan->header, header) == 0) {
204            if (prev) {
205                prev->next = scan->next;
206            } else {
207                ctx->list = scan->next;
208            }
209            if (ctx->last == scan) {
210                ctx->last = NULL;
211            }
212        } else {
213            prev = scan;
214        }
215        scan = scan->next;
216    }
217}
218
219void serf_bucket_headers_do(
220    serf_bucket_t *headers_bucket,
221    serf_bucket_headers_do_callback_fn_t func,
222    void *baton)
223{
224    headers_context_t *ctx = headers_bucket->data;
225    header_list_t *scan = ctx->list;
226
227    while (scan) {
228        if (func(baton, scan->header, scan->value) != 0) {
229            break;
230        }
231        scan = scan->next;
232    }
233}
234
235static void serf_headers_destroy_and_data(serf_bucket_t *bucket)
236{
237    headers_context_t *ctx = bucket->data;
238    header_list_t *scan = ctx->list;
239
240    while (scan) {
241        header_list_t *next_hdr = scan->next;
242
243        if (scan->alloc_flags & ALLOC_HEADER)
244            serf_bucket_mem_free(bucket->allocator, (void *)scan->header);
245        if (scan->alloc_flags & ALLOC_VALUE)
246            serf_bucket_mem_free(bucket->allocator, (void *)scan->value);
247        serf_bucket_mem_free(bucket->allocator, scan);
248
249        scan = next_hdr;
250    }
251
252    serf_default_destroy_and_data(bucket);
253}
254
255static void select_value(
256    headers_context_t *ctx,
257    const char **value,
258    apr_size_t *len)
259{
260    const char *v;
261    apr_size_t l;
262
263    if (ctx->state == READ_START) {
264        if (ctx->list == NULL) {
265            /* No headers. Move straight to the TERM state. */
266            ctx->state = READ_TERM;
267        }
268        else {
269            ctx->state = READ_HEADER;
270            ctx->cur_read = ctx->list;
271        }
272        ctx->amt_read = 0;
273    }
274
275    switch (ctx->state) {
276    case READ_HEADER:
277        v = ctx->cur_read->header;
278        l = ctx->cur_read->header_size;
279        break;
280    case READ_SEP:
281        v = ": ";
282        l = 2;
283        break;
284    case READ_VALUE:
285        v = ctx->cur_read->value;
286        l = ctx->cur_read->value_size;
287        break;
288    case READ_CRLF:
289    case READ_TERM:
290        v = "\r\n";
291        l = 2;
292        break;
293    case READ_DONE:
294        *len = 0;
295        return;
296    default:
297        /* Not reachable */
298        return;
299    }
300
301    *value = v + ctx->amt_read;
302    *len = l - ctx->amt_read;
303}
304
305/* the current data chunk has been read/consumed. move our internal state. */
306static apr_status_t consume_chunk(headers_context_t *ctx)
307{
308    /* move to the next state, resetting the amount read. */
309    ++ctx->state;
310    ctx->amt_read = 0;
311
312    /* just sent the terminator and moved to DONE. signal completion. */
313    if (ctx->state == READ_DONE)
314        return APR_EOF;
315
316    /* end of this header. move to the next one. */
317    if (ctx->state == READ_TERM) {
318        ctx->cur_read = ctx->cur_read->next;
319        if (ctx->cur_read != NULL) {
320            /* We've got another head to send. Reset the read state. */
321            ctx->state = READ_HEADER;
322        }
323        /* else leave in READ_TERM */
324    }
325
326    /* there is more data which can be read immediately. */
327    return APR_SUCCESS;
328}
329
330static apr_status_t serf_headers_peek(serf_bucket_t *bucket,
331                                      const char **data,
332                                      apr_size_t *len)
333{
334    headers_context_t *ctx = bucket->data;
335
336    select_value(ctx, data, len);
337
338    /* already done or returning the CRLF terminator? return EOF */
339    if (ctx->state == READ_DONE || ctx->state == READ_TERM)
340        return APR_EOF;
341
342    return APR_SUCCESS;
343}
344
345static apr_status_t serf_headers_read(serf_bucket_t *bucket,
346                                      apr_size_t requested,
347                                      const char **data, apr_size_t *len)
348{
349    headers_context_t *ctx = bucket->data;
350    apr_size_t avail;
351
352    select_value(ctx, data, &avail);
353    if (ctx->state == READ_DONE) {
354        *len = avail;
355        return APR_EOF;
356    }
357
358    if (requested >= avail) {
359        /* return everything from this chunk */
360        *len = avail;
361
362        /* we consumed this chunk. advance the state. */
363        return consume_chunk(ctx);
364    }
365
366    /* return just the amount requested, and advance our pointer */
367    *len = requested;
368    ctx->amt_read += requested;
369
370    /* there is more that can be read immediately */
371    return APR_SUCCESS;
372}
373
374static apr_status_t serf_headers_readline(serf_bucket_t *bucket,
375                                          int acceptable, int *found,
376                                          const char **data, apr_size_t *len)
377{
378    headers_context_t *ctx = bucket->data;
379    apr_status_t status;
380
381    /* ### what behavior should we use here? APR_EGENERAL for now */
382    if ((acceptable & SERF_NEWLINE_CRLF) == 0)
383        return APR_EGENERAL;
384
385    /* get whatever is in this chunk */
386    select_value(ctx, data, len);
387    if (ctx->state == READ_DONE)
388        return APR_EOF;
389
390    /* we consumed this chunk. advance the state. */
391    status = consume_chunk(ctx);
392
393    /* the type of newline found is easy... */
394    *found = (ctx->state == READ_CRLF || ctx->state == READ_TERM)
395        ? SERF_NEWLINE_CRLF : SERF_NEWLINE_NONE;
396
397    return status;
398}
399
400static apr_status_t serf_headers_read_iovec(serf_bucket_t *bucket,
401                                            apr_size_t requested,
402                                            int vecs_size,
403                                            struct iovec *vecs,
404                                            int *vecs_used)
405{
406    apr_size_t avail = requested;
407    int i;
408
409    *vecs_used = 0;
410
411    for (i = 0; i < vecs_size; i++) {
412        const char *data;
413        apr_size_t len;
414        apr_status_t status;
415
416        /* Calling read() would not be a safe opt in the general case, but it
417         * is here for the header bucket as it only frees all of the header
418         * keys and values when the entire bucket goes away - not on a
419         * per-read() basis as is normally the case.
420         */
421        status = serf_headers_read(bucket, avail, &data, &len);
422
423        if (len) {
424            vecs[*vecs_used].iov_base = (char*)data;
425            vecs[*vecs_used].iov_len = len;
426
427            (*vecs_used)++;
428
429            if (avail != SERF_READ_ALL_AVAIL) {
430                avail -= len;
431
432                /* If we reach 0, then read()'s status will suffice.  */
433                if (avail == 0) {
434                    return status;
435                }
436            }
437        }
438
439        if (status) {
440            return status;
441        }
442    }
443
444    return APR_SUCCESS;
445}
446
447const serf_bucket_type_t serf_bucket_type_headers = {
448    "HEADERS",
449    serf_headers_read,
450    serf_headers_readline,
451    serf_headers_read_iovec,
452    serf_default_read_for_sendfile,
453    serf_default_read_bucket,
454    serf_headers_peek,
455    serf_headers_destroy_and_data,
456};
457