CGI: Use path wildcards with `bin_root` executables; look for "index.gmi" files

This commit is contained in:
Jaakko Keränen 2024-04-13 15:20:10 +03:00
parent a9923d43fc
commit 8ff7ab3d17
No known key found for this signature in database
GPG Key ID: BACCFCFB98DB2EDC
2 changed files with 32 additions and 13 deletions

View File

@ -47,6 +47,14 @@ The log can be viewed via journalctl (or syslog):
## Change log
### v0.7
* CGI: Fixed contents of `PATH_INFO`: it is now URL-decoded and the script path is removed so only the part following the script path is included in the environment variable (RFC 3875, section 4.1.5).
* CGI: `bin_root` applies a wildcard to all found CGI executables so `PATH_INFO` can be used.
* CGI: `bin_root` looks for executable "index.gmi" files in requested directories to provide the directory index.
* Skip the TLS SNI check when request hostname has a literal IP address. (SNI is not supposed to be used with literal addresses.)
* Respond with status 53 if an unknown hostname is requested. This helps reveal configuration errors where the correct hostnames are not specified.
### v0.6
* Added `stream` to the `[cgi.*]` section to enable streaming output mode where the output of the CGI child process is immediately sent to the client without any buffering. Streaming is not supported if the server is using multiple processes.
@ -57,11 +65,6 @@ v0.6.1:
* Static: Fixed unquoting URL paths when looking up files.
* Added example of printing Gemini meta line in CGI documentation.
v0.6.2:
* Skip the TLS SNI check when request hostname has a literal IP address. (SNI is not supposed to be used with literal addresses.)
* Respond with status 53 if an unknown hostname is requested. This helps reveal configuration errors where the correct hostnames are not specified.
### v0.5
* Added `processes` to the `[server]` section to configure how many request handler processes are started.

View File

@ -17,8 +17,8 @@ class CgiContext:
self.port = port
self.args = args
self.base_path = url_path
if self.base_path.endswith('/*'):
self.base_path = self.base_path[:-2]
if self.base_path.endswith('*'):
self.base_path = self.base_path[:-1]
self.work_dir = work_dir
self.is_streaming = is_streaming
@ -31,7 +31,11 @@ class CgiContext:
env_vars['REMOTE_ADDR'] = '%s:%d' % req.remote_address
if req.query != None:
env_vars['QUERY_STRING'] = req.query
env_vars['PATH_INFO'] = req.path
# PATH_INFO contains any additional subdirectories deeper than the script location.
path_info = urllib.parse.unquote(req.path)
if path_info.startswith(self.base_path):
path_info = path_info[len(self.base_path):]
env_vars['PATH_INFO'] = path_info
env_vars['SCRIPT_NAME'] = self.base_path
env_vars['SERVER_SOFTWARE'] = 'GmCapsule/' + gmcapsule.__version__
env_vars['SERVER_PROTOCOL'] = req.scheme.upper()
@ -110,13 +114,25 @@ class CgiTreeMapper:
def __call__(self, url_path):
# Check if url_path is a valid CGI entrypoint and return
# a CgiContext for it.
fn = str(self.root_dir) + url_path
root_dir = str(self.root_dir)
fn = root_dir + url_path
if self.protocol == 'titan':
fn += ',titan'
if os.path.isdir(fn):
return None
if os.access(fn, os.X_OK):
return CgiContext(self.port, url_path, [fn], work_dir=os.path.dirname(fn))
# Look for an executable up the path.
par_path = fn
par_url = url_path
while len(par_path) > len(root_dir):
# An executable 'index.gmi' is used for generating the index page.
if os.path.isdir(par_path):
if os.access(os.path.join(par_path, 'index.gmi'), os.X_OK):
return CgiContext(self.port, par_url + '*', [os.path.join(par_path, 'index.gmi')],
work_dir=par_path)
else:
return None
if os.access(par_path, os.X_OK):
return CgiContext(self.port, par_url + '*', [par_path], work_dir=os.path.dirname(par_path))
par_path = os.path.dirname(par_path)
par_url = os.path.dirname(par_url)
return None