Compare commits
5 Commits
b0f725749a
...
305b86ebd2
Author | SHA1 | Date |
---|---|---|
Jaakko Keränen | 305b86ebd2 | |
Jaakko Keränen | 8ff7ab3d17 | |
Jaakko Keränen | a9923d43fc | |
Jaakko Keränen | d1905e688c | |
Jaakko Keränen | cf91752b5b |
|
@ -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.
|
||||
|
|
|
@ -12,7 +12,7 @@ Extensibility is achieved with Python modules that get loaded at launch
|
|||
from the configured directories. A set of built-in extension modules is
|
||||
provided for common functionality like CGI and for serving static files.
|
||||
|
||||
The supported protocols are `Gemini <https://gemini.circumlunar.space>`_ and
|
||||
The supported protocols are `Gemini <https://geminiprotocol.net>`_ and
|
||||
`Titan <https://transjovian.org/titan>`_. Both are accepted via the same
|
||||
TCP port.
|
||||
|
||||
|
@ -210,6 +210,10 @@ bin_root : path
|
|||
with ``,titan`` (note: a comma), the entrypoint will use the Titan
|
||||
protocol instead of Gemini. The ``,titan`` suffix is omitted from the URL.
|
||||
|
||||
Executable files named `index.gmi` are assumed to be directory indices, so
|
||||
a request for the directory `DIR` will check for `DIR/index.gmi` and use
|
||||
it for generating the index page.
|
||||
|
||||
|
||||
cgi.*
|
||||
-----
|
||||
|
@ -502,7 +506,7 @@ from .gemini import Server, Cache, Context, Identity
|
|||
from .markdown import to_gemtext as markdown_to_gemtext
|
||||
|
||||
|
||||
__version__ = '0.6.1'
|
||||
__version__ = '0.7.0'
|
||||
__all__ = [
|
||||
'Config', 'Cache', 'Context', 'Identity',
|
||||
'get_mime_type', 'markdown_to_gemtext'
|
||||
|
|
|
@ -7,6 +7,7 @@ import importlib
|
|||
import os.path
|
||||
import select
|
||||
import socket
|
||||
import ipaddress
|
||||
import multiprocessing as mp
|
||||
import subprocess
|
||||
import threading
|
||||
|
@ -381,12 +382,23 @@ def handle_gemini_or_titan_request(request_data):
|
|||
report_error(stream, 59, "Invalid port number")
|
||||
return
|
||||
if not stream.get_servername():
|
||||
# Server name indication is required.
|
||||
report_error(stream, 59, "Missing TLS server name indication")
|
||||
return
|
||||
if stream.get_servername().decode() != hostname:
|
||||
report_error(stream, 53, "Proxy request refused")
|
||||
return
|
||||
# The hostname may be a literal IPv4/IPv6 address.
|
||||
try:
|
||||
ipaddress.ip_address(hostname)
|
||||
# No error during parsing, looks like a literal address.
|
||||
except ValueError:
|
||||
# Server name indication is required.
|
||||
report_error(stream, 59, "Missing TLS server name indication")
|
||||
return
|
||||
else:
|
||||
sni_name = stream.get_servername().decode()
|
||||
if sni_name != hostname:
|
||||
# SNI servername does not match the hostname in the URL. Misbehaving client?
|
||||
report_error(stream, 53, "Proxy request refused")
|
||||
return
|
||||
if sni_name not in worker.cfg.hostnames():
|
||||
report_error(stream, 53, f"Proxy request refused (domain \"{sni_name}\")")
|
||||
return
|
||||
|
||||
try:
|
||||
status, meta, body, from_cache = worker.context.call_entrypoint(Request(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue