1
0
mirror of https://github.com/apache/httpd.git synced 2026-10-23 22:49:01 +03:00

mod_negotiation: Do not send the qs parameter in the Content-Type.

A MultiViews response took the media type of the selected file as it
was, so the qs source quality set with AddType was sent to the client.
Drop it after the variant has been selected. Other parameters are kept.

PR: 53595
GitHub: closes #824


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1939268 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Arturo Bernal
2026-10-19 10:12:12 +00:00
parent 6f0c237673
commit 23104ca014
12 changed files with 106 additions and 1 deletions

View File

@@ -0,0 +1,2 @@
*) mod_negotiation: Do not send the "qs" source quality parameter of the selected
variant in the Content-Type of a response negotiated by MultiViews. PR 53595

View File

@@ -3068,6 +3068,41 @@ static int handle_map_file(request_rec *r)
return OK;
}
/* The "qs" parameter of a variant's media type is only its source quality
* for the selection, see get_entry(), and does not belong in the
* Content-Type of the response. Return content_type without it, all
* other parameters are kept as they are.
*/
static const char *strip_source_quality(apr_pool_t *p,
const char *content_type)
{
const char *line = content_type;
char *stripped;
int found = 0;
if (!ap_strcasestr(content_type, "qs")) {
return content_type;
}
stripped = ap_get_token(p, &line, 0);
while (*line == ';') {
const char *param = line++;
const char *name = ap_get_token(p, &line, 1);
apr_size_t len = strcspn(name, "= \t");
if (len == 2 && name[len] && !ap_cstr_casecmpn(name, "qs", 2)) {
found = 1;
}
else {
stripped = apr_pstrcat(p, stripped,
apr_pstrmemdup(p, param, line - param),
NULL);
}
}
return found ? apr_pstrcat(p, stripped, line, NULL) : content_type;
}
static int handle_multi(request_rec *r)
{
negotiation_state *neg;
@@ -3124,6 +3159,14 @@ static int handle_multi(request_rec *r)
/* now do a "fast redirect" ... promotes the sub_req into the main req */
ap_internal_fast_redirect(sub_req, r);
if (r->content_type) {
const char *type = strip_source_quality(r->pool, r->content_type);
if (type != r->content_type) {
ap_set_content_type_ex(r, type, AP_REQUEST_IS_TRUSTED_CT(r));
}
}
/* give no advise for time on this subrequest. Perhaps we
* should tally the last mtime among all variants, and date
* the most recent, but that could confuse the proxies.

View File

@@ -135,6 +135,15 @@
LanguagePriority zh-TW fr fu en de
</Directory>
<Directory @SERVERROOT@/htdocs/modules/negotiation/qs>
Options +MultiViews
AddType text/html;qs=0.9 .html
AddType application/xhtml+xml;qs=1.0 .xhtml
AddType "text/html;charset=utf-8;qs=0.8" .qsafter
AddType "text/html;QS=0.8;charset=utf-8" .qsbefore
AddType "application/example;foo=\"a;b\";q=0.7;qs=0.5;bar=\"c, d\"" .quoted
</Directory>
<IfDefine APACHE2>
<IfModule @CGI_MODULE@>
<Directory @SERVERROOT@/htdocs/modules/negotiation/query>

View File

@@ -0,0 +1 @@
after

View File

@@ -0,0 +1 @@
before

View File

@@ -0,0 +1 @@
html

View File

@@ -0,0 +1 @@
xhtml

View File

@@ -0,0 +1 @@
quoted

View File

@@ -0,0 +1 @@
html

View File

@@ -0,0 +1 @@
xht

View File

@@ -0,0 +1,5 @@
URI: tm1.htm
Content-Type: text/html; qs=0.9
URI: tm2.xht
Content-Type: application/xhtml+xml; qs=1.0

View File

@@ -3,12 +3,15 @@
Covers default-language selection, explicit variant requests, Accept-Language
obedience (plain + compressed + typemap), quality-rating preferences, a
non-existent highest-quality fallback, a typemap query-string case, and
Accept content-type negotiation (with 406 cases).
Accept content-type negotiation (with 406 cases), and the source quality
("qs") of a variant not showing up in the Content-Type of the response.
Perl original used ``need_module('negotiation') && need_cgi &&
need_module('mime')``.
"""
import re
import pytest
from apache_pytest import need_cgi, need_module, t_cmp
@@ -153,3 +156,39 @@ def test_content_type(http, accept, expected):
else:
assert t_cmp(r.status_code, 406), \
f"expect Not Acceptable for Accept: {accept}"
XML = "application/xhtml+xml"
# "qs" only decides between variants, the other parameters are kept
QS_PARAMS = [
("after", "text/html;charset=utf-8"),
("before", "text/html;charset=utf-8"),
("quoted", 'application/example;foo="a;b";q=0.7;bar="c, d"'),
]
def _content_type(resp):
assert resp.status_code == 200
return re.sub(r";\s+", ";", resp.headers["content-type"])
@need_module("negotiation", "mime")
@pytest.mark.parametrize("path", ["doc", "typemap.var"])
@pytest.mark.parametrize("accept,expected", [
(f"text/html, {XML}", XML),
("text/html", "text/html"),
(f"text/html, {XML};q=0.5", "text/html"),
])
def test_source_quality(http, path, accept, expected):
r = http.GET(f"/modules/negotiation/qs/{path}", headers={"Accept": accept})
assert t_cmp(_content_type(r), expected), \
"qs selects the variant but is not sent in Content-Type"
@need_module("negotiation", "mime")
@pytest.mark.parametrize("name,expected", QS_PARAMS, ids=[n for n, _ in QS_PARAMS])
def test_source_quality_parameters(http, name, expected):
r = http.GET(f"/modules/negotiation/qs/{name}", headers={"Accept": "*/*"})
assert t_cmp(_content_type(r), expected), \
"only qs is removed from the Content-Type"