2 * HTTP extensions logic and helpers
4 * Copyright 2022 HAProxy Technologies
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.1 of the License, or (at your option) any later version.
13 /* forwarded header (7239 RFC) */
15 #include <haproxy/sample.h>
16 #include <haproxy/http_htx.h>
17 #include <haproxy/http_ext.h>
18 #include <haproxy/chunk.h>
19 #include <haproxy/stream.h>
20 #include <haproxy/proxy.h>
21 #include <haproxy/sc_strm.h>
22 #include <haproxy/obj_type.h>
23 #include <haproxy/cfgparse.h>
24 #include <haproxy/tools.h>
26 /* check if char is a valid obfuscated identifier char
27 * (according to 7239 RFC)
28 * Returns non zero value for valid char
30 static int http_7239_valid_obfsc(char c)
32 return (isalnum((unsigned char)c) ||
33 (c == '.' || c == '-' || c == '_'));
37 * =========== ANALYZE ===========
38 * below are http process/ana helpers
41 /* checks if <input> contains rfc7239 compliant port
42 * Returns 1 for success and 0 for failure
43 * if <port> is not NULL, it will be set to the extracted value contained
45 * <input> will be consumed accordingly (parsed/extracted characters are
46 * removed from <input>)
48 static inline int http_7239_extract_port(struct ist *input, uint16_t *port)
50 char *start = istptr(*input);
51 uint32_t port_cast = 0;
54 /* strtol does not support non-null terminated str,
55 * we extract port ourselves
57 while (it < istlen(*input) &&
58 isdigit((unsigned char)start[it])) {
59 port_cast = (port_cast * 10) + (start[it] - '0');
60 if (port_cast > 65535)
61 return 0; /* invalid port */
65 return 0; /* invalid port */
68 *port = (uint16_t)port_cast;
69 *input = istadv(*input, it);
73 /* checks if <input> contains rfc7239 compliant obfuscated identifier
74 * Returns 1 for success and 0 for failure
75 * if <obfs> is not NULL, it will be set to the extracted value contained
77 * <input> will be consumed accordingly (parsed/extracted characters are
78 * removed from <input>)
80 static inline int http_7239_extract_obfs(struct ist *input, struct ist *obfs)
85 obfs->ptr = input->ptr;
87 while (it < istlen(*input) && istptr(*input)[it] != ';') {
88 if (!http_7239_valid_obfsc(istptr(*input)[it]))
89 break; /* end of obfs token */
94 *input = istadv(*input, it);
98 /* checks if <input> contains rfc7239 compliant IPV4 address
99 * Returns 1 for success and 0 for failure
100 * if <ip> is not NULL, it will be set to the extracted value contained
102 * <input> will be consumed accordingly (parsed/extracted characters are
103 * removed from <input>)
105 static inline int http_7239_extract_ipv4(struct ist *input, struct in_addr *ip)
107 char ip4[INET_ADDRSTRLEN];
108 unsigned char buf[sizeof(struct in_addr)];
111 /* extract ipv4 addr */
112 while (it < istlen(*input) && it < (sizeof(ip4) - 1)) {
113 if (!isdigit((unsigned char)istptr(*input)[it]) &&
114 istptr(*input)[it] != '.')
115 break; /* no more ip4 char */
116 ip4[it] = istptr(*input)[it];
120 if (inet_pton(AF_INET, ip4, buf) != 1)
121 return 0; /* invalid ip4 addr */
124 memcpy(ip, buf, sizeof(buf));
125 *input = istadv(*input, it);
129 /* checks if <input> contains rfc7239 compliant IPV6 address
130 * assuming input.len >= 1 and first char is '['
131 * Returns 1 for success and 0 for failure
132 * if <ip> is not NULL, it will be set to the extracted value contained
134 * <input> will be consumed accordingly (parsed/extracted characters are
135 * removed from <input>)
137 static inline int http_7239_extract_ipv6(struct ist *input, struct in6_addr *ip)
139 char ip6[INET6_ADDRSTRLEN];
140 unsigned char buf[sizeof(struct in6_addr)];
143 *input = istnext(*input); /* skip '[' leading char */
144 /* extract ipv6 addr */
145 while (it < istlen(*input) &&
146 it < (sizeof(ip6) - 1)) {
147 if (!isalnum((unsigned char)istptr(*input)[it]) &&
148 istptr(*input)[it] != ':')
149 break; /* no more ip6 char */
150 ip6[it] = istptr(*input)[it];
154 if ((istlen(*input)-it) < 1 || istptr(*input)[it] != ']')
155 return 0; /* missing ending "]" char */
157 if (inet_pton(AF_INET6, ip6, buf) != 1)
158 return 0; /* invalid ip6 addr */
161 memcpy(ip, buf, sizeof(buf));
162 *input = istadv(*input, it);
166 /* checks if <input> contains rfc7239 compliant host
167 * <quoted> is used to determine if the current input is being extracted
168 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
169 * differ wheteher the input is quoted or not according to the rfc.
170 * Returns 1 for success and 0 for failure
171 * if <host> is not NULL, it will be set to the extracted value contained
173 * <input> will be consumed accordingly (parsed/extracted characters are
174 * removed from <input>)
176 static inline int http_7239_extract_host(struct ist *input, struct ist *host, int quoted)
178 if (istlen(*input) < 1)
179 return 0; /* invalid input */
182 host->ptr = input->ptr;
184 if (quoted && *istptr(*input) == '[') {
185 /* raw ipv6 address */
186 if (!http_7239_extract_ipv6(input, NULL))
187 return 0; /* invalid addr */
191 while (istlen(*input)) {
192 if (!isalnum((unsigned char)*istptr(*input)) &&
193 *istptr(*input) != '.')
194 break; /* end of hostname token */
195 *input = istnext(*input);
198 if (istlen(*input) < 1 || *istptr(*input) != ':') {
199 goto out; /* no optional port provided */
202 return 0; /* not supported */
203 *input = istnext(*input); /* skip ':' */
205 if (!http_7239_extract_port(input, NULL))
206 return 0; /* invalid port */
209 host->len = (input->ptr - host->ptr);
213 /* checks if <input> contains rfc7239 compliant nodename
214 * <quoted> is used to determine if the current input is being extracted
215 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
216 * differ wheteher the input is quoted or not according to the rfc.
217 * Returns 1 for success and 0 for failure
218 * if <nodename> is not NULL, it will be set to the extracted value contained
220 * <input> will be consumed accordingly (parsed/extracted characters are
221 * removed from <input>)
223 static inline int http_7239_extract_nodename(struct ist *input, struct forwarded_header_nodename *nodename, int quoted)
225 if (istlen(*input) < 1)
226 return 0; /* invalid input */
227 if (*istptr(*input) == '_') {
228 struct ist *obfs = NULL;
230 /* obfuscated nodename */
231 *input = istnext(*input); /* skip '_' */
233 nodename->type = FORWARDED_HEADER_OBFS;
234 obfs = &nodename->obfs;
236 if (!http_7239_extract_obfs(input, obfs))
237 return 0; /* invalid obfs */
238 } else if (*istptr(*input) == 'u') {
239 /* "unknown" nodename? */
240 if (istlen(*input) < 7 ||
241 strncmp("unknown", istptr(*input), 7))
242 return 0; /* syntax error */
243 *input = istadv(*input, 7); /* skip "unknown" */
245 nodename->type = FORWARDED_HEADER_UNK;
246 } else if (quoted && *istptr(*input) == '[') {
247 struct in6_addr *ip6 = NULL;
251 struct sockaddr_in6 *addr = (void *)&nodename->ip;
253 ip6 = &addr->sin6_addr;
254 addr->sin6_family = AF_INET6;
255 nodename->type = FORWARDED_HEADER_IP;
257 if (!http_7239_extract_ipv6(input, ip6))
258 return 0; /* invalid ip6 */
259 } else if (*istptr(*input)) {
260 struct in_addr *ip = NULL;
264 struct sockaddr_in *addr = (void *)&nodename->ip;
266 ip = &addr->sin_addr;
267 addr->sin_family = AF_INET;
268 nodename->type = FORWARDED_HEADER_IP;
270 if (!http_7239_extract_ipv4(input, ip))
271 return 0; /* invalid ip */
273 return 0; /* unexpected char */
279 /* checks if <input> contains rfc7239 compliant nodeport
280 * <quoted> is used to determine if the current input is being extracted
281 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
282 * differ wheteher the input is quoted or not according to the rfc.
283 * Returns 1 for success and 0 for failure
284 * if <nodeport> is not NULL, it will be set to the extracted value contained
286 * <input> will be consumed accordingly (parsed/extracted characters are
287 * removed from <input>)
289 static inline int http_7239_extract_nodeport(struct ist *input, struct forwarded_header_nodeport *nodeport)
291 if (*istptr(*input) == '_') {
292 struct ist *obfs = NULL;
294 /* obfuscated nodeport */
295 *input = istnext(*input); /* skip '_' */
297 nodeport->type = FORWARDED_HEADER_OBFS;
298 obfs = &nodeport->obfs;
300 if (!http_7239_extract_obfs(input, obfs))
301 return 0; /* invalid obfs */
303 uint16_t *port = NULL;
307 nodeport->type = FORWARDED_HEADER_PORT;
308 port = &nodeport->port;
310 if (!http_7239_extract_port(input, port))
311 return 0; /* invalid port */
317 /* checks if <input> contains rfc7239 compliant node (nodename:nodeport token)
318 * <quoted> is used to determine if the current input is being extracted
319 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
320 * differ wheteher the input is quoted or not according to the rfc.
321 * Returns 1 for success and 0 for failure
322 * if <node> is not NULL, it will be set to the extracted value contained
324 * <input> will be consumed accordingly (parsed/extracted characters are
325 * removed from <input>)
327 static inline int http_7239_extract_node(struct ist *input, struct forwarded_header_node *node, int quoted)
329 struct forwarded_header_nodename *nodename = NULL;
330 struct forwarded_header_nodeport *nodeport = NULL;
333 nodename = &node->nodename;
334 nodeport = &node->nodeport;
335 node->raw.ptr = input->ptr;
337 if (!http_7239_extract_nodename(input, nodename, quoted))
338 return 0; /* invalid nodename */
339 if (istlen(*input) < 1 || *istptr(*input) != ':') {
341 node->nodeport.type = FORWARDED_HEADER_UNK;
342 goto out; /* no optional port provided */
345 return 0; /* not supported */
346 *input = istnext(*input);
347 if (!http_7239_extract_nodeport(input, nodeport))
348 return 0; /* invalid nodeport */
352 node->raw.len = input->ptr - node->raw.ptr;
356 static inline int _forwarded_header_save_ctx(struct forwarded_header_ctx *ctx, int current_step, int required_steps)
358 return (ctx && (current_step & required_steps));
361 static inline void _forwarded_header_quote_expected(struct ist *hdr, uint8_t *quoted)
363 if (istlen(*hdr) > 0 && *istptr(*hdr) == '"') {
365 /* node is quoted, we must find corresponding
366 * ending quote at the end of the token
368 *hdr = istnext(*hdr); /* skip quote */
372 /* checks if current header <hdr> is RFC 7239 compliant and can be "trusted".
373 * function will stop parsing as soon as every <required_steps> have
374 * been validated or error is encountered.
375 * Provide FORWARDED_HEADER_ALL for a full header validating spectrum.
376 * You may provide limited scope to perform quick searches on specific attributes
377 * If <ctx> is provided (not NULL), parsed attributes will be stored according to
378 * their types, allowing you to extract some useful information from the header.
379 * Returns 0 on failure and <validated_steps> bitfield on success.
381 int http_validate_7239_header(struct ist hdr, int required_steps, struct forwarded_header_ctx *ctx)
383 int validated_steps = 0;
384 int current_step = 0;
388 while (istlen(hdr) && (required_steps & ~validated_steps)) {
390 if (*istptr(hdr) == ';')
391 hdr = istnext(hdr); /* skip ';' */
393 goto not_ok; /* unexpected char */
398 if (!(validated_steps & FORWARDED_HEADER_FOR) && istlen(hdr) > 4 &&
399 strncmp("for=", istptr(hdr), 4) == 0) {
400 struct forwarded_header_node *node = NULL;
403 current_step = FORWARDED_HEADER_FOR;
404 hdr = istadv(hdr, 4); /* skip "for=" */
405 _forwarded_header_quote_expected(&hdr, "ed);
406 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
409 if (!http_7239_extract_node(&hdr, node, quoted))
410 goto not_ok; /* invalid node */
412 else if (!(validated_steps & FORWARDED_HEADER_BY) && istlen(hdr) > 3 &&
413 strncmp("by=", istptr(hdr), 3) == 0) {
414 struct forwarded_header_node *node = NULL;
417 current_step = FORWARDED_HEADER_BY;
418 hdr = istadv(hdr, 3); /* skip "by=" */
419 _forwarded_header_quote_expected(&hdr, "ed);
420 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
423 if (!http_7239_extract_node(&hdr, node, quoted))
424 goto not_ok; /* invalid node */
426 else if (!(validated_steps & FORWARDED_HEADER_HOST) && istlen(hdr) > 5 &&
427 strncmp("host=", istptr(hdr), 5) == 0) {
428 struct ist *host = NULL;
431 current_step = FORWARDED_HEADER_HOST;
432 hdr = istadv(hdr, 5); /* skip "host=" */
433 _forwarded_header_quote_expected(&hdr, "ed);
434 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
437 if (!http_7239_extract_host(&hdr, host, quoted))
438 goto not_ok; /* invalid host */
440 else if (!(validated_steps & FORWARDED_HEADER_PROTO) && istlen(hdr) > 6 &&
441 strncmp("proto=", istptr(hdr), 6) == 0) {
442 /* proto parameter */
443 current_step = FORWARDED_HEADER_PROTO;
444 hdr = istadv(hdr, 6); /* skip "proto=" */
445 /* validate proto (only common used http|https are supported for now) */
446 if (istlen(hdr) < 4 || strncmp("http", istptr(hdr), 4))
448 hdr = istadv(hdr, 4); /* skip "http" */
449 if (istlen(hdr) && *istptr(hdr) == 's') {
451 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
452 ctx->proto = FORWARDED_HEADER_HTTPS;
453 } else if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
454 ctx->proto = FORWARDED_HEADER_HTTP;
455 /* rfc allows for potential proto quoting, but we don't support
456 * it: it is not common usage
461 * rfc allows for upcoming extensions
462 * but obviously, we can't trust them
463 * as they are not yet standardized
470 if (istlen(hdr) < 1 || *istptr(hdr) != '"') {
471 /* matching ending quote not found */
474 hdr = istnext(hdr); /* skip ending quote */
475 quoted = 0; /* reset */
477 validated_steps |= current_step;
480 return validated_steps;
486 static inline void http_build_7239_header_nodename(struct buffer *out,
487 struct stream *s, struct proxy *curproxy,
488 const struct sockaddr_storage *addr,
489 struct http_ext_7239_forby *forby)
491 struct in6_addr *ip6_addr;
493 if (forby->nn_mode == HTTP_7239_FORBY_ORIG) {
494 if (addr && addr->ss_family == AF_INET) {
495 unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)addr)->sin_addr;
497 chunk_appendf(out, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
499 else if (addr && addr->ss_family == AF_INET6) {
500 ip6_addr = &((struct sockaddr_in6 *)addr)->sin6_addr;
503 char pn[INET6_ADDRSTRLEN];
509 chunk_appendf(out, "\""); /* explicit quoting required for ipv6 */
510 chunk_appendf(out, "[%s]", pn);
513 /* else: not supported */
515 else if (forby->nn_mode == HTTP_7239_FORBY_SMP && forby->nn_expr) {
518 smp = sample_process(curproxy, s->sess, s,
519 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->nn_expr, NULL);
522 if (smp->data.type == SMP_T_IPV6) {
523 /* smp is valid IP6, print with RFC compliant output */
524 ip6_addr = &smp->data.u.ipv6;
527 if (sample_casts[smp->data.type][SMP_T_STR] &&
528 sample_casts[smp->data.type][SMP_T_STR](smp)) {
529 struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
530 struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data);
531 struct forwarded_header_nodename nodename;
533 /* validate nodename */
534 if (http_7239_extract_nodename(&validate_n, &nodename, 1) &&
535 !istlen(validate_n)) {
536 if (nodename.type == FORWARDED_HEADER_IP &&
537 nodename.ip.ss_family == AF_INET6) {
538 /* special care needed for valid ip6 nodename (quoting) */
539 ip6_addr = &((struct sockaddr_in6 *)&nodename.ip)->sin6_addr;
542 /* no special care needed, input is already rfc compliant,
543 * just print as regular non quoted string
545 chunk_cat(out, &smp->data.u.str);
547 else if (http_7239_extract_obfs(&validate_o, NULL) &&
548 !istlen(validate_o)) {
549 /* raw user input that should be printed as 7239 obfs */
550 chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
552 /* else: not compliant */
554 /* else: cannot be casted to str */
556 /* else: smp error */
560 static inline void http_build_7239_header_nodeport(struct buffer *out,
561 struct stream *s, struct proxy *curproxy,
562 const struct sockaddr_storage *addr,
563 struct http_ext_7239_forby *forby)
565 if (forby->np_mode == HTTP_7239_FORBY_ORIG) {
566 if (addr && addr->ss_family == AF_INET)
567 chunk_appendf(out, "%d", ntohs(((struct sockaddr_in *)addr)->sin_port));
568 else if (addr && addr->ss_family == AF_INET6)
569 chunk_appendf(out, "%d", ntohs(((struct sockaddr_in6 *)addr)->sin6_port));
570 /* else: not supported */
572 else if (forby->np_mode == HTTP_7239_FORBY_SMP && forby->np_expr) {
575 smp = sample_fetch_as_type(curproxy, s->sess, s,
576 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->np_expr, SMP_T_STR);
578 struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
579 struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data);
581 /* validate nodeport */
582 if (http_7239_extract_nodeport(&validate_n, NULL) &&
583 !istlen(validate_n)) {
584 /* no special care needed, input is already rfc compliant,
585 * just print as regular non quoted string
587 chunk_cat(out, &smp->data.u.str);
589 else if (http_7239_extract_obfs(&validate_o, NULL) &&
590 !istlen(validate_o)) {
591 /* raw user input that should be printed as 7239 obfs */
592 chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
594 /* else: not compliant */
596 /* else: smp error */
600 static inline void http_build_7239_header_node(struct buffer *out,
601 struct stream *s, struct proxy *curproxy,
602 const struct sockaddr_storage *addr,
603 struct http_ext_7239_forby *forby)
608 offset_start = out->data;
610 chunk_appendf(out, "\"");
611 offset_save = out->data;
612 http_build_7239_header_node(out, s, curproxy, addr, &curproxy->http.fwd.p_by);
613 if (offset_save == out->data) {
614 /* could not build nodename, either because some
615 * data is not available or user is providing bad input
617 chunk_appendf(out, "unknown");
619 if (forby->np_mode) {
620 chunk_appendf(out, ":");
621 offset_save = out->data;
622 http_build_7239_header_nodeport(out, s, curproxy, addr, &curproxy->http.fwd.p_by);
623 if (offset_save == out->data) {
624 /* could not build nodeport, either because some data is
625 * not available or user is providing bad input
627 out->data = offset_save - 1;
630 if (out->data != offset_start && out->area[offset_start] == '"')
631 chunk_appendf(out, "\""); /* add matching end quote */
634 static inline void http_build_7239_header_host(struct buffer *out,
635 struct stream *s, struct proxy *curproxy,
636 struct htx *htx, struct http_ext_7239_host *host)
638 struct http_hdr_ctx ctx = { .blk = NULL };
642 if (host->mode == HTTP_7239_HOST_ORIG &&
643 http_find_header(htx, ist("host"), &ctx, 0)) {
645 str_len = ctx.value.len;
648 struct ist validate = ist2(str, str_len);
649 /* host check, to ensure rfc compliant output
650 * (assumming host is quoted/escaped)
652 if (http_7239_extract_host(&validate, NULL, 1) && !istlen(validate))
653 chunk_memcat(out, str, str_len);
654 /* else: not compliant or partially compliant */
658 else if (host->mode == HTTP_7239_HOST_SMP && host->expr) {
661 smp = sample_fetch_as_type(curproxy, s->sess, s,
662 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, host->expr, SMP_T_STR);
664 str = smp->data.u.str.area;
665 str_len = smp->data.u.str.data;
668 /* else: smp error */
672 /* Tries build 7239 header according to <curproxy> parameters and <s> context
673 * It both depends on <curproxy>->http_ext->fwd for config and <s> for request
675 * The function will write output to <out> buffer
676 * Returns 1 for success and 0 for error (ie: not enough space in buffer)
678 static int http_build_7239_header(struct buffer *out,
679 struct stream *s, struct proxy *curproxy, struct htx *htx)
681 struct connection *cli_conn = objt_conn(strm_sess(s)->origin);
683 if (curproxy->http.fwd.p_proto) {
684 chunk_appendf(out, "%sproto=%s", ((out->data) ? ";" : ""),
685 ((conn_is_ssl(cli_conn)) ? "https" : "http"));
687 if (curproxy->http.fwd.p_host.mode) {
688 /* always add quotes for host parameter to make output compliancy checks simpler */
689 chunk_appendf(out, "%shost=\"", ((out->data) ? ";" : ""));
690 /* ignore return value for now, but could be useful some day */
691 http_build_7239_header_host(out, s, curproxy, htx,
692 &curproxy->http.fwd.p_host);
693 chunk_appendf(out, "\"");
696 if (curproxy->http.fwd.p_by.nn_mode) {
697 const struct sockaddr_storage *dst = sc_dst(s->scf);
699 chunk_appendf(out, "%sby=", ((out->data) ? ";" : ""));
700 http_build_7239_header_node(out, s, curproxy, dst, &curproxy->http.fwd.p_by);
703 if (curproxy->http.fwd.p_for.nn_mode) {
704 const struct sockaddr_storage *src = sc_src(s->scf);
706 chunk_appendf(out, "%sfor=", ((out->data) ? ";" : ""));
707 http_build_7239_header_node(out, s, curproxy, src, &curproxy->http.fwd.p_for);
709 if (unlikely(out->data == out->size)) {
710 /* not enough space in buffer, error */
716 /* This function will try to inject 7239 forwarded header
717 * Returns 1 for success and 0 for failure
719 int http_handle_7239_header(struct stream *s, struct channel *req)
721 struct htx *htx = htxbuf(&req->buf);
722 struct proxy *curproxy = s->be; /* ignore frontend */
724 struct http_hdr_ctx find = { .blk = NULL };
725 struct http_hdr_ctx last = { .blk = NULL};
726 struct ist hdr = ist("forwarded");
728 BUG_ON(!(curproxy->options & PR_O_HTTP_7239)); /* should not happen */
730 /* ok, let's build forwarded header */
732 if (unlikely(!http_build_7239_header(&trash, s, curproxy, htx)))
733 return 0; /* error when building header (bad user conf or memory error) */
735 /* validate existing forwarded header (including multiple values),
736 * hard stop if error is encountered
738 while (http_find_header(htx, hdr, &find, 0)) {
739 /* validate current header chunk */
740 if (!http_validate_7239_header(find.value, FORWARDED_HEADER_ALL, NULL)) {
741 /* at least one error, existing forwarded header not OK, add our own
742 * forwarded header, so that it can be trusted
749 /* no errors, append our data at the end of existing header */
750 if (last.blk && validate) {
751 if (unlikely(!http_append_header_value(htx, &last, ist2(trash.area, trash.data))))
752 return 0; /* htx error */
755 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
756 return 0; /* htx error */
762 * =========== CONFIG ===========
763 * below are helpers to parse http ext options from the config
765 static int proxy_http_parse_oom(const char *file, int linenum)
769 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
770 err_code |= ERR_ALERT | ERR_ABORT;
774 static inline int _proxy_http_parse_7239_expr(char **args, int *cur_arg,
775 const char *file, int linenum,
780 if (!*args[*cur_arg + 1]) {
781 ha_alert("parsing [%s:%d]: '%s' expects <expr> as argument.\n",
782 file, linenum, args[*cur_arg]);
783 err_code |= ERR_ALERT | ERR_FATAL;
788 *expr_s = strdup(args[*cur_arg]);
790 return proxy_http_parse_oom(file, linenum);
796 int proxy_http_parse_7239(char **args, int cur_arg,
797 struct proxy *curproxy, const struct proxy *defpx,
798 const char *file, int linenum)
802 if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, "option forwarded", NULL)) {
803 /* option is ignored for frontends */
804 err_code |= ERR_WARN;
808 curproxy->options |= PR_O_HTTP_7239;
809 curproxy->http.fwd.p_proto = 0;
810 curproxy->http.fwd.p_host.mode = 0;
811 curproxy->http.fwd.p_for.nn_mode = 0;
812 curproxy->http.fwd.p_for.np_mode = 0;
813 curproxy->http.fwd.p_by.nn_mode = 0;
814 curproxy->http.fwd.p_by.np_mode = 0;
815 ha_free(&curproxy->http.fwd.c_file);
816 curproxy->http.fwd.c_file = strdup(file);
817 curproxy->http.fwd.c_line = linenum;
819 /* start at 2, since 0+1 = "option" "forwarded" */
821 if (!*(args[cur_arg])) {
822 /* no optional argument provided, use default settings */
823 curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_ORIG; /* enable for and mimic xff */
824 curproxy->http.fwd.p_proto = 1; /* enable proto */
827 /* loop to go through optional arguments */
828 while (*(args[cur_arg])) {
829 if (strcmp(args[cur_arg], "proto") == 0) {
830 curproxy->http.fwd.p_proto = 1;
832 } else if (strcmp(args[cur_arg], "host") == 0) {
833 curproxy->http.fwd.p_host.mode = HTTP_7239_HOST_ORIG;
835 } else if (strcmp(args[cur_arg], "host-expr") == 0) {
836 curproxy->http.fwd.p_host.mode = HTTP_7239_HOST_SMP;
837 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
838 &curproxy->http.fwd.p_host.expr_s);
839 if (err_code & ERR_FATAL)
841 } else if (strcmp(args[cur_arg], "by") == 0) {
842 curproxy->http.fwd.p_by.nn_mode = HTTP_7239_FORBY_ORIG;
844 } else if (strcmp(args[cur_arg], "by-expr") == 0) {
845 curproxy->http.fwd.p_by.nn_mode = HTTP_7239_FORBY_SMP;
846 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
847 &curproxy->http.fwd.p_by.nn_expr_s);
848 if (err_code & ERR_FATAL)
850 } else if (strcmp(args[cur_arg], "for") == 0) {
851 curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_ORIG;
853 } else if (strcmp(args[cur_arg], "for-expr") == 0) {
854 curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_SMP;
855 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
856 &curproxy->http.fwd.p_for.nn_expr_s);
857 if (err_code & ERR_FATAL)
859 } else if (strcmp(args[cur_arg], "by_port") == 0) {
860 curproxy->http.fwd.p_by.np_mode = HTTP_7239_FORBY_ORIG;
862 } else if (strcmp(args[cur_arg], "by_port-expr") == 0) {
863 curproxy->http.fwd.p_by.np_mode = HTTP_7239_FORBY_SMP;
864 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
865 &curproxy->http.fwd.p_by.np_expr_s);
866 if (err_code & ERR_FATAL)
868 } else if (strcmp(args[cur_arg], "for_port") == 0) {
869 curproxy->http.fwd.p_for.np_mode = HTTP_7239_FORBY_ORIG;
871 } else if (strcmp(args[cur_arg], "for_port-expr") == 0) {
872 curproxy->http.fwd.p_for.np_mode = HTTP_7239_FORBY_SMP;
873 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
874 &curproxy->http.fwd.p_for.np_expr_s);
875 if (err_code & ERR_FATAL)
878 /* unknown suboption - catchall */
879 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'proto', 'host', "
880 "'host-expr', 'by', 'by-expr', 'by_port', 'by_port-expr', "
881 "'for', 'for-expr', 'for_port' and 'for_port-expr'.\n",
882 file, linenum, args[0], args[1]);
883 err_code |= ERR_ALERT | ERR_FATAL;
886 } /* end while loop */
888 /* consistency check */
889 if (curproxy->http.fwd.p_by.np_mode &&
890 !curproxy->http.fwd.p_by.nn_mode) {
891 curproxy->http.fwd.p_by.np_mode = 0;
892 ha_free(&curproxy->http.fwd.p_by.np_expr_s);
893 ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'by' "
894 "and 'by-expr' are unset\n",
895 file, linenum, args[0], args[1],
896 ((curproxy->http.fwd.p_by.np_mode == HTTP_7239_FORBY_ORIG) ? "by_port" : "by_port-expr"));
897 err_code |= ERR_WARN;
899 if (curproxy->http.fwd.p_for.np_mode &&
900 !curproxy->http.fwd.p_for.nn_mode) {
901 curproxy->http.fwd.p_for.np_mode = 0;
902 ha_free(&curproxy->http.fwd.p_for.np_expr_s);
903 ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'for' "
904 "and 'for-expr' are unset\n",
905 file, linenum, args[0], args[1],
906 ((curproxy->http.fwd.p_for.np_mode == HTTP_7239_FORBY_ORIG) ? "for_port" : "for_port-expr"));
907 err_code |= ERR_WARN;
914 /* returns 0 for success and positive value
915 * (equals to number of errors) in case of error
917 int proxy_http_compile_7239(struct proxy *curproxy)
923 BUG_ON(!(curproxy->options & PR_O_HTTP_7239)); /* should not happen */
924 if (!(curproxy->cap & PR_CAP_BE)) {
925 /* no backend cap: not supported (ie: frontend)
926 * Moreover, 7239 settings are only inherited from default
927 * if proxy is backend capable.. going further would result in
928 * undefined behavior */
932 curproxy->conf.args.ctx = ARGC_OPT; /* option */
933 curproxy->conf.args.file = curproxy->http.fwd.c_file;
934 curproxy->conf.args.line = curproxy->http.fwd.c_line;
936 for (loop = 0; loop < 5; loop++) {
937 char *expr_str = NULL;
938 struct sample_expr **expr = NULL;
945 expr_str = curproxy->http.fwd.p_host.expr_s;
946 expr = &curproxy->http.fwd.p_host.expr;
947 smp = (curproxy->http.fwd.p_host.mode ==
952 expr_str = curproxy->http.fwd.p_by.nn_expr_s;
953 expr = &curproxy->http.fwd.p_by.nn_expr;
954 smp = (curproxy->http.fwd.p_by.nn_mode ==
955 HTTP_7239_FORBY_SMP);
959 expr_str = curproxy->http.fwd.p_by.np_expr_s;
960 expr = &curproxy->http.fwd.p_by.np_expr;
961 smp = (curproxy->http.fwd.p_by.np_mode ==
962 HTTP_7239_FORBY_SMP);
966 expr_str = curproxy->http.fwd.p_for.nn_expr_s;
967 expr = &curproxy->http.fwd.p_for.nn_expr;
968 smp = (curproxy->http.fwd.p_for.nn_mode ==
969 HTTP_7239_FORBY_SMP);
973 expr_str = curproxy->http.fwd.p_for.np_expr_s;
974 expr = &curproxy->http.fwd.p_for.np_expr;
975 smp = (curproxy->http.fwd.p_for.np_mode ==
976 HTTP_7239_FORBY_SMP);
979 if (!smp || !expr_str)
980 continue; /* no expr */
981 /* expr cannot be NULL past this point */
982 ALREADY_CHECKED(expr);
985 sample_parse_expr((char*[]){expr_str, NULL}, &idx,
986 curproxy->http.fwd.c_file,
987 curproxy->http.fwd.c_line,
988 &err, &curproxy->conf.args, NULL);
991 ha_alert("%s '%s' [%s:%d]: failed to parse 'option forwarded' expression '%s' in : %s.\n",
992 proxy_type_str(curproxy), curproxy->id,
993 curproxy->http.fwd.c_file, curproxy->http.fwd.c_line,
999 curproxy->conf.args.file = NULL;
1000 curproxy->conf.args.line = 0;
1007 * =========== MGMT ===========
1008 * below are helpers to manage http ext options
1011 void http_ext_7239_clean(struct http_ext_7239 *clean)
1013 ha_free(&clean->c_file);
1014 ha_free(&clean->p_host.expr_s);
1015 ha_free(&clean->p_by.nn_expr_s);
1016 ha_free(&clean->p_by.np_expr_s);
1017 ha_free(&clean->p_for.nn_expr_s);
1018 ha_free(&clean->p_for.np_expr_s);
1020 release_sample_expr(clean->p_host.expr);
1021 clean->p_host.expr = NULL;
1022 release_sample_expr(clean->p_by.nn_expr);
1023 clean->p_by.nn_expr = NULL;
1024 release_sample_expr(clean->p_by.np_expr);
1025 clean->p_by.np_expr = NULL;
1026 release_sample_expr(clean->p_for.nn_expr);
1027 clean->p_for.nn_expr = NULL;
1028 release_sample_expr(clean->p_for.np_expr);
1029 clean->p_for.np_expr = NULL;
1032 void http_ext_7239_copy(struct http_ext_7239 *dest, const struct http_ext_7239 *orig)
1035 dest->c_file = strdup(orig->c_file);
1036 dest->c_line = orig->c_line;
1038 dest->p_proto = orig->p_proto;
1040 dest->p_host.mode = orig->p_host.mode;
1041 if (orig->p_host.expr_s)
1042 dest->p_host.expr_s = strdup(orig->p_host.expr_s);
1044 dest->p_by.nn_mode = orig->p_by.nn_mode;
1045 if (orig->p_by.nn_expr_s)
1046 dest->p_by.nn_expr_s = strdup(orig->p_by.nn_expr_s);
1048 dest->p_by.np_mode = orig->p_by.np_mode;
1049 if (orig->p_by.np_expr_s)
1050 dest->p_by.np_expr_s = strdup(orig->p_by.np_expr_s);
1051 /* for - nodename */
1052 dest->p_for.nn_mode = orig->p_for.nn_mode;
1053 if (orig->p_for.nn_expr_s)
1054 dest->p_for.nn_expr_s = strdup(orig->p_for.nn_expr_s);
1055 /* for - nodeport */
1056 dest->p_for.np_mode = orig->p_for.np_mode;
1057 if (orig->p_for.np_expr_s)
1058 dest->p_for.np_expr_s = strdup(orig->p_for.np_expr_s);