File cgi.py of Package failed_python-web.py
"""
Minimal compatibility shim for the removed `cgi` stdlib module (Python 3.13+).
This provides a very small subset of the original `cgi.FieldStorage` behaviour
that is sufficient for simple application/x-www-form-urlencoded POST parsing
used by the web.py package during build.
It intentionally implements only:
- FieldStorage.__init__(fp=None, environ=None, keep_blank_values=False)
- FieldStorage.getfirst(name, default=None)
- FieldStorage.getvalue(name, default=None)
- FieldStorage.__getitem__(name)
This avoids requiring changes to the upstream web.py sources while allowing
the package to build on Python versions that do not include the stdlib cgi.
"""
from urllib.parse import parse_qs
from io import BytesIO
class FieldStorage:
"""
Lightweight FieldStorage replacement.
- If environ is provided and CONTENT_LENGTH > 0, it will read that many bytes
from fp (if fp provided) and parse them with urllib.parse.parse_qs.
- Parsed values are stored as lists (to mimic parse_qs) and getfirst/getvalue
return the first value or a provided default.
Note: This implementation only supports application/x-www-form-urlencoded
request bodies (typical for simple HTML forms). Multipart/form-data (file
uploads) is not supported by this shim.
"""
def __init__(self, fp=None, environ=None, keep_blank_values=False):
self._data = {}
self.list = [] # mimic real FieldStorage's .list to some degree
if environ is None:
return
try:
length = int(environ.get('CONTENT_LENGTH') or 0)
except Exception:
length = 0
raw = b''
if fp is not None and length > 0:
try:
# If fp is a text stream, attempt to read text and convert to bytes
raw = fp.read(length)
except TypeError:
# some streams expect no length; read all
try:
raw = fp.read()
except Exception:
raw = b''
# If we got text instead of bytes, convert to bytes
if isinstance(raw, str):
raw = raw.encode('utf-8', errors='replace')
# decode using charset if provided, otherwise utf-8 with replace
charset = 'utf-8'
content_type = environ.get('CONTENT_TYPE', '') or ''
if 'charset=' in content_type:
try:
charset = content_type.split('charset=')[-1].split(';', 1)[0].strip()
except Exception:
charset = 'utf-8'
try:
qs = raw.decode(charset, errors='replace')
except Exception:
qs = ''
if qs:
parsed = parse_qs(qs, keep_blank_values=keep_blank_values)
# store as lists to be similar to cgi.FieldStorage behavior
for k, v in parsed.items():
self._data[k] = v
self.list.append((k, v))
def getfirst(self, name, default=None):
v = self._data.get(name)
if v:
return v[0]
return default
def getvalue(self, name, default=None):
return self.getfirst(name, default)
def __getitem__(self, name):
v = self.getfirst(name)
if v is None:
raise KeyError(name)
return v
# Provide small helpers that some code might expect
def parse_qs(qs, keep_blank_values=False):
return parse_qs(qs, keep_blank_values=keep_blank_values) # type: ignore
# Keep module-level names that old code might import from cgi
FieldStorage = FieldStorage