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