~ubuntu-branches/ubuntu/feisty/apache2/feisty

« back to all changes in this revision

Viewing changes to modules/cache/cache_util.c

  • Committer: Bazaar Package Importer
  • Author(s): Andreas Barth
  • Date: 2006-12-09 21:05:45 UTC
  • mfrom: (0.6.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061209210545-h70s0xaqc2v8vqr2
Tags: 2.2.3-3.2
* Non-maintainer upload.
* 043_ajp_connection_reuse: Patch from upstream Bugzilla, fixing a critical
  issue with regard to connection reuse in mod_proxy_ajp.
  Closes: #396265

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Licensed to the Apache Software Foundation (ASF) under one or more
 
2
 * contributor license agreements.  See the NOTICE file distributed with
 
3
 * this work for additional information regarding copyright ownership.
 
4
 * The ASF licenses this file to You under the Apache License, Version 2.0
 
5
 * (the "License"); you may not use this file except in compliance with
 
6
 * the License.  You may obtain a copy of the License at
 
7
 *
 
8
 *     http://www.apache.org/licenses/LICENSE-2.0
 
9
 *
 
10
 * Unless required by applicable law or agreed to in writing, software
 
11
 * distributed under the License is distributed on an "AS IS" BASIS,
 
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
13
 * See the License for the specific language governing permissions and
 
14
 * limitations under the License.
 
15
 */
 
16
 
 
17
#define CORE_PRIVATE
 
18
 
 
19
#include "mod_cache.h"
 
20
 
 
21
#include <ap_provider.h>
 
22
 
 
23
/* -------------------------------------------------------------- */
 
24
 
 
25
extern module AP_MODULE_DECLARE_DATA cache_module;
 
26
 
 
27
/* Determine if "url" matches the hostname, scheme and port and path
 
28
 * in "filter". All but the path comparisons are case-insensitive.
 
29
 */
 
30
static int uri_meets_conditions(apr_uri_t filter, int pathlen, apr_uri_t url)
 
31
{
 
32
    /* Compare the hostnames */
 
33
    if(filter.hostname) {
 
34
        if (!url.hostname) {
 
35
            return 0;
 
36
        }
 
37
        else if (strcasecmp(filter.hostname, url.hostname)) {
 
38
            return 0;
 
39
        }
 
40
    }
 
41
 
 
42
    /* Compare the schemes */
 
43
    if(filter.scheme) {
 
44
        if (!url.scheme) {
 
45
            return 0;
 
46
        }
 
47
        else if (strcasecmp(filter.scheme, url.scheme)) {
 
48
            return 0;
 
49
        }
 
50
    }
 
51
 
 
52
    /* Compare the ports */
 
53
    if(filter.port_str) {
 
54
        if (url.port_str && filter.port != url.port) {
 
55
            return 0;
 
56
        }
 
57
        /* NOTE:  ap_port_of_scheme will return 0 if given NULL input */
 
58
        else if (filter.port != apr_uri_port_of_scheme(url.scheme)) {
 
59
            return 0;
 
60
        }
 
61
    }
 
62
    else if(url.port_str && filter.scheme) {
 
63
        if (apr_uri_port_of_scheme(filter.scheme) == url.port) {
 
64
            return 0;
 
65
        }
 
66
    }
 
67
 
 
68
    /* Url has met all of the filter conditions so far, determine
 
69
     * if the paths match.
 
70
     */
 
71
    return !strncmp(filter.path, url.path, pathlen);
 
72
}
 
73
 
 
74
CACHE_DECLARE(cache_provider_list *)ap_cache_get_providers(request_rec *r,
 
75
                                                  cache_server_conf *conf,
 
76
                                                  apr_uri_t uri)
 
77
{
 
78
    cache_provider_list *providers = NULL;
 
79
    int i;
 
80
 
 
81
    /* loop through all the cacheenable entries */
 
82
    for (i = 0; i < conf->cacheenable->nelts; i++) {
 
83
        struct cache_enable *ent =
 
84
                                (struct cache_enable *)conf->cacheenable->elts;
 
85
        if (uri_meets_conditions(ent[i].url, ent[i].pathlen, uri)) {
 
86
            /* Fetch from global config and add to the list. */
 
87
            cache_provider *provider;
 
88
            provider = ap_lookup_provider(CACHE_PROVIDER_GROUP, ent[i].type,
 
89
                                          "0");
 
90
            if (!provider) {
 
91
                /* Log an error! */
 
92
            }
 
93
            else {
 
94
                cache_provider_list *newp;
 
95
                newp = apr_pcalloc(r->pool, sizeof(cache_provider_list));
 
96
                newp->provider_name = ent[i].type;
 
97
                newp->provider = provider;
 
98
 
 
99
                if (!providers) {
 
100
                    providers = newp;
 
101
                }
 
102
                else {
 
103
                    cache_provider_list *last = providers;
 
104
 
 
105
                    while (last->next) {
 
106
                        last = last->next;
 
107
                    }
 
108
                    last->next = newp;
 
109
                }
 
110
            }
 
111
        }
 
112
    }
 
113
 
 
114
    /* then loop through all the cachedisable entries
 
115
     * Looking for urls that contain the full cachedisable url and possibly
 
116
     * more.
 
117
     * This means we are disabling cachedisable url and below...
 
118
     */
 
119
    for (i = 0; i < conf->cachedisable->nelts; i++) {
 
120
        struct cache_disable *ent =
 
121
                               (struct cache_disable *)conf->cachedisable->elts;
 
122
        if (uri_meets_conditions(ent[i].url, ent[i].pathlen, uri)) {
 
123
            /* Stop searching now. */
 
124
            return NULL;
 
125
        }
 
126
    }
 
127
 
 
128
    return providers;
 
129
}
 
130
 
 
131
 
 
132
/* do a HTTP/1.1 age calculation */
 
133
CACHE_DECLARE(apr_int64_t) ap_cache_current_age(cache_info *info,
 
134
                                                const apr_time_t age_value,
 
135
                                                apr_time_t now)
 
136
{
 
137
    apr_time_t apparent_age, corrected_received_age, response_delay,
 
138
               corrected_initial_age, resident_time, current_age,
 
139
               age_value_usec;
 
140
 
 
141
    age_value_usec = apr_time_from_sec(age_value);
 
142
 
 
143
    /* Perform an HTTP/1.1 age calculation. (RFC2616 13.2.3) */
 
144
 
 
145
    apparent_age = MAX(0, info->response_time - info->date);
 
146
    corrected_received_age = MAX(apparent_age, age_value_usec);
 
147
    response_delay = info->response_time - info->request_time;
 
148
    corrected_initial_age = corrected_received_age + response_delay;
 
149
    resident_time = now - info->response_time;
 
150
    current_age = corrected_initial_age + resident_time;
 
151
 
 
152
    return apr_time_sec(current_age);
 
153
}
 
154
 
 
155
CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h,
 
156
                                            request_rec *r)
 
157
{
 
158
    apr_int64_t age, maxage_req, maxage_cresp, maxage, smaxage, maxstale;
 
159
    apr_int64_t minfresh;
 
160
    const char *cc_cresp, *cc_req;
 
161
    const char *pragma;
 
162
    const char *agestr = NULL;
 
163
    const char *expstr = NULL;
 
164
    char *val;
 
165
    apr_time_t age_c = 0;
 
166
    cache_info *info = &(h->cache_obj->info);
 
167
    cache_server_conf *conf =
 
168
      (cache_server_conf *)ap_get_module_config(r->server->module_config,
 
169
                                                &cache_module);
 
170
 
 
171
    /*
 
172
     * We now want to check if our cached data is still fresh. This depends
 
173
     * on a few things, in this order:
 
174
     *
 
175
     * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache. no-cache in
 
176
     * either the request or the cached response means that we must
 
177
     * revalidate the request unconditionally, overriding any expiration
 
178
     * mechanism. It's equivalent to max-age=0,must-revalidate.
 
179
     *
 
180
     * - RFC2616 14.32 Pragma: no-cache This is treated the same as
 
181
     * Cache-Control: no-cache.
 
182
     *
 
183
     * - RFC2616 14.9.3 Cache-Control: max-stale, must-revalidate,
 
184
     * proxy-revalidate if the max-stale request header exists, modify the
 
185
     * stale calculations below so that an object can be at most <max-stale>
 
186
     * seconds stale before we request a revalidation, _UNLESS_ a
 
187
     * must-revalidate or proxy-revalidate cached response header exists to
 
188
     * stop us doing this.
 
189
     *
 
190
     * - RFC2616 14.9.3 Cache-Control: s-maxage the origin server specifies the
 
191
     * maximum age an object can be before it is considered stale. This
 
192
     * directive has the effect of proxy|must revalidate, which in turn means
 
193
     * simple ignore any max-stale setting.
 
194
     *
 
195
     * - RFC2616 14.9.4 Cache-Control: max-age this header can appear in both
 
196
     * requests and responses. If both are specified, the smaller of the two
 
197
     * takes priority.
 
198
     *
 
199
     * - RFC2616 14.21 Expires: if this request header exists in the cached
 
200
     * entity, and it's value is in the past, it has expired.
 
201
     *
 
202
     */
 
203
 
 
204
    /* This value comes from the client's initial request. */
 
205
    cc_req = apr_table_get(r->headers_in, "Cache-Control");
 
206
    pragma = apr_table_get(r->headers_in, "Pragma");
 
207
 
 
208
    if (ap_cache_liststr(NULL, pragma, "no-cache", NULL)
 
209
        || ap_cache_liststr(NULL, cc_req, "no-cache", NULL)) {
 
210
 
 
211
        if (!conf->ignorecachecontrol) {
 
212
            /* Treat as stale, causing revalidation */
 
213
            return 0;
 
214
        }
 
215
 
 
216
        ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
 
217
                     "Incoming request is asking for a uncached version of "
 
218
                     "%s, but we know better and are ignoring it",
 
219
                     r->unparsed_uri);
 
220
    }
 
221
 
 
222
    /* These come from the cached entity. */
 
223
    cc_cresp = apr_table_get(h->resp_hdrs, "Cache-Control");
 
224
    expstr = apr_table_get(h->resp_hdrs, "Expires");
 
225
 
 
226
    if ((agestr = apr_table_get(h->resp_hdrs, "Age"))) {
 
227
        age_c = apr_atoi64(agestr);
 
228
    }
 
229
 
 
230
    /* calculate age of object */
 
231
    age = ap_cache_current_age(info, age_c, r->request_time);
 
232
 
 
233
    /* extract s-maxage */
 
234
    if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "s-maxage", &val)) {
 
235
        smaxage = apr_atoi64(val);
 
236
    }
 
237
    else {
 
238
        smaxage = -1;
 
239
    }
 
240
 
 
241
    /* extract max-age from request */
 
242
    if (!conf->ignorecachecontrol
 
243
        && cc_req && ap_cache_liststr(r->pool, cc_req, "max-age", &val)) {
 
244
        maxage_req = apr_atoi64(val);
 
245
    }
 
246
    else {
 
247
        maxage_req = -1;
 
248
    }
 
249
 
 
250
    /* extract max-age from response */
 
251
    if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "max-age", &val)) {
 
252
        maxage_cresp = apr_atoi64(val);
 
253
    }
 
254
    else {
 
255
        maxage_cresp = -1;
 
256
    }
 
257
 
 
258
    /*
 
259
     * if both maxage request and response, the smaller one takes priority
 
260
     */
 
261
    if (maxage_req == -1) {
 
262
        maxage = maxage_cresp;
 
263
    }
 
264
    else if (maxage_cresp == -1) {
 
265
        maxage = maxage_req;
 
266
    }
 
267
    else {
 
268
        maxage = MIN(maxage_req, maxage_cresp);
 
269
    }
 
270
 
 
271
    /* extract max-stale */
 
272
    if (cc_req && ap_cache_liststr(r->pool, cc_req, "max-stale", &val)) {
 
273
        maxstale = apr_atoi64(val);
 
274
    }
 
275
    else {
 
276
        maxstale = 0;
 
277
    }
 
278
 
 
279
    /* extract min-fresh */
 
280
    if (!conf->ignorecachecontrol
 
281
        && cc_req && ap_cache_liststr(r->pool, cc_req, "min-fresh", &val)) {
 
282
        minfresh = apr_atoi64(val);
 
283
    }
 
284
    else {
 
285
        minfresh = 0;
 
286
    }
 
287
 
 
288
    /* override maxstale if must-revalidate or proxy-revalidate */
 
289
    if (maxstale && ((cc_cresp &&
 
290
                      ap_cache_liststr(NULL, cc_cresp,
 
291
                                       "must-revalidate", NULL)) ||
 
292
                     (cc_cresp &&
 
293
                      ap_cache_liststr(NULL, cc_cresp,
 
294
                                       "proxy-revalidate", NULL)))) {
 
295
        maxstale = 0;
 
296
    }
 
297
 
 
298
    /* handle expiration */
 
299
    if (((smaxage != -1) && (age < (smaxage - minfresh))) ||
 
300
        ((maxage != -1) && (age < (maxage + maxstale - minfresh))) ||
 
301
        ((smaxage == -1) && (maxage == -1) &&
 
302
         (info->expire != APR_DATE_BAD) &&
 
303
         (age < (apr_time_sec(info->expire - info->date) + maxstale - minfresh)))) {
 
304
        const char *warn_head;
 
305
 
 
306
        warn_head = apr_table_get(h->resp_hdrs, "Warning");
 
307
 
 
308
        /* it's fresh darlings... */
 
309
        /* set age header on response */
 
310
        apr_table_set(h->resp_hdrs, "Age",
 
311
                      apr_psprintf(r->pool, "%lu", (unsigned long)age));
 
312
 
 
313
        /* add warning if maxstale overrode freshness calculation */
 
314
        if (!(((smaxage != -1) && age < smaxage) ||
 
315
              ((maxage != -1) && age < maxage) ||
 
316
              (info->expire != APR_DATE_BAD &&
 
317
               (info->expire - info->date) > age))) {
 
318
            /* make sure we don't stomp on a previous warning */
 
319
            if ((warn_head == NULL) ||
 
320
                ((warn_head != NULL) && (ap_strstr_c(warn_head, "110") == NULL))) {
 
321
                apr_table_merge(h->resp_hdrs, "Warning",
 
322
                                "110 Response is stale");
 
323
            }
 
324
        }
 
325
        /*
 
326
         * If none of Expires, Cache-Control: max-age, or Cache-Control:
 
327
         * s-maxage appears in the response, and the respose header age
 
328
         * calculated is more than 24 hours add the warning 113
 
329
         */
 
330
        if ((maxage_cresp == -1) && (smaxage == -1) &&
 
331
            (expstr == NULL) && (age > 86400)) {
 
332
 
 
333
            /* Make sure we don't stomp on a previous warning, and don't dup
 
334
             * a 113 marning that is already present. Also, make sure to add
 
335
             * the new warning to the correct *headers_out location.
 
336
             */
 
337
            if ((warn_head == NULL) ||
 
338
                ((warn_head != NULL) && (ap_strstr_c(warn_head, "113") == NULL))) {
 
339
                apr_table_merge(h->resp_hdrs, "Warning",
 
340
                                "113 Heuristic expiration");
 
341
            }
 
342
        }
 
343
        return 1;    /* Cache object is fresh (enough) */
 
344
    }
 
345
 
 
346
    return 0;        /* Cache object is stale */
 
347
}
 
348
 
 
349
/*
 
350
 * list is a comma-separated list of case-insensitive tokens, with
 
351
 * optional whitespace around the tokens.
 
352
 * The return returns 1 if the token val is found in the list, or 0
 
353
 * otherwise.
 
354
 */
 
355
CACHE_DECLARE(int) ap_cache_liststr(apr_pool_t *p, const char *list,
 
356
                                    const char *key, char **val)
 
357
{
 
358
    apr_size_t key_len;
 
359
    const char *next;
 
360
 
 
361
    if (!list) {
 
362
        return 0;
 
363
    }
 
364
 
 
365
    key_len = strlen(key);
 
366
    next = list;
 
367
 
 
368
    for (;;) {
 
369
 
 
370
        /* skip whitespace and commas to find the start of the next key */
 
371
        while (*next && (apr_isspace(*next) || (*next == ','))) {
 
372
            next++;
 
373
        }
 
374
 
 
375
        if (!*next) {
 
376
            return 0;
 
377
        }
 
378
 
 
379
        if (!strncasecmp(next, key, key_len)) {
 
380
            /* this field matches the key (though it might just be
 
381
             * a prefix match, so make sure the match is followed
 
382
             * by either a space or an equals sign)
 
383
             */
 
384
            next += key_len;
 
385
            if (!*next || (*next == '=') || apr_isspace(*next) ||
 
386
                (*next == ',')) {
 
387
                /* valid match */
 
388
                if (val) {
 
389
                    while (*next && (*next != '=') && (*next != ',')) {
 
390
                        next++;
 
391
                    }
 
392
                    if (*next == '=') {
 
393
                        next++;
 
394
                        while (*next && apr_isspace(*next )) {
 
395
                            next++;
 
396
                        }
 
397
                        if (!*next) {
 
398
                            *val = NULL;
 
399
                        }
 
400
                        else {
 
401
                            const char *val_start = next;
 
402
                            while (*next && !apr_isspace(*next) &&
 
403
                                   (*next != ',')) {
 
404
                                next++;
 
405
                            }
 
406
                            *val = apr_pstrmemdup(p, val_start,
 
407
                                                  next - val_start);
 
408
                        }
 
409
                    }
 
410
                }
 
411
                return 1;
 
412
            }
 
413
        }
 
414
 
 
415
        /* skip to the next field */
 
416
        do {
 
417
            next++;
 
418
            if (!*next) {
 
419
                return 0;
 
420
            }
 
421
        } while (*next != ',');
 
422
    }
 
423
}
 
424
 
 
425
/* return each comma separated token, one at a time */
 
426
CACHE_DECLARE(const char *)ap_cache_tokstr(apr_pool_t *p, const char *list,
 
427
                                           const char **str)
 
428
{
 
429
    apr_size_t i;
 
430
    const char *s;
 
431
 
 
432
    s = ap_strchr_c(list, ',');
 
433
    if (s != NULL) {
 
434
        i = s - list;
 
435
        do
 
436
            s++;
 
437
        while (apr_isspace(*s))
 
438
            ; /* noop */
 
439
    }
 
440
    else
 
441
        i = strlen(list);
 
442
 
 
443
    while (i > 0 && apr_isspace(list[i - 1]))
 
444
        i--;
 
445
 
 
446
    *str = s;
 
447
    if (i)
 
448
        return apr_pstrndup(p, list, i);
 
449
    else
 
450
        return NULL;
 
451
}
 
452
 
 
453
/*
 
454
 * Converts apr_time_t expressed as hex digits to
 
455
 * a true apr_time_t.
 
456
 */
 
457
CACHE_DECLARE(apr_time_t) ap_cache_hex2usec(const char *x)
 
458
{
 
459
    int i, ch;
 
460
    apr_time_t j;
 
461
    for (i = 0, j = 0; i < sizeof(j) * 2; i++) {
 
462
        ch = x[i];
 
463
        j <<= 4;
 
464
        if (apr_isdigit(ch))
 
465
            j |= ch - '0';
 
466
        else if (apr_isupper(ch))
 
467
            j |= ch - ('A' - 10);
 
468
        else
 
469
            j |= ch - ('a' - 10);
 
470
    }
 
471
    return j;
 
472
}
 
473
 
 
474
/*
 
475
 * Converts apr_time_t to apr_time_t expressed as hex digits.
 
476
 */
 
477
CACHE_DECLARE(void) ap_cache_usec2hex(apr_time_t j, char *y)
 
478
{
 
479
    int i, ch;
 
480
 
 
481
    for (i = (sizeof(j) * 2)-1; i >= 0; i--) {
 
482
        ch = (int)(j & 0xF);
 
483
        j >>= 4;
 
484
        if (ch >= 10)
 
485
            y[i] = ch + ('A' - 10);
 
486
        else
 
487
            y[i] = ch + '0';
 
488
    }
 
489
    y[sizeof(j) * 2] = '\0';
 
490
}
 
491
 
 
492
static void cache_hash(const char *it, char *val, int ndepth, int nlength)
 
493
{
 
494
    apr_md5_ctx_t context;
 
495
    unsigned char digest[16];
 
496
    char tmp[22];
 
497
    int i, k, d;
 
498
    unsigned int x;
 
499
    static const char enc_table[64] =
 
500
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@";
 
501
 
 
502
    apr_md5_init(&context);
 
503
    apr_md5_update(&context, (const unsigned char *) it, strlen(it));
 
504
    apr_md5_final(digest, &context);
 
505
 
 
506
    /* encode 128 bits as 22 characters, using a modified uuencoding
 
507
     * the encoding is 3 bytes -> 4 characters* i.e. 128 bits is
 
508
     * 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters
 
509
     */
 
510
    for (i = 0, k = 0; i < 15; i += 3) {
 
511
        x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2];
 
512
        tmp[k++] = enc_table[x >> 18];
 
513
        tmp[k++] = enc_table[(x >> 12) & 0x3f];
 
514
        tmp[k++] = enc_table[(x >> 6) & 0x3f];
 
515
        tmp[k++] = enc_table[x & 0x3f];
 
516
    }
 
517
 
 
518
    /* one byte left */
 
519
    x = digest[15];
 
520
    tmp[k++] = enc_table[x >> 2];    /* use up 6 bits */
 
521
    tmp[k++] = enc_table[(x << 4) & 0x3f];
 
522
 
 
523
    /* now split into directory levels */
 
524
    for (i = k = d = 0; d < ndepth; ++d) {
 
525
        memcpy(&val[i], &tmp[k], nlength);
 
526
        k += nlength;
 
527
        val[i + nlength] = '/';
 
528
        i += nlength + 1;
 
529
    }
 
530
    memcpy(&val[i], &tmp[k], 22 - k);
 
531
    val[i + 22 - k] = '\0';
 
532
}
 
533
 
 
534
CACHE_DECLARE(char *)ap_cache_generate_name(apr_pool_t *p, int dirlevels,
 
535
                                            int dirlength, const char *name)
 
536
{
 
537
    char hashfile[66];
 
538
    cache_hash(name, hashfile, dirlevels, dirlength);
 
539
    return apr_pstrdup(p, hashfile);
 
540
}
 
541
 
 
542
/* Create a new table consisting of those elements from an input
 
543
 * headers table that are allowed to be stored in a cache.
 
544
 */
 
545
CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_hdrs_out(apr_pool_t *pool,
 
546
                                                        apr_table_t *t,
 
547
                                                        server_rec *s)
 
548
{
 
549
    cache_server_conf *conf;
 
550
    char **header;
 
551
    int i;
 
552
 
 
553
    /* Make a copy of the headers, and remove from
 
554
     * the copy any hop-by-hop headers, as defined in Section
 
555
     * 13.5.1 of RFC 2616
 
556
     */
 
557
    apr_table_t *headers_out;
 
558
    headers_out = apr_table_copy(pool, t);
 
559
    apr_table_unset(headers_out, "Connection");
 
560
    apr_table_unset(headers_out, "Keep-Alive");
 
561
    apr_table_unset(headers_out, "Proxy-Authenticate");
 
562
    apr_table_unset(headers_out, "Proxy-Authorization");
 
563
    apr_table_unset(headers_out, "TE");
 
564
    apr_table_unset(headers_out, "Trailers");
 
565
    apr_table_unset(headers_out, "Transfer-Encoding");
 
566
    apr_table_unset(headers_out, "Upgrade");
 
567
 
 
568
    conf = (cache_server_conf *)ap_get_module_config(s->module_config,
 
569
                                                     &cache_module);
 
570
    /* Remove the user defined headers set with CacheIgnoreHeaders.
 
571
     * This may break RFC 2616 compliance on behalf of the administrator.
 
572
     */
 
573
    header = (char **)conf->ignore_headers->elts;
 
574
    for (i = 0; i < conf->ignore_headers->nelts; i++) {
 
575
        apr_table_unset(headers_out, header[i]);
 
576
    }
 
577
    return headers_out;
 
578
}