httpserver: Rewrite without the cgi module

It is removed in Python 3.13
This commit is contained in:
Valentin Lorentz 2024-05-05 20:26:09 +02:00 committed by Val Lorentz
parent 9bcb21389a
commit 0ad61f5791

View File

@ -1,5 +1,5 @@
### ###
# Copyright (c) 2011-2021, Valentin Lorentz # Copyright (c) 2011-2024, Valentin Lorentz
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -32,8 +32,8 @@ An embedded and centralized HTTP server for Supybot's plugins.
""" """
import os import os
import cgi
import socket import socket
import urllib.parse
from threading import Thread from threading import Thread
import supybot.log as log import supybot.log as log
@ -164,6 +164,114 @@ def get_template(filename):
with open(path + '.example', 'r') as fd: with open(path + '.example', 'r') as fd:
return fd.read() return fd.read()
class HttpHeader:
__slots__ = ('name', 'value')
def __init__(self, name, value):
self.name = name
self.value = value
def __repr__(self):
"""Return printable representation."""
return "HttpHeader(%r, %r)" % (self.name, self.value)
class HttpHeaders:
"""Copy of `cgi.FieldStorage
<https://github.com/python/cpython/blob/v3.12.3/Lib/cgi.py#L512-L594>`
before it was removed from the stdlib.
"""
__slots__ = ('list',)
def __init__(self, headers):
self.list = headers
def __repr__(self):
return 'HttpHeaders(%r)' % self.list
def __iter__(self):
return iter(self.keys())
def __getattr__(self, name):
if name != 'value':
raise AttributeError(name)
if self.file:
self.file.seek(0)
value = self.file.read()
self.file.seek(0)
elif self.list is not None:
value = self.list
else:
value = None
return value
def __getitem__(self, key):
"""Dictionary style indexing."""
if self.list is None:
raise TypeError("not indexable")
found = []
for item in self.list:
if item.name == key: found.append(item)
if not found:
raise KeyError(key)
if len(found) == 1:
return found[0]
else:
return found
def getvalue(self, key, default=None):
"""Dictionary style get() method, including 'value' lookup."""
if key in self:
value = self[key]
if isinstance(value, list):
return [x.value for x in value]
else:
return value.value
else:
return default
def getfirst(self, key, default=None):
""" Return the first value received."""
if key in self:
value = self[key]
if isinstance(value, list):
return value[0].value
else:
return value.value
else:
return default
def getlist(self, key):
""" Return list of received values."""
if key in self:
value = self[key]
if isinstance(value, list):
return [x.value for x in value]
else:
return [value.value]
else:
return []
def keys(self):
"""Dictionary style keys() method."""
if self.list is None:
raise TypeError("not indexable")
return list(set(item.name for item in self.list))
def __contains__(self, key):
"""Dictionary style __contains__ method."""
if self.list is None:
raise TypeError("not indexable")
return any(item.name == key for item in self.list)
def __len__(self):
"""Dictionary style len(x) support."""
return len(self.keys())
def __bool__(self):
if self.list is None:
raise TypeError("Cannot be converted to bool.")
return bool(self.list)
class SupyHTTPRequestHandler(BaseHTTPRequestHandler): class SupyHTTPRequestHandler(BaseHTTPRequestHandler):
def do_X(self, callbackMethod, *args, **kwargs): def do_X(self, callbackMethod, *args, **kwargs):
if self.path == '/': if self.path == '/':
@ -199,12 +307,11 @@ class SupyHTTPRequestHandler(BaseHTTPRequestHandler):
if 'Content-Type' not in self.headers: if 'Content-Type' not in self.headers:
self.headers['Content-Type'] = 'application/x-www-form-urlencoded' self.headers['Content-Type'] = 'application/x-www-form-urlencoded'
if self.headers['Content-Type'] == 'application/x-www-form-urlencoded': if self.headers['Content-Type'] == 'application/x-www-form-urlencoded':
form = cgi.FieldStorage( length = min(100000, int(self.headers.get('Content-Length', '100000')))
fp=self.rfile, qs = self.rfile.read(length).decode()
headers=self.headers, form = HttpHeaders([
environ={'REQUEST_METHOD':'POST', HttpHeader(k, v) for (k, v) in urllib.parse.parse_qsl(qs)
'CONTENT_TYPE':self.headers['Content-Type'], ])
})
else: else:
content_length = int(self.headers.get('Content-Length', '0')) content_length = int(self.headers.get('Content-Length', '0'))
form = self.rfile.read(content_length) form = self.rfile.read(content_length)