blob: c3027f948498d524bcfdbed64a8e289ff719ce8b [file] [log] [blame]
Christopher Faulet47596d32018-10-22 09:17:28 +02001/*
2 * Functions to manipulate HTTP messages using the internal representation.
3 *
4 * Copyright (C) 2018 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <common/config.h>
14#include <common/http.h>
15
16#include <proto/http_htx.h>
17#include <proto/htx.h>
18
19/* Finds the start line in the HTX message stopping at the first
20 * end-of-message. It returns an empty start line when not found, otherwise, it
21 * returns the corresponding <struct h1_sl>.
22 */
23union h1_sl http_find_stline(const struct htx *htx)
24{
25 union htx_sl *htx_sl;
26 union h1_sl sl;
27 int32_t pos;
28
29 for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
30 struct htx_blk *blk = htx_get_blk(htx, pos);
31 enum htx_blk_type type = htx_get_blk_type(blk);
32
33 if (type == HTX_BLK_REQ_SL) {
34 htx_sl = htx_get_blk_ptr(htx, blk);
35 sl.rq.meth = htx_sl->rq.meth;
36 sl.rq.m = ist2(htx_sl->rq.l, htx_sl->rq.m_len);
37 sl.rq.u = ist2(htx_sl->rq.l + htx_sl->rq.m_len, htx_sl->rq.u_len);
38 sl.rq.v = ist2(htx_sl->rq.l + htx_sl->rq.m_len + htx_sl->rq.u_len, htx_sl->rq.v_len);
39 return sl;
40 }
41
42 if (type == HTX_BLK_RES_SL) {
43 htx_sl = htx_get_blk_ptr(htx, blk);
44 sl.st.status = htx_sl->st.status;
45 sl.st.v = ist2(htx_sl->st.l, htx_sl->st.v_len);
46 sl.st.c = ist2(htx_sl->st.l + htx_sl->st.v_len, htx_sl->st.c_len);
47 sl.st.r = ist2(htx_sl->st.l + htx_sl->st.v_len + htx_sl->st.c_len, htx_sl->st.r_len);
48 return sl;
49 }
Christopher Faulet573fe732018-11-28 16:55:12 +010050
51 if (type == HTX_BLK_EOH || type == HTX_BLK_EOM)
Christopher Faulet47596d32018-10-22 09:17:28 +020052 break;
53 }
54
55 sl.rq.m = ist("");
56 sl.rq.u = ist("");
57 sl.rq.v = ist("");
58 return sl;
59}
60
61/* Finds the first or next occurrence of header <name> in the HTX message <htx>
62 * using the context <ctx>. This structure holds everything necessary to use the
63 * header and find next occurrence. If its <blk> member is NULL, the header is
64 * searched from the beginning. Otherwise, the next occurrence is returned. The
65 * function returns 1 when it finds a value, and 0 when there is no more. It is
66 * designed to work with headers defined as comma-separated lists. If <full> is
67 * set, it works on full-line headers in whose comma is not a delimiter but is
68 * part of the syntax. A special case, if ctx->value is NULL when searching for
69 * a new values of a header, the current header is rescanned. This allows
70 * rescanning after a header deletion.
71 */
72int http_find_header(const struct htx *htx, const struct ist name,
73 struct http_hdr_ctx *ctx, int full)
74{
75 struct htx_blk *blk = ctx->blk;
76 struct ist n, v;
77 enum htx_blk_type type;
78 uint32_t pos;
79
80 if (blk) {
81 char *p;
82
83 pos = htx_get_blk_pos(htx, blk);
84 if (!ctx->value.ptr)
85 goto rescan_hdr;
86 if (full)
87 goto next_blk;
88 v = htx_get_blk_value(htx, blk);
89 p = ctx->value.ptr + ctx->value.len + ctx->lws_after;
90 v.len -= (p - v.ptr);
91 v.ptr = p;
92 if (!v.len)
93 goto next_blk;
94 /* Skip comma */
95 if (*(v.ptr) == ',') {
96 v.ptr++;
97 v.len--;
98 }
99
100 goto return_hdr;
101 }
102
103 if (!htx->used)
104 return 0;
105
106 pos = htx_get_head(htx);
107 while (1) {
108 rescan_hdr:
109 blk = htx_get_blk(htx, pos);
110 type = htx_get_blk_type(blk);
Christopher Faulet573fe732018-11-28 16:55:12 +0100111 if (type == HTX_BLK_EOH || type == HTX_BLK_EOM)
112 break;
Christopher Faulet47596d32018-10-22 09:17:28 +0200113 if (type != HTX_BLK_HDR)
114 goto next_blk;
115 if (name.len) {
116 /* If no name was passed, we want any header. So skip the comparison */
117 n = htx_get_blk_name(htx, blk);
118 if (!isteqi(n, name))
119 goto next_blk;
120 }
121 v = htx_get_blk_value(htx, blk);
122
123 return_hdr:
124 ctx->lws_before = 0;
125 ctx->lws_after = 0;
126 while (v.len && HTTP_IS_LWS(*v.ptr)) {
127 v.ptr++;
128 v.len--;
129 ctx->lws_before++;
130 }
131 if (!full)
132 v.len = http_find_hdr_value_end(v.ptr, v.ptr + v.len) - v.ptr;
133 while (v.len && HTTP_IS_LWS(*(v.ptr + v.len - 1))) {
134 v.len--;
135 ctx->lws_after++;
136 }
137 if (!v.len)
138 goto next_blk;
139 ctx->blk = blk;
140 ctx->value = v;
141 return 1;
142
143 next_blk:
144 if (pos == htx->tail)
145 break;
146 pos++;
147 if (pos >= htx->wrap)
148 pos = 0;
149 }
150
151 ctx->blk = NULL;
152 ctx->value = ist("");
153 ctx->lws_before = ctx->lws_after = 0;
154 return 0;
155}
156
157/* Adds a header block int the HTX message <htx>, just before the EOH block. It
158 * returns 1 on success, otherwise it returns 0.
159 */
160int http_add_header(struct htx *htx, const struct ist n, const struct ist v)
161{
162 struct htx_blk *blk;
163 enum htx_blk_type type = htx_get_tail_type(htx);
164 int32_t prev;
165
166 blk = htx_add_header(htx, n, v);
167 if (!blk)
168 return 0;
169
170 if (unlikely(type < HTX_BLK_EOH))
171 return 1;
172
173 /* <blk> is the head, swap it iteratively with its predecessor to place
174 * it just before the end-of-header block. So blocks remains ordered. */
175 for (prev = htx_get_prev(htx, htx->tail); prev != -1; prev = htx_get_prev(htx, prev)) {
176 struct htx_blk *pblk = htx_get_blk(htx, prev);
177 enum htx_blk_type type = htx_get_blk_type(pblk);
178
179 /* Swap .addr and .info fields */
180 blk->addr ^= pblk->addr; pblk->addr ^= blk->addr; blk->addr ^= pblk->addr;
181 blk->info ^= pblk->info; pblk->info ^= blk->info; blk->info ^= pblk->info;
182
183 if (blk->addr == pblk->addr)
184 blk->addr += htx_get_blksz(pblk);
185 htx->front = prev;
186
187 /* Stop when end-of-header is reached */
188 if (type == HTX_BLK_EOH)
189 break;
190
191 blk = pblk;
192 }
193 return 1;
194}
195
196/* Replaces the request start line of the HTX message <htx> by <sl>. It returns
197 * 1 on success, otherwise it returns 0. The start line must be found in the
198 * message.
199 */
200int http_replace_reqline(struct htx *htx, const union h1_sl sl)
201{
202 int32_t pos;
203
204 for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
205 struct htx_blk *blk = htx_get_blk(htx, pos);
206 enum htx_blk_type type = htx_get_blk_type(blk);
207
208 if (type == HTX_BLK_REQ_SL) {
209 blk = htx_replace_reqline(htx, blk, sl);
210 if (!blk)
211 return 0;
212 return 1;
213 }
214 if (type == HTX_BLK_EOM)
215 break;
216 }
217
218 return 0;
219}
220
221
222/* Replaces the response start line of the HTX message <htx> by <sl>. It returns
223 * 1 on success, otherwise it returns 0. The start line must be found in the
224 * message.
225 */
226int http_replace_resline(struct htx *htx, const union h1_sl sl)
227{
228 int32_t pos;
229
230 for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
231 struct htx_blk *blk = htx_get_blk(htx, pos);
232 enum htx_blk_type type = htx_get_blk_type(blk);
233
234 if (type == HTX_BLK_RES_SL) {
235 blk = htx_replace_resline(htx, blk, sl);
236 if (!blk)
237 return 0;
238 return 1;
239 }
240 if (type == HTX_BLK_EOM)
241 break;
242 }
243
244 return 0;
245}
246
Christopher Faulete010c802018-10-24 10:36:45 +0200247/* Replace the request method in the HTX message <htx> by <meth>. It returns 1
248 * on success, otherwise 0.
249 */
250int http_replace_req_meth(struct htx *htx, const struct ist meth)
251{
252 struct buffer *temp = get_trash_chunk();
253 union h1_sl sl = http_find_stline(htx);
254 union h1_sl new_sl;
255
256 /* Start by copying old uri and version */
257 chunk_memcat(temp, sl.rq.u.ptr, sl.rq.u.len); /* uri */
258 chunk_memcat(temp, sl.rq.v.ptr, sl.rq.v.len); /* vsn */
259
260 /* create the new start line */
261 new_sl.rq.meth = find_http_meth(meth.ptr, meth.len);
262 new_sl.rq.m = meth;
263 new_sl.rq.u = ist2(temp->area, sl.rq.u.len);
264 new_sl.rq.v = ist2(temp->area + sl.rq.u.len, sl.rq.v.len);
265
266 return http_replace_reqline(htx, new_sl);
267}
268
269/* Replace the request uri in the HTX message <htx> by <uri>. It returns 1 on
270 * success, otherwise 0.
271 */
272int http_replace_req_uri(struct htx *htx, const struct ist uri)
273{
274 struct buffer *temp = get_trash_chunk();
275 union h1_sl sl = http_find_stline(htx);
276 union h1_sl new_sl;
277
278 /* Start by copying old method and version */
279 chunk_memcat(temp, sl.rq.m.ptr, sl.rq.m.len); /* meth */
280 chunk_memcat(temp, sl.rq.v.ptr, sl.rq.v.len); /* vsn */
281
282 /* create the new start line */
283 new_sl.rq.meth = sl.rq.meth;
284 new_sl.rq.m = ist2(temp->area, sl.rq.m.len);
285 new_sl.rq.u = uri;
286 new_sl.rq.v = ist2(temp->area + sl.rq.m.len, sl.rq.v.len);
287
288 return http_replace_reqline(htx, new_sl);
289}
290
291/* Replace the request path in the HTX message <htx> by <path>. The host part
292 * and the query string are preserved. It returns 1 on success, otherwise 0.
293 */
294int http_replace_req_path(struct htx *htx, const struct ist path)
295{
296 struct buffer *temp = get_trash_chunk();
297 union h1_sl sl = http_find_stline(htx);
298 union h1_sl new_sl;
299 struct ist p, uri;
300 size_t plen = 0;
301
302 p = http_get_path(sl.rq.u);
303 if (!p.ptr)
304 p = sl.rq.u;
305 while (plen < p.len && *(p.ptr + plen) != '?')
306 plen++;
307
308 /* Start by copying old method and version and create the new uri */
309 chunk_memcat(temp, sl.rq.m.ptr, sl.rq.m.len); /* meth */
310 chunk_memcat(temp, sl.rq.v.ptr, sl.rq.v.len); /* vsn */
311
312 chunk_memcat(temp, sl.rq.u.ptr, p.ptr - sl.rq.u.ptr); /* uri: host part */
313 chunk_memcat(temp, path.ptr, path.len); /* uri: new path */
314 chunk_memcat(temp, p.ptr + plen, p.len - plen); /* uri: QS part */
315
316 /* Get uri ptr and len */
317 uri.ptr = temp->area + sl.rq.m.len + sl.rq.v.len;
318 uri.len = sl.rq.u.len - plen + path.len;
319
320 /* create the new start line */
321 new_sl.rq.meth = sl.rq.meth;
322 new_sl.rq.m = ist2(temp->area, sl.rq.m.len);
323 new_sl.rq.u = uri;
324 new_sl.rq.v = ist2(temp->area + sl.rq.m.len, sl.rq.v.len);
325
326 return http_replace_reqline(htx, new_sl);
327}
328
329/* Replace the request query-string in the HTX message <htx> by <query>. The
330 * host part and the path are preserved. It returns 1 on success, otherwise
331 * 0.
332 */
333int http_replace_req_query(struct htx *htx, const struct ist query)
334{
335 struct buffer *temp = get_trash_chunk();
336 union h1_sl sl = http_find_stline(htx);
337 union h1_sl new_sl;
338 struct ist q, uri;
339 int offset = 1;
340
341 q = sl.rq.u;
342 while (q.len > 0 && *(q.ptr) != '?') {
343 q.ptr++;
344 q.len--;
345 }
346
347 /* skip the question mark or indicate that we must insert it
348 * (but only if the format string is not empty then).
349 */
350 if (q.len) {
351 q.ptr++;
352 q.len--;
353 }
354 else if (query.len > 1)
355 offset = 0;
356
357 /* Start by copying old method and version and create the new uri */
358 chunk_memcat(temp, sl.rq.m.ptr, sl.rq.m.len); /* meth */
359 chunk_memcat(temp, sl.rq.v.ptr, sl.rq.v.len); /* vsn */
360
361 chunk_memcat(temp, sl.rq.u.ptr, q.ptr - sl.rq.u.ptr); /* uri: host + path part */
362 chunk_memcat(temp, query.ptr + offset, query.len - offset); /* uri: new QS */
363
364 /* Get uri ptr and len */
365 uri.ptr = temp->area + sl.rq.m.len + sl.rq.v.len;
366 uri.len = sl.rq.u.len - q.len + query.len - offset;
367
368 /* create the new start line */
369 new_sl.rq.meth = sl.rq.meth;
370 new_sl.rq.m = ist2(temp->area, sl.rq.m.len);
371 new_sl.rq.u = uri;
372 new_sl.rq.v = ist2(temp->area + sl.rq.m.len, sl.rq.v.len);
373
374 return http_replace_reqline(htx, new_sl);
375}
376
377/* Replace the response status in the HTX message <htx> by <status>. It returns
378 * 1 on success, otherwise 0.
379*/
380int http_replace_res_status(struct htx *htx, const struct ist status)
381{
382 struct buffer *temp = get_trash_chunk();
383 union h1_sl sl = http_find_stline(htx);
384 union h1_sl new_sl;
385
386 /* Start by copying old uri and version */
387 chunk_memcat(temp, sl.st.v.ptr, sl.st.v.len); /* vsn */
388 chunk_memcat(temp, sl.st.r.ptr, sl.st.r.len); /* reason */
389
390 /* create the new start line */
391 new_sl.st.status = strl2ui(status.ptr, status.len);
392 new_sl.st.v = ist2(temp->area, sl.st.v.len);
393 new_sl.st.c = status;
394 new_sl.st.r = ist2(temp->area + sl.st.v.len, sl.st.r.len);
395
396 return http_replace_resline(htx, new_sl);
397}
398
399/* Replace the response reason in the HTX message <htx> by <reason>. It returns
400 * 1 on success, otherwise 0.
401*/
402int http_replace_res_reason(struct htx *htx, const struct ist reason)
403{
404 struct buffer *temp = get_trash_chunk();
405 union h1_sl sl = http_find_stline(htx);
406 union h1_sl new_sl;
407
408 /* Start by copying old uri and version */
409 chunk_memcat(temp, sl.st.v.ptr, sl.st.v.len); /* vsn */
410 chunk_memcat(temp, sl.st.c.ptr, sl.st.c.len); /* code */
411
412 /* create the new start line */
413 new_sl.st.status = sl.st.status;
414 new_sl.st.v = ist2(temp->area, sl.st.v.len);
415 new_sl.st.c = ist2(temp->area + sl.st.v.len, sl.st.c.len);
416 new_sl.st.r = reason;
417
418 return http_replace_resline(htx, new_sl);
419}
420
Christopher Faulet47596d32018-10-22 09:17:28 +0200421/* Replaces a part of a header value referenced in the context <ctx> by
422 * <data>. It returns 1 on success, otherwise it returns 0. The context is
423 * updated if necessary.
424 */
425int http_replace_header_value(struct htx *htx, struct http_hdr_ctx *ctx, const struct ist data)
426{
427 struct htx_blk *blk = ctx->blk;
428 char *start;
429 struct ist v;
430 uint32_t len, off;
431
432 if (!blk)
433 return 0;
434
435 v = htx_get_blk_value(htx, blk);
436 start = ctx->value.ptr - ctx->lws_before;
437 len = ctx->lws_before + ctx->value.len + ctx->lws_after;
438 off = start - v.ptr;
439
440 blk = htx_replace_blk_value(htx, blk, ist2(start, len), data);
441 if (!blk)
442 return 0;
443
444 v = htx_get_blk_value(htx, blk);
445 ctx->blk = blk;
446 ctx->value.ptr = v.ptr + off;
447 ctx->value.len = data.len;
448 ctx->lws_before = ctx->lws_after = 0;
449
450 return 1;
451}
452
453/* Fully replaces a header referenced in the context <ctx> by the name <name>
454 * with the value <value>. It returns 1 on success, otherwise it returns 0. The
455 * context is updated if necessary.
456 */
457int http_replace_header(struct htx *htx, struct http_hdr_ctx *ctx,
458 const struct ist name, const struct ist value)
459{
460 struct htx_blk *blk = ctx->blk;
461
462 if (!blk)
463 return 0;
464
465 blk = htx_replace_header(htx, blk, name, value);
466 if (!blk)
467 return 0;
468
469 ctx->blk = blk;
470 ctx->value = ist(NULL);
471 ctx->lws_before = ctx->lws_after = 0;
472
473 return 1;
474}
475
476/* Remove one value of a header. This only works on a <ctx> returned by
477 * http_find_header function. The value is removed, as well as surrounding commas
478 * if any. If the removed value was alone, the whole header is removed. The
479 * <ctx> is always updated accordingly, as well as the HTX message <htx>. It
480 * returns 1 on success. Otherwise, it returns 0. The <ctx> is always left in a
481 * form that can be handled by http_find_header() to find next occurrence.
482 */
483int http_remove_header(struct htx *htx, struct http_hdr_ctx *ctx)
484{
485 struct htx_blk *blk = ctx->blk;
486 char *start;
487 struct ist v;
488 uint32_t len;
489
490 if (!blk)
491 return 0;
492
493 start = ctx->value.ptr - ctx->lws_before;
494 len = ctx->lws_before + ctx->value.len + ctx->lws_after;
495
496 v = htx_get_blk_value(htx, blk);
497 if (len == v.len) {
498 blk = htx_remove_blk(htx, blk);
499 if (blk || !htx->used) {
500 ctx->blk = blk;
501 ctx->value = ist2(NULL, 0);
502 ctx->lws_before = ctx->lws_after = 0;
503 }
504 else {
505 ctx->blk = htx_get_blk(htx, htx->tail);
506 ctx->value = htx_get_blk_value(htx, ctx->blk);
507 ctx->lws_before = ctx->lws_after = 0;
508 }
509 return 1;
510 }
511
512 /* This was not the only value of this header. We have to remove the
513 * part pointed by ctx->value. If it is the last entry of the list, we
514 * remove the last separator.
515 */
516 if (start == v.ptr) {
517 /* It's the first header part but not the only one. So remove
518 * the comma after it. */
519 len++;
520 }
521 else {
522 /* There is at least one header part before the removed one. So
523 * remove the comma between them. */
524 start--;
525 len++;
526 }
527 /* Update the block content and its len */
528 memmove(start, start+len, v.len-len);
529 htx_set_blk_value_len(blk, v.len-len);
530
531 /* Update HTX msg */
532 htx->data -= len;
533
534 /* Finally update the ctx */
535 ctx->value.ptr = start;
536 ctx->value.len = 0;
537 ctx->lws_before = ctx->lws_after = 0;
538
539 return 1;
540}
Christopher Faulet7ff1cea2018-10-24 10:39:35 +0200541
542
543/* Return in <vptr> and <vlen> the pointer and length of occurrence <occ> of
544 * header whose name is <hname> of length <hlen>. If <ctx> is null, lookup is
545 * performed over the whole headers. Otherwise it must contain a valid header
546 * context, initialised with ctx->blk=NULL for the first lookup in a series. If
547 * <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
548 * is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
549 * than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
550 * -1. The value fetch stops at commas, so this function is suited for use with
551 * list headers.
552 * The return value is 0 if nothing was found, or non-zero otherwise.
553 */
554unsigned int http_get_htx_hdr(const struct htx *htx, const struct ist hdr,
555 int occ, struct http_hdr_ctx *ctx, char **vptr, size_t *vlen)
556{
557 struct http_hdr_ctx local_ctx;
558 struct ist val_hist[MAX_HDR_HISTORY];
559 unsigned int hist_idx;
560 int found;
561
562 if (!ctx) {
563 local_ctx.blk = NULL;
564 ctx = &local_ctx;
565 }
566
567 if (occ >= 0) {
568 /* search from the beginning */
569 while (http_find_header(htx, hdr, ctx, 0)) {
570 occ--;
571 if (occ <= 0) {
572 *vptr = ctx->value.ptr;
573 *vlen = ctx->value.len;
574 return 1;
575 }
576 }
577 return 0;
578 }
579
580 /* negative occurrence, we scan all the list then walk back */
581 if (-occ > MAX_HDR_HISTORY)
582 return 0;
583
584 found = hist_idx = 0;
585 while (http_find_header(htx, hdr, ctx, 0)) {
586 val_hist[hist_idx] = ctx->value;
587 if (++hist_idx >= MAX_HDR_HISTORY)
588 hist_idx = 0;
589 found++;
590 }
591 if (-occ > found)
592 return 0;
593
594 /* OK now we have the last occurrence in [hist_idx-1], and we need to
595 * find occurrence -occ. 0 <= hist_idx < MAX_HDR_HISTORY, and we have
596 * -10 <= occ <= -1. So we have to check [hist_idx%MAX_HDR_HISTORY+occ]
597 * to remain in the 0..9 range.
598 */
599 hist_idx += occ + MAX_HDR_HISTORY;
600 if (hist_idx >= MAX_HDR_HISTORY)
601 hist_idx -= MAX_HDR_HISTORY;
602 *vptr = val_hist[hist_idx].ptr;
603 *vlen = val_hist[hist_idx].len;
604 return 1;
605}
606
607/* Return in <vptr> and <vlen> the pointer and length of occurrence <occ> of
608 * header whose name is <hname> of length <hlen>. If <ctx> is null, lookup is
609 * performed over the whole headers. Otherwise it must contain a valid header
610 * context, initialised with ctx->blk=NULL for the first lookup in a series. If
611 * <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
612 * is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
613 * than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
614 * -1. This function differs from http_get_hdr() in that it only returns full
615 * line header values and does not stop at commas.
616 * The return value is 0 if nothing was found, or non-zero otherwise.
617 */
618unsigned int http_get_htx_fhdr(const struct htx *htx, const struct ist hdr,
619 int occ, struct http_hdr_ctx *ctx, char **vptr, size_t *vlen)
620{
621 struct http_hdr_ctx local_ctx;
622 struct ist val_hist[MAX_HDR_HISTORY];
623 unsigned int hist_idx;
624 int found;
625
626 if (!ctx) {
627 local_ctx.blk = NULL;
628 ctx = &local_ctx;
629 }
630
631 if (occ >= 0) {
632 /* search from the beginning */
633 while (http_find_header(htx, hdr, ctx, 1)) {
634 occ--;
635 if (occ <= 0) {
636 *vptr = ctx->value.ptr;
637 *vlen = ctx->value.len;
638 return 1;
639 }
640 }
641 return 0;
642 }
643
644 /* negative occurrence, we scan all the list then walk back */
645 if (-occ > MAX_HDR_HISTORY)
646 return 0;
647
648 found = hist_idx = 0;
649 while (http_find_header(htx, hdr, ctx, 1)) {
650 val_hist[hist_idx] = ctx->value;
651 if (++hist_idx >= MAX_HDR_HISTORY)
652 hist_idx = 0;
653 found++;
654 }
655 if (-occ > found)
656 return 0;
657
658 /* OK now we have the last occurrence in [hist_idx-1], and we need to
659 * find occurrence -occ. 0 <= hist_idx < MAX_HDR_HISTORY, and we have
660 * -10 <= occ <= -1. So we have to check [hist_idx%MAX_HDR_HISTORY+occ]
661 * to remain in the 0..9 range.
662 */
663 hist_idx += occ + MAX_HDR_HISTORY;
664 if (hist_idx >= MAX_HDR_HISTORY)
665 hist_idx -= MAX_HDR_HISTORY;
666 *vptr = val_hist[hist_idx].ptr;
667 *vlen = val_hist[hist_idx].len;
668 return 1;
669}