aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKim Alvefur <zash@zash.se>2023-06-03 16:15:52 +0200
committerKim Alvefur <zash@zash.se>2023-06-03 16:15:52 +0200
commit99906e5b9c212beaf6cbc21580d67a5806fa4f24 (patch)
tree17f8ef39c3d86523c5fb0d3e28a10e87ff0df706
parent517f20b5230586df9baee3afb47982a54478d074 (diff)
downloadprosody-99906e5b9c212beaf6cbc21580d67a5806fa4f24.tar.gz
prosody-99906e5b9c212beaf6cbc21580d67a5806fa4f24.zip
util.http: Implement parser for RFC 7239 Forwarded header
Standardized and structured replacement for the X-Forwarded-For, X-Forwarded-Proto set of headers. Notably, this allows per-hop protocol information, unlike X-Forwarded-Proto which is always a single value for some reason.
-rw-r--r--doc/doap.xml1
-rw-r--r--spec/util_http_spec.lua21
-rw-r--r--util/http.lua33
3 files changed, 55 insertions, 0 deletions
diff --git a/doc/doap.xml b/doc/doap.xml
index dc4dcebc..af9711f5 100644
--- a/doc/doap.xml
+++ b/doc/doap.xml
@@ -56,6 +56,7 @@
<implements rdf:resource="https://www.rfc-editor.org/info/rfc6455"/>
<implements rdf:resource="https://www.rfc-editor.org/info/rfc6901"/>
<implements rdf:resource="https://www.rfc-editor.org/info/rfc7233"/>
+ <implements rdf:resource="https://www.rfc-editor.org/info/rfc7239"/>
<implements rdf:resource="https://www.rfc-editor.org/info/rfc7301"/>
<implements rdf:resource="https://www.rfc-editor.org/info/rfc7395"/>
<implements rdf:resource="https://www.rfc-editor.org/info/rfc7590"/>
diff --git a/spec/util_http_spec.lua b/spec/util_http_spec.lua
index c6087450..62679f0f 100644
--- a/spec/util_http_spec.lua
+++ b/spec/util_http_spec.lua
@@ -108,4 +108,25 @@ describe("util.http", function()
assert.is_(http.contains_token("fo o", "foo"));
end);
end);
+
+do
+ describe("parse_forwarded", function()
+ it("works", function()
+ assert.same({ { ["for"] = "[2001:db8:cafe::17]:4711" } }, http.parse_forwarded('For="[2001:db8:cafe::17]:4711"'), "case insensitive");
+
+ assert.same({ { ["for"] = "192.0.2.60"; proto = "http"; by = "203.0.113.43" } }, http.parse_forwarded('for=192.0.2.60;proto=http;by=203.0.113.43'),
+ "separated by semicolon");
+
+ assert.same({ { ["for"] = "192.0.2.43" }; { ["for"] = "198.51.100.17" } }, http.parse_forwarded('for=192.0.2.43, for=198.51.100.17'),
+ "Values from multiple proxy servers can be appended using a comma");
+
+ end)
+ it("rejects quoted quotes", function ()
+ assert.falsy(http.parse_forwarded('foo="bar\"bar'), "quoted quotes");
+ end)
+ pending("deals with quoted quotes", function ()
+ assert.same({ { foo = 'bar"baz' } }, http.parse_forwarded('foo="bar\"bar'), "quoted quotes");
+ end)
+ end)
+end
end);
diff --git a/util/http.lua b/util/http.lua
index 3852f91c..b21bf798 100644
--- a/util/http.lua
+++ b/util/http.lua
@@ -69,9 +69,42 @@ local function normalize_path(path, is_dir)
return path;
end
+--- Parse the RFC 7239 Forwarded header into array of key-value pairs.
+local function parse_forwarded(forwarded)
+ if type(forwarded) ~= "string" then
+ return nil;
+ end
+
+ local fwd = {}; -- array
+ local cur = {}; -- map, to which we add the next key-value pair
+ for key, quoted, value, delim in forwarded:gmatch("(%w+)%s*=%s*(\"?)([^,;\"]+)%2%s*(.?)") do
+ -- FIXME quoted quotes like "foo\"bar"
+ -- unlikely when only dealing with IP addresses
+ if quoted == '"' then
+ value = value:gsub("\\(.)", "%1");
+ end
+
+ cur[key:lower()] = value;
+ if delim == "" or delim == "," then
+ t_insert(fwd, cur)
+ if delim == "" then
+ -- end of the string
+ break;
+ end
+ cur = {};
+ elseif delim ~= ";" then
+ -- misparsed
+ return false;
+ end
+ end
+
+ return fwd;
+end
+
return {
urlencode = urlencode, urldecode = urldecode;
formencode = formencode, formdecode = formdecode;
contains_token = contains_token;
normalize_path = normalize_path;
+ parse_forwarded = parse_forwarded;
};