Skip to content

Commit 714c85d

Browse files
authored
fix(lib): enable multiple consecutive slash support (#1155)
* fix(lib): enable multiple consecutive slash support Closes #754 Closes #808 Closes #815 Apparently more applications use multiple slashes in a row than I thought. There is no easy way around this other than to do this hacky fix to avoid net/http#ServeMux's URL cleaning. * test(double_slash): add sourceware case Signed-off-by: Xe Iaso <[email protected]> * test(lib): fix tests for double slash fix Signed-off-by: Xe Iaso <[email protected]> --------- Signed-off-by: Xe Iaso <[email protected]> Signed-off-by: Xe Iaso <[email protected]>
1 parent 75ea1b6 commit 714c85d

File tree

11 files changed

+307
-6
lines changed

11 files changed

+307
-6
lines changed

.github/workflows/smoke-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ jobs:
1414
strategy:
1515
matrix:
1616
test:
17+
- double_slash
1718
- forced-language
1819
- git-clone
1920
- git-push

docs/docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2929
- Fixes concurrency problems with very old browsers ([#1082](https://github.com/TecharoHQ/anubis/issues/1082)).
3030
- Randomly use the Refresh header instead of the meta refresh tag in the metarefresh challenge.
3131
- Update OpenRC service to truncate the runtime directory before starting Anubis.
32+
- Allow multiple consecutive slashes in a row in application paths ([#754](https://github.com/TecharoHQ/anubis/issues/754)).
3233
- Add option to set `targetSNI` to special keyword 'auto' to indicate that it should be automatically set to the request Host name ([424](https://github.com/TecharoHQ/anubis/issues/424)).
3334

3435
### Bug Fixes

lib/anubis_test.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ func TestBasePrefix(t *testing.T) {
457457
}{
458458
{
459459
name: "no prefix",
460-
basePrefix: "/",
460+
basePrefix: "",
461461
path: "/.within.website/x/cmd/anubis/api/make-challenge",
462462
expected: "/.within.website/x/cmd/anubis/api/make-challenge",
463463
},
@@ -499,9 +499,15 @@ func TestBasePrefix(t *testing.T) {
499499
}
500500

501501
q := req.URL.Query()
502-
q.Set("redir", tc.basePrefix)
502+
redir := tc.basePrefix
503+
if tc.basePrefix == "" {
504+
redir = "/"
505+
}
506+
q.Set("redir", redir)
503507
req.URL.RawQuery = q.Encode()
504508

509+
t.Log(req.URL.String())
510+
505511
// Test API endpoint with prefix
506512
resp, err := cli.Do(req)
507513
if err != nil {
@@ -513,8 +519,15 @@ func TestBasePrefix(t *testing.T) {
513519
t.Errorf("expected status code %d, got: %d", http.StatusOK, resp.StatusCode)
514520
}
515521

522+
data, err := io.ReadAll(resp.Body)
523+
if err != nil {
524+
t.Fatalf("can't read body: %v", err)
525+
}
526+
527+
t.Log(string(data))
528+
516529
var chall challengeResp
517-
if err := json.NewDecoder(resp.Body).Decode(&chall); err != nil {
530+
if err := json.NewDecoder(bytes.NewBuffer(data)).Decode(&chall); err != nil {
518531
t.Fatalf("can't read challenge response body: %v", err)
519532
}
520533

@@ -535,7 +548,7 @@ func TestBasePrefix(t *testing.T) {
535548
nonce++
536549
}
537550
elapsedTime := 420
538-
redir := "/"
551+
redir = "/"
539552

540553
cli.CheckRedirect = func(req *http.Request, via []*http.Request) error {
541554
return http.ErrUseLastResponse

lib/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func New(opts Options) (*Server, error) {
107107
opts.ED25519PrivateKey = priv
108108
}
109109

110-
anubis.BasePrefix = opts.BasePrefix
110+
anubis.BasePrefix = strings.TrimRight(opts.BasePrefix, "/")
111111
anubis.PublicUrl = opts.PublicUrl
112112

113113
result := &Server{

lib/http.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,12 @@ func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg s
279279
}
280280

281281
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
282-
s.mux.ServeHTTP(w, r)
282+
if strings.HasPrefix(r.URL.Path, anubis.BasePrefix+anubis.StaticPath) {
283+
s.mux.ServeHTTP(w, r)
284+
return
285+
}
286+
287+
s.maybeReverseProxyOrPage(w, r)
283288
}
284289

285290
func (s *Server) stripBasePrefixFromRequest(r *http.Request) *http.Request {

test/cmd/httpdebug/main.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"log"
7+
"log/slog"
8+
"net/http"
9+
)
10+
11+
var (
12+
bind = flag.String("bind", ":3923", "TCP port to bind to")
13+
)
14+
15+
func main() {
16+
flag.Parse()
17+
18+
slog.Info("listening", "url", "http://localhost"+*bind)
19+
log.Fatal(http.ListenAndServe(*bind, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20+
slog.Info("got request", "method", r.Method, "path", r.RequestURI)
21+
22+
fmt.Fprintln(w, r.Method, r.RequestURI)
23+
r.Header.Write(w)
24+
})))
25+
}

test/double_slash/anubis.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
bots:
2+
- name: challenge
3+
user_agent_regex: CHALLENGE
4+
action: CHALLENGE
5+
6+
status_codes:
7+
CHALLENGE: 200
8+
DENY: 403

test/double_slash/input.txt

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/wiki//bin
2+
/wiki//boot
3+
/wiki//dev
4+
/wiki//dev/de
5+
/wiki//dev/en
6+
/wiki//dev/en-ca
7+
/wiki//dev/es
8+
/wiki//dev/fr
9+
/wiki//dev/hr
10+
/wiki//dev/hu
11+
/wiki//dev/it
12+
/wiki//dev/ja
13+
/wiki//dev/ko
14+
/wiki//dev/pl
15+
/wiki//dev/pt-br
16+
/wiki//dev/ro
17+
/wiki//dev/ru
18+
/wiki//dev/sv
19+
/wiki//dev/uk
20+
/wiki//dev/zh-cn
21+
/wiki//etc
22+
/wiki//etc/conf.d
23+
/wiki//etc/env.d
24+
/wiki//etc/fstab
25+
/wiki//etc/fstab/de
26+
/wiki//etc/fstab/en
27+
/wiki//etc/fstab/es
28+
/wiki//etc/fstab/fr
29+
/wiki//etc/fstab/hu
30+
/wiki//etc/fstab/it
31+
/wiki//etc/fstab/ja
32+
/wiki//etc/fstab/ko
33+
/wiki//etc/fstab/ru
34+
/wiki//etc/fstab/sv
35+
/wiki//etc/fstab/uk
36+
/wiki//etc/fstab/zh-cn
37+
/wiki//etc/hosts
38+
/wiki//etc/local.d
39+
/wiki//etc/make.conf
40+
/wiki//etc/portage
41+
/wiki//etc/portage/bashrc
42+
/wiki//etc/portage/Bashrc
43+
/wiki//etc/portage/binrepos.conf
44+
/wiki//etc/portage/binrepos.conf/en
45+
/wiki//etc/portage/binrepos.conf/hu
46+
/wiki//etc/portage/binrepos.conf/ja
47+
/wiki//etc/portage/binrepos.conf/ru
48+
/wiki//etc/portage/categories
49+
/wiki//etc/portage/color.map
50+
/wiki//etc/portage/env
51+
/wiki//etc/portage/img/ico.png
52+
/wiki//etc/portage/license_groups
53+
/wiki//etc/portage/make.conf
54+
/wiki//etc/portage/make.conf/de
55+
/wiki//etc/portage/make.conf/de/etc/portage/make.conf
56+
/wiki//etc/portage/make.conf/en
57+
/wiki//etc/portage/make.conf/es
58+
/wiki//etc/portage/make.conf/fr
59+
/wiki//etc/portage/make.conf/hu
60+
/wiki//etc/portage/make.conf/it
61+
/wiki//etc/portage/make.conf/it/var/db/repos/gentoo/licenses
62+
/wiki//etc/portage/make.conf/ja
63+
/wiki//etc/portage/make.conf/pl
64+
/wiki//etc/portage/make.conf/ru
65+
/wiki//etc/portage/make.conf/uk
66+
/wiki//etc/portage/make.conf/zh-cn
67+
/wiki//etc/portage/make.profile
68+
/wiki//etc/portage/mirrors
69+
/wiki//etc/portage/modules
70+
/wiki//etc/portage/package.accept_keywords
71+
/wiki//etc/portage/package.env
72+
/wiki//etc/portage/package.license
73+
/wiki//etc/portage/package.license/en
74+
/wiki//etc/portage/package.license/es
75+
/wiki//etc/portage/package.license/hu
76+
/wiki//etc/portage/package.license/ja
77+
/wiki//etc/portage/package.mask
78+
/wiki//etc/portage/package.mask/en
79+
/wiki//etc/portage/package.mask/hu
80+
/wiki//etc/portage/package.mask/ja
81+
/wiki//etc/portage/package.properties
82+
/wiki//etc/portage/package.unmask
83+
/wiki//etc/portage/package.use
84+
/wiki//etc/portage/package.use/de
85+
/wiki//etc/portage/package.use/en
86+
/wiki//etc/portage/package.use/es
87+
/wiki//etc/portage/package.use/fr
88+
/wiki//etc/portage/package.use/hu
89+
/wiki//etc/portage/package.use/it
90+
/wiki//etc/portage/package.use/ja
91+
/wiki//etc/portage/package.use/ru
92+
/wiki//etc/portage/package.use/uk
93+
/wiki//etc/portage/package.use/zh-cn
94+
/wiki//etc/portage/patches
95+
/wiki//etc/portage/profile/make.defaults
96+
/wiki//etc/portage/profile/package.provided
97+
/wiki//etc/portage/profile/package.provided/etc/portage/profile/package.provided
98+
/wiki//etc/portage/profile/package.provided/etc/portage/profiles/package.provided
99+
/wiki//etc/portage/profile/package.use.mask
100+
/wiki//etc/portage/profiles/package.provided
101+
/wiki//etc/portage/profiles/package.use.mask
102+
/wiki//etc/portage/profiles/package.use.mask/etc/portage/profile/package.use.mask
103+
/wiki//etc/portage/profiles/package.use.mask/etc/portage/profiles/package.use.mask
104+
/wiki//etc/portage/profiles/use.mask
105+
/wiki//etc/portage/profile/use.mask
106+
/wiki//etc/portage/repos.conf
107+
/wiki//etc/portage/repos.conf/brother-overlay.conf
108+
/wiki//etc/portage/repos.conf/de
109+
/wiki//etc/portage/repos.conf/en
110+
/wiki//etc/portage/repos.conf/es
111+
/wiki//etc/portage/repos.conf/etc/portage/repos.conf/gentoo.conf
112+
/wiki//etc/portage/repos.conf/fr
113+
/wiki//etc/portage/repos.conf/fr/etc/portage/repos.conf/gentoo.conf
114+
/wiki//etc/portage/repos.conf/gentoo.conf
115+
/wiki//etc/portage/repos.conf/gentoo.conf/etc/portage/repos.conf/gentoo.conf
116+
/wiki//etc/portage/repos.conf/hr
117+
/wiki//etc/portage/repos.conf/hu
118+
/wiki//etc/portage/repos.conf/it
119+
/wiki//etc/portage/repos.conf/ja
120+
/wiki//etc/portage/repos.conf/ko
121+
/wiki//etc/portage/repos.conf/pl
122+
/wiki//etc/portage/repos.conf/pt-br
123+
/wiki//etc/portage/repos.conf/ru
124+
/wiki//etc/portage/repos.conf/uk
125+
/wiki//etc/portage/repos.conf/zh-cn
126+
/wiki//etc/portage/savedconfig
127+
/wiki//etc/portage/sets
128+
/wiki//etc/profile
129+
/wiki//etc/profile.env
130+
/wiki//etc/sandbox.conf
131+
/wiki//home
132+
/wiki//lib
133+
/wiki//lib64
134+
/wiki//media
135+
/wiki//mnt
136+
/wiki//opt
137+
/wiki//proc
138+
/wiki//proc/config.gz
139+
/wiki//run
140+
/wiki//sbin
141+
/wiki//srv
142+
/wiki//sys
143+
/wiki//tmp
144+
/wiki//usr
145+
/wiki//usr/bin
146+
/wiki//usr_move
147+
/wiki//usr/portage
148+
/wiki//usr/portage/distfiles
149+
/wiki//usr/portage/licenses
150+
/wiki//usr/portage/metadata
151+
/wiki//usr/portage/metadata/md5-cache
152+
/wiki//usr/portage/metadata/md5-cache/usr/portage/metadata/md5-cache
153+
/wiki//usr/portage/metadata/md5-cache/var/db/repos/gentoo//metadata/md5-cache
154+
/wiki//usr/portage/packages
155+
/wiki//usr/portage/profiles
156+
/wiki//usr/portage/profiles/license_groups
157+
/wiki//usr/portage/profiles/license_groups/usr/portage/profiles/license_groups
158+
/wiki//usr/portage/profiles/license_groups/var/db/repos/gentoo//profiles/license_groups
159+
/wiki//usr/share/doc/
160+
/wiki//var/cache/binpkgs
161+
/wiki//var/cache/distfiles
162+
/wiki//var/db/pkg
163+
/wiki//var/db/pkg%22
164+
/wiki//var/db/repos/gentoo
165+
/wiki//var/db/repos/gentoo/licenses
166+
/wiki//var/db/repos/gentoo/licenses/var/db/repos/gentoo//licenses
167+
/wiki//var/db/repos/gentoo/licenses/var/db/repos/gentoo/licenses
168+
/wiki//var/db/repos/gentoo/metadata
169+
/wiki//var/db/repos/gentoo/metadata/md5-cache
170+
/wiki//var/db/repos/gentoo/metadata/var/db/repos/gentoo//metadata
171+
/wiki//var/db/repos/gentoo/metadata/var/db/repos/gentoo/metadata
172+
/wiki//var/db/repos/gentoo/profiles
173+
/wiki//var/db/repos/gentoo/profiles/license_groups
174+
/wiki//var/db/repos/gentoo/profiles/package.mask
175+
/wiki//var/lib/portage
176+
/wiki//var/lib/portage/world
177+
/wiki//var/run
178+
/gcc-bugs/[email protected]%2Fbugzilla%2F/T/

test/double_slash/test.mjs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { createReadStream } from "fs";
2+
import { createInterface } from "readline";
3+
4+
async function getPage(path) {
5+
return fetch(`http://localhost:8923${path}`)
6+
.then(resp => {
7+
if (resp.status !== 200) {
8+
throw new Error(`wanted status 200, got status: ${resp.status}`);
9+
}
10+
return resp;
11+
})
12+
.then(resp => resp.text());
13+
}
14+
15+
(async () => {
16+
const fin = createReadStream("input.txt");
17+
const rl = createInterface({
18+
input: fin,
19+
crlfDelay: Infinity,
20+
});
21+
22+
const resultSheet = {};
23+
24+
let failed = false;
25+
26+
for await (const line of rl) {
27+
console.log(line);
28+
29+
const resp = await getPage(line);
30+
resultSheet[line] = {
31+
match: resp.includes(`GET ${line}`),
32+
line: resp.split("\n")[0],
33+
};
34+
}
35+
36+
for (let [k, v] of Object.entries(resultSheet)) {
37+
if (!v.match) {
38+
failed = true;
39+
}
40+
41+
console.debug({ path: k, results: v });
42+
}
43+
44+
process.exit(failed ? 1 : 0);
45+
})();

test/double_slash/test.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
function cleanup() {
6+
pkill -P $$
7+
}
8+
9+
trap cleanup EXIT SIGINT
10+
11+
# Build static assets
12+
(cd ../.. && npm ci && npm run assets)
13+
14+
go tool anubis --help 2>/dev/null || :
15+
16+
go run ../cmd/httpdebug &
17+
18+
go tool anubis \
19+
--policy-fname ./anubis.yaml \
20+
--use-remote-address \
21+
--target=http://localhost:3923 &
22+
23+
backoff-retry node ./test.mjs

0 commit comments

Comments
 (0)