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