MEDIUM: http: implement http-request set-{method,path,query,uri}

This commit implements the following new actions :

- "set-method" rewrites the request method with the result of the
  evaluation of format string <fmt>. 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 <fmt>. 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 <fmt>. 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 <fmt>. 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".

All of them are handled by the same parser and the same exec function,
which is why they're merged all together. For once, instead of adding
even more entries to the huge switch/case, we used the new facility to
register action keywords. A number of the existing ones should probably
move there as well.
This commit is contained in:
Willy Tarreau 2015-01-22 20:46:11 +01:00
parent d817e468bf
commit a0dc23f093
3 changed files with 201 additions and 1 deletions

View File

@ -2971,7 +2971,8 @@ http-request { allow | deny | tarpit | auth [realm <realm>] | redirect <rule> |
del-header <name> | set-nice <nice> | set-log-level <level> |
replace-header <name> <match-regex> <replace-fmt> |
replace-value <name> <match-regex> <replace-fmt> |
set-tos <tos> | set-mark <mark> |
set-method <fmt> | set-path <fmt> | set-query <fmt> |
set-uri <fmt> | set-tos <tos> | set-mark <mark> |
add-acl(<file name>) <key fmt> |
del-acl(<file name>) <key fmt> |
del-map(<file name>) <key fmt> |
@ -3081,6 +3082,42 @@ http-request { allow | deny | tarpit | auth [realm <realm>] | redirect <rule> |
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 <fmt>. 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 <fmt>. 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 <fmt>. 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 <fmt>. 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

View File

@ -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 {

View File

@ -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 <format>");
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 <const> 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);
}