diff --git a/doc/configuration.txt b/doc/configuration.txt index 380fb836b..60d1df2aa 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2971,7 +2971,8 @@ http-request { allow | deny | tarpit | auth [realm ] | redirect | del-header | set-nice | set-log-level | replace-header | replace-value | - set-tos | set-mark | + set-method | set-path | set-query | + set-uri | set-tos | set-mark | add-acl() | del-acl() | del-map() | @@ -3081,6 +3082,42 @@ http-request { allow | deny | tarpit | auth [realm ] | redirect | X-Forwarded-For: 172.16.10.1, 172.16.13.24, 10.0.0.37 + - "set-method" rewrites the request method with the result of the + evaluation of format string . There should be very few valid reasons + for having to do so as this is more likely to break something than to fix + it. + + - "set-path" rewrites the request path with the result of the evaluation of + format string . The query string, if any, is left intact. If a + scheme and authority is found before the path, they are left intact as + well. If the request doesn't have a path ("*"), this one is replaced with + the format. This can be used to prepend a directory component in front of + a path for example. See also "set-query" and "set-uri". + + Example : + # prepend the host name before the path + http-request set-path /%[hdr(host)]%[path] + + - "set-query" rewrites the request's query string which appears after the + first question mark ("?") with the result of the evaluation of format + string . The part prior to the question mark is left intact. If the + request doesn't contain a question mark and the new value is not empty, + then one is added at the end of the URI, followed by the new value. If + a question mark was present, it will never be removed even if the value + is empty. This can be used to add or remove parameters from the query + string. See also "set-query" and "set-uri". + + Example : + # replace "%3D" with "=" in the query string + http-request set-query %[query,regsub(%3D,=,g)] + + - "set-uri" rewrites the request URI with the result of the evaluation of + format string . The scheme, authority, path and query string are all + replaced at once. This can be used to rewrite hosts in front of proxies, + or to perform complex modifications to the URI such as moving parts + between the path and the query string. See also "set-path" and + "set-query". + - "set-nice" sets the "nice" factor of the current request being processed. It only has effect against the other requests being processed at the same time. The default value is 0, unless altered by the "nice" setting on the diff --git a/include/types/proto_http.h b/include/types/proto_http.h index 2654b78b0..d9cda713d 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -438,6 +438,9 @@ struct http_req_rule { struct list key; /* pattern to retrieve MAP or ACL key */ struct list value; /* pattern to retrieve MAP value */ } map; + struct { + void *p[4]; + } act; /* generic pointers to be used by custom actions */ } arg; /* arguments used by some actions */ union { diff --git a/src/proto_http.c b/src/proto_http.c index ac1c60fbc..3c8747841 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -11476,6 +11476,149 @@ expect_comma: return smp->data.str.len != 0; } +/* This function executes one of the set-{method,path,query,uri} actions. It + * builds a string in the trash from the specified format string, then modifies + * the relevant part of the request line accordingly. Then it updates various + * pointers to the next elements which were moved, and the total buffer length. + * It finds the action to be performed in p[2], previously filled by function + * parse_set_req_line(). It returns 0 in case of success, -1 in case of internal + * error, though this can be revisited when this code is finally exploited. + */ +int http_action_set_req_line(struct http_req_rule *rule, struct proxy *px, struct session *s, struct http_txn *txn) +{ + char *cur_ptr, *cur_end; + int offset; + int delta; + + chunk_reset(&trash); + + /* prepare a '?' just in case we have to create a query string */ + trash.str[trash.len++] = '?'; + offset = 1; + trash.len += build_logline(s, trash.str + trash.len, trash.size - trash.len, (struct list *)&rule->arg.act.p[0]); + + switch (*(int *)&rule->arg.act.p[2]) { + case 0: // method + cur_ptr = s->req->buf->p; + cur_end = cur_ptr + txn->req.sl.rq.m_l; + + /* adjust req line offsets and lengths */ + delta = trash.len - offset - (cur_end - cur_ptr); + txn->req.sl.rq.m_l += delta; + txn->req.sl.rq.u += delta; + txn->req.sl.rq.v += delta; + break; + + case 1: // path + cur_ptr = http_get_path(txn); + if (!cur_ptr) + cur_ptr = s->req->buf->p + txn->req.sl.rq.u; + + cur_end = cur_ptr; + while (cur_end < s->req->buf->p + txn->req.sl.rq.u + txn->req.sl.rq.u_l && *cur_end != '?') + cur_end++; + + /* adjust req line offsets and lengths */ + delta = trash.len - offset - (cur_end - cur_ptr); + txn->req.sl.rq.u_l += delta; + txn->req.sl.rq.v += delta; + break; + + case 2: // query + cur_ptr = s->req->buf->p + txn->req.sl.rq.u; + cur_end = cur_ptr + txn->req.sl.rq.u_l; + while (cur_ptr < cur_end && *cur_ptr != '?') + cur_ptr++; + + /* skip the question mark or indicate that we must insert it + * (but only if the format string is not empty then). + */ + if (cur_ptr < cur_end) + cur_ptr++; + else if (trash.len > 1) + offset = 0; + + /* adjust req line offsets and lengths */ + delta = trash.len - offset - (cur_end - cur_ptr); + txn->req.sl.rq.u_l += delta; + txn->req.sl.rq.v += delta; + break; + + case 3: // uri + cur_ptr = s->req->buf->p + txn->req.sl.rq.u; + cur_end = cur_ptr + txn->req.sl.rq.u_l; + + /* adjust req line offsets and lengths */ + delta = trash.len - offset - (cur_end - cur_ptr); + txn->req.sl.rq.u_l += delta; + txn->req.sl.rq.v += delta; + break; + + default: + return -1; + } + + /* commit changes and adjust end of message */ + delta = buffer_replace2(s->req->buf, cur_ptr, cur_end, trash.str + offset, trash.len - offset); + http_msg_move_end(&txn->req, delta); + return 0; +} + +/* parse an http-request action among : + * set-method + * set-path + * set-query + * set-uri + * + * All of them accept a single argument of type string representing a log-format. + * The resulting rule makes use of arg->act.p[0..1] to store the log-format list + * head, and p[2] to store the action as an int (0=method, 1=path, 2=query, 3=uri). + * It returns 0 on success, < 0 on error. + */ +int parse_set_req_line(const char **args, int *orig_arg, struct proxy *px, struct http_req_rule *rule, char **err) +{ + int cur_arg = *orig_arg; + + rule->action = HTTP_REQ_ACT_CUSTOM_CONT; + + switch (args[0][4]) { + case 'm' : + *(int *)&rule->arg.act.p[2] = 0; + rule->action_ptr = http_action_set_req_line; + break; + case 'p' : + *(int *)&rule->arg.act.p[2] = 1; + rule->action_ptr = http_action_set_req_line; + break; + case 'q' : + *(int *)&rule->arg.act.p[2] = 2; + rule->action_ptr = http_action_set_req_line; + break; + case 'u' : + *(int *)&rule->arg.act.p[2] = 3; + rule->action_ptr = http_action_set_req_line; + break; + default: + memprintf(err, "internal error: unhandled action '%s'", args[0]); + return -1; + } + + if (!*args[cur_arg] || + (*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) { + memprintf(err, "expects exactly 1 argument "); + return -1; + } + + LIST_INIT((struct list *)&rule->arg.act.p[0]); + proxy->conf.args.ctx = ARGC_HRQ; + parse_logformat_string(args[cur_arg], proxy, (struct list *)&rule->arg.act.p[0], LOG_OPT_HTTP, + (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, + proxy->conf.args.file, proxy->conf.args.line); + + (*orig_arg)++; + return 0; +} + /* * Return the struct http_req_action_kw associated to a keyword. */ @@ -11715,6 +11858,9 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { }}; +/************************************************************************/ +/* All supported converter keywords must be declared here. */ +/************************************************************************/ /* Note: must not be declared as its list will be overwritten */ static struct sample_conv_kw_list sample_conv_kws = {ILH, { { "http_date", sample_conv_http_date, ARG1(0,SINT), NULL, SMP_T_UINT, SMP_T_STR}, @@ -11722,12 +11868,26 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, { { NULL, NULL, 0, 0, 0 }, }}; +/************************************************************************/ +/* All supported http-request action keywords must be declared here. */ +/************************************************************************/ +struct http_req_action_kw_list http_req_actions = { + .scope = "http", + .kw = { + { "set-method", parse_set_req_line }, + { "set-path", parse_set_req_line }, + { "set-query", parse_set_req_line }, + { "set-uri", parse_set_req_line }, + } +}; + __attribute__((constructor)) static void __http_protocol_init(void) { acl_register_keywords(&acl_kws); sample_register_fetches(&sample_fetch_keywords); sample_register_convs(&sample_conv_kws); + http_req_keywords_register(&http_req_actions); }