From: Willy Tarreau Date: Wed, 19 Mar 2014 11:07:52 +0000 (+0100) Subject: MEDIUM: compression: consider the "q=" attribute in Accept-Encoding X-Git-Tag: v1.5-dev23~76 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0e9b1b4d1f0efc5e46a10371d9be21e97581faab;p=thirdparty%2Fhaproxy.git MEDIUM: compression: consider the "q=" attribute in Accept-Encoding Till now we didn't consider "q=". It's problematic because the first effect is that compression tokens were not even matched if it was present. It is important to parse it correctly because we still want to allow a user-agent to send "q=0" to explicitly disable a compressor, or to specify its preferences. Now, q-values are respected in order of precedence, and when several q-values are equal, the first occurrence is used. --- diff --git a/src/proto_http.c b/src/proto_http.c index 89e75b7172..0bca45cfe3 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -2144,6 +2144,38 @@ static inline int http_skip_chunk_crlf(struct http_msg *msg) return 1; } +/* Parses a qvalue and returns it multipled by 1000, from 0 to 1000. If the + * value is larger than 1000, it is bound to 1000. The parser consumes up to + * 1 digit, one dot and 3 digits and stops on the first invalid character. + * Unparsable qvalues return 1000 as "q=1.000". + */ +int parse_qvalue(const char *qvalue) +{ + int q = 1000; + + if (!isdigit(*qvalue)) + goto out; + q = (*qvalue++ - '0') * 1000; + + if (*qvalue++ != '.') + goto out; + + if (!isdigit(*qvalue)) + goto out; + q += (*qvalue++ - '0') * 100; + + if (!isdigit(*qvalue)) + goto out; + q += (*qvalue++ - '0') * 10; + + if (!isdigit(*qvalue)) + goto out; + q += (*qvalue++ - '0') * 1; + out: + if (q > 1000) + q = 1000; + return q; +} /* * Selects a compression algorithm depending on the client request. @@ -2175,26 +2207,71 @@ int select_compression_request_header(struct session *s, struct buffer *req) /* search for the algo in the backend in priority or the frontend */ if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) || (s->fe->comp && (comp_algo_back = s->fe->comp->algos))) { + int best_q = 0; + ctx.idx = 0; while (http_find_header2("Accept-Encoding", 15, req->p, &txn->hdr_idx, &ctx)) { - for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) { - if (word_match(ctx.line + ctx.val, ctx.vlen, comp_algo->name, comp_algo->name_len)) { - s->comp_algo = comp_algo; + const char *qval; + int q; + int toklen; + + /* try to isolate the token from the optional q-value */ + toklen = 0; + while (toklen < ctx.vlen && http_is_token[(unsigned char)*(ctx.line + ctx.val + toklen)]) + toklen++; + + qval = ctx.line + ctx.val + toklen; + while (1) { + while (qval < ctx.line + ctx.val + ctx.vlen && http_is_lws[(unsigned char)*qval]) + qval++; + + if (qval >= ctx.line + ctx.val + ctx.vlen || *qval != ';') { + qval = NULL; + break; + } + qval++; - /* remove all occurrences of the header when "compression offload" is set */ + while (qval < ctx.line + ctx.val + ctx.vlen && http_is_lws[(unsigned char)*qval]) + qval++; - if ((s->be->comp && s->be->comp->offload) || - (s->fe->comp && s->fe->comp->offload)) { - http_remove_header2(msg, &txn->hdr_idx, &ctx); - ctx.idx = 0; - while (http_find_header2("Accept-Encoding", 15, req->p, &txn->hdr_idx, &ctx)) { - http_remove_header2(msg, &txn->hdr_idx, &ctx); - } - } - return 1; + if (qval >= ctx.line + ctx.val + ctx.vlen) { + qval = NULL; + break; } + if (strncmp(qval, "q=", MIN(ctx.line + ctx.val + ctx.vlen - qval, 2)) == 0) + break; + + while (qval < ctx.line + ctx.val + ctx.vlen && *qval != ';') + qval++; + } + + /* here we have qval pointing to the first "q=" attribute or NULL if not found */ + q = qval ? parse_qvalue(qval + 2) : 1000; + + if (q <= best_q) + continue; + + for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) { + if (*(ctx.line + ctx.val) == '*' || + word_match(ctx.line + ctx.val, toklen, comp_algo->name, comp_algo->name_len)) { + s->comp_algo = comp_algo; + best_q = q; + break; + } + } + } + } + + /* remove all occurrences of the header when "compression offload" is set */ + if (s->comp_algo) { + if ((s->be->comp && s->be->comp->offload) || (s->fe->comp && s->fe->comp->offload)) { + http_remove_header2(msg, &txn->hdr_idx, &ctx); + ctx.idx = 0; + while (http_find_header2("Accept-Encoding", 15, req->p, &txn->hdr_idx, &ctx)) { + http_remove_header2(msg, &txn->hdr_idx, &ctx); } } + return 1; } /* identity is implicit does not require headers */