I am trying to understand how path stripping works in httpd(8), particularly how FastCGI's SCRIPT_NAME parameter gets filled. The rule about whether it has a trailing slash or not seems inconsistent. I would really appreciate some extra eyes to work through this. I don't know if httpd is at fault, my app, or my understanding of CGI.
I am giving a webapp[1] a mountpoint on my site, using `request strip 3` to hide the mountpoint from the app. ``` # /etc/httpd.conf server "default" { listen on localhost port 80 directory auto index location "/path/to/app" { request strip 3 fastcgi socket :5232 } location "/path/to/app/*" { request strip 3 fastcgi socket :5232 } log syslog } ``` with this, I see: http://localhost/path/to/app => 'DOCUMENT_URI': '/path/to/app', 'PATH_INFO': '', 'REQUEST_URI': '/path/to/app', 'SCRIPT_NAME': '/path/to/app', http://localhost/path/to/app/ => 'DOCUMENT_URI': '/path/to/app/', 'PATH_INFO': '', 'REQUEST_URI': '/path/to/app/', 'SCRIPT_NAME': '/path/to/app/', http://localhost/path/to/app/login => 'DOCUMENT_URI': '/path/to/app/login', 'PATH_INFO': '/login', 'REQUEST_URI': '/path/to/app/login', 'SCRIPT_NAME': '/path/to/app', http://localhost/path/to/app/posts/1 => 'DOCUMENT_URI': '/path/to/app/posts/1', 'PATH_INFO': '/posts/1', 'REQUEST_URI': '/path/to/app/posts/1', 'SCRIPT_NAME': '/path/to/app', Up to the strip limit, SCRIPT_NAME doesn't have a trailing slash, after the strip limit it doesn't have a trailing slash, but *at* the strip limit it does. This is causing me angst because I *want* to use the simpler ``` # /etc/httpd.conf server "default" { listen on localhost port 80 directory auto index location "/path/to/app" { request rewrite "$DOCUMENT_URI/" } location "/path/to/app/*" { request strip 3 fastcgi socket :5232 } log syslog } ``` but with this http://localhost/path/to/app => 'DOCUMENT_URI': '/path/to/app/', 'PATH_INFO': '', 'REQUEST_URI': '/path/to/app', 'SCRIPT_NAME': '/path/to/app/', **which gives this warning** "WARNING: SCRIPT_NAME does not match REQUEST_URI" which is complaining that SCRIPT_NAME is not a prefix of REQUEST_URI. SCRIPT_NAME shouldn't have been touched, imo; my goal in `request rewrite "$DOCUMENT_URI/"` was to append to PATH_INFO -- and if I `request rewrite "$DOCUMENT_URI/login"` instead that's exactly what happens, PATH_INFO gets "/login" -- it's only when I add a single "/" that this problem crops up. Unrelated to the rewrite, the same underlying issue, that /path/to/app/ sets PATH_INFO="", also causes Radicale to mistakenly redirect /path/to/app/ to /path/to/app/app/.web [2], because it thinks that means it's being called as /path/to/app/. I don't know if httpd or Radicale is at fault here. I suspect this is an off-by-one in httpd [3] but I'd like to know if there's a better explanation for this behaviour. I think the better behaviour is http://localhost/path/to/app/ => 'DOCUMENT_URI': '/path/to/app/', 'PATH_INFO': '/', 'REQUEST_URI': '/path/to/app/', 'SCRIPT_NAME': '/path/to/app', but I am second-guessing myself a lot. Thank you for your time, and any clues you can toss my way -Nick [1] It's Radicale. But see below for my testing webapp that isolated the issue. [2] https://github.com/Kozea/Radicale/blob/db7587c59335fa00580ce88d583419ce45594143/radicale/app/get.py#L64-L69 [3] https://github.com/openbsd/src/blob/4564063e97c6de536114caf655a9e16da7a4259f/usr.sbin/httpd/server_fcgi.c#L215 # Appendix: Reproduction (OpenBSD 6.6) ``` $ doas pkg_add py3-flup $ cat app.fcgi #!/usr/bin/env python3 """ Python FastCGI example. Opens a FastCGI socket on localhost:5232 that just returns "Hello, World!" but while logging the FastCGI parameters. """ from flup.server.fcgi import WSGIServer from pprint import pprint import sys def application(environ, start_response): pprint(environ, stream=sys.stderr) start_response('200 OK', [('Content-Type', 'text/html')]) yield 'Hello, World!\r\n' if __name__ == "__main__": WSGIServer(application, bindAddress=("localhost", 5232)).run() $ chmod +x app.fcgi ``` ``` $ cat /etc/httpd.conf server "default" { listen on localhost port 80 directory auto index # Add a trailing slash so the app recognizes /base as its own name # as in https://wordpress.org/support/article/htaccess/ # or https://radicale.org/proxy/ location "/path/to/app" { request rewrite "$DOCUMENT_URI/" } location "/path/to/app/*" { request strip 3 fastcgi socket :5232 } log syslog } ``` Client: ``` $ curl -s -v http://localhost/path/to/app * Trying 127.0.0.1:80... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 80 (#0) > GET /path/to/app HTTP/1.1 > Host: localhost > User-Agent: curl/7.66.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Connection: keep-alive < Content-Type: text/html < Date: Mon, 17 Feb 2020 10:09:29 GMT < Server: OpenBSD httpd < Transfer-Encoding: chunked < Hello, World! * Connection #0 to host localhost left intact ``` Server: ``` $ doas httpd -d -v startup WARNING: SCRIPT_NAME does not match REQUEST_URI default 127.0.0.1 - - [17/Feb/2020:05:09:29 -0500] "GET /path/to/app HTTP/1.1" 200 0 ``` App: ``` $ ./app.fcgi WARNING: SCRIPT_NAME does not match REQUEST_URI{'DOCUMENT_ROOT': '/htdocs', 'DOCUMENT_URI': '/path/to/app/', 'GATEWAY_INTERFACE': 'CGI/1.1', 'HTTP_ACCEPT': '*/*', 'HTTP_HOST': 'localhost', 'HTTP_USER_AGENT': 'curl/7.66.0', 'PATH_INFO': '', 'QUERY_STRING': '', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '20707', 'REQUEST_METHOD': 'GET', 'REQUEST_URI': '/path/to/app', 'SCRIPT_FILENAME': '/htdocs/', 'SCRIPT_NAME': '/path/to/app/', 'SERVER_ADDR': '127.0.0.1', 'SERVER_NAME': 'default', 'SERVER_PORT': '80', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'OpenBSD httpd', 'wsgi.errors': <flup.server.fcgi_base.TeeOutputStream object at 0xe9a4835d690>, 'wsgi.input': <flup.server.fcgi_base.InputStream object at 0xe9a48355b50>, 'wsgi.multiprocess': False, 'wsgi.multithread': True, 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.version': (1, 0)} ```