Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changes-entries/negotiation-qs-content-type.txt
Original file line number Diff line number Diff line change
@@ -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
43 changes: 43 additions & 0 deletions modules/mappers/mod_negotiation.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions test/pytest_suite/t/conf/extra.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
after
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
before
1 change: 1 addition & 0 deletions test/pytest_suite/t/htdocs/modules/negotiation/qs/doc.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
quoted
1 change: 1 addition & 0 deletions test/pytest_suite/t/htdocs/modules/negotiation/qs/tm1.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
html
1 change: 1 addition & 0 deletions test/pytest_suite/t/htdocs/modules/negotiation/qs/tm2.xht
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xht
5 changes: 5 additions & 0 deletions test/pytest_suite/t/htdocs/modules/negotiation/qs/typemap.var
Original file line number Diff line number Diff line change
@@ -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
41 changes: 40 additions & 1 deletion test/pytest_suite/tests/t/modules/test_negotiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Loading