commit ee4d108b8abd15b7f3816260d0927ad3cc3d1536
parent f05af1e5e9433435c1c81dbdfd0e6b4ee89cfea6
Author: xfnw <xfnw@ttm.sh>
Date: Thu, 24 Dec 2020 15:40:17 -0500
use spaces instead of tabs
Diffstat:
M | gem.css | | | 42 | +++++++++++++++++++++--------------------- |
M | gem2html.py | | | 140 | ++++++++++++++++++++++++++++++++++++++++---------------------------------------- |
M | index.cgi | | | 274 | ++++++++++++++++++++++++++++++++++++++++---------------------------------------- |
M | mimeparse.py | | | 118 | ++++++++++++++++++++++++++++++++++++++++---------------------------------------- |
4 files changed, 287 insertions(+), 287 deletions(-)
diff --git a/gem.css b/gem.css
@@ -10,7 +10,7 @@ padding: 10px;
}
h1, h2, h3 {
- font-weight: 100;
+ font-weight: 100;
}
a {
@@ -34,54 +34,54 @@ overflow:auto;
}
mark {
- background-color: #dc5;
+ background-color: #dc5;
}
@font-face {
- font-family: 'VT323';
- src: url(https://xfnw.ttm.sh/assets/PressStart2P-Regular.ttf);
+ font-family: 'VT323';
+ src: url(https://xfnw.ttm.sh/assets/PressStart2P-Regular.ttf);
}
summary {
- cursor: pointer;
- padding: 10px;
+ cursor: pointer;
+ padding: 10px;
}
th, summary {
- background-color: #222;
+ background-color: #222;
}
th, td, details {
- border: 1px solid #222;
+ border: 1px solid #222;
}
th, td {
- padding: 5px;
+ padding: 5px;
}
table, details *:not(summary) {
- border-collapse: collapse;
- margin: 10px;
+ border-collapse: collapse;
+ margin: 10px;
}
.flex {
- display: flex;
- flex-wrap: wrap;
+ display: flex;
+ flex-wrap: wrap;
}
.flex div {
- display:flex;
- min-width: 10em;
- width: 100%;
- margin: 10px;
- flex: 1 1 0;
- flex-direction: column;
+ display:flex;
+ min-width: 10em;
+ width: 100%;
+ margin: 10px;
+ flex: 1 1 0;
+ flex-direction: column;
}
.flex div h3 {
- margin: 0;
+ margin: 0;
}
.flex div p {
- flex: 1 1 0;
+ flex: 1 1 0;
}
diff --git a/gem2html.py b/gem2html.py
@@ -6,17 +6,17 @@ _rand_n = lambda: functools.reduce(lambda x, y: (x<<8)+y,os.urandom(4))
ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz"
USED_IDS = set()
def rand_id():
- n = _rand_n()
- id = ""
- while n>0:
- n, index = divmod(n,len(ALPHABET))
- id = ALPHABET[index]+id
- if id in USED_IDS: return rand_id()
- return id
+ n = _rand_n()
+ id = ""
+ while n>0:
+ n, index = divmod(n,len(ALPHABET))
+ id = ALPHABET[index]+id
+ if id in USED_IDS: return rand_id()
+ return id
def gem2html(content,link_callback=lambda url, text: (url, text)):
- lines = content.splitlines()
- out = """<!DOCTYPE html>
+ lines = content.splitlines()
+ out = """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
@@ -25,64 +25,64 @@ def gem2html(content,link_callback=lambda url, text: (url, text)):
<body>
"""
- pre = False
- pre_alt = False
- set_title = False
- for line in lines:
- if pre:
- if line[:3]=="```":
- pre=False
- out+="</pre>\n"
- if pre_alt:
- out+="</figure>\n"
- pre_alt=False
- else:
- out+=escape(line)+"\n"
- else:
- if line[:3]=="```":
- if len(line)>3:
- cap_id = rand_id()
- out+="<figure role='img' aria-captionedby='{0}'><figcaption id='{0}' style='clip: rect(0 0 0 0); clip-path: inset(50%); height: 1px; overflow: hidden; position: absolute; white-space: nowrap; width: 1px;'>{1}</figcaption>\n".format(cap_id,escape(line[3:]))
- pre_alt = True
- pre = True
- out+="<pre>\n"
- elif line.startswith("#"):
- if line[:3]=="###":
- out+="<h3>{}</h3>".format(escape(line[3:].strip()))
- elif line[:2]=="##":
- out+="<h2>{}</h2>".format(escape(line[2:].strip()))
- elif line[:1]=="#":
- out+="<h1>{}</h1>".format(escape(line[1:].strip()))
- if not set_title:
- out+="<title>{}</title>".format(escape(line[1:].strip()))
- set_title = True
- elif line.startswith("* "):
- out += "<ul>\n<li>{}</li>\n</ul>\n".format(escape(line[1:].strip()))
- # combine consecutive unordered list items into one unordered list
- out = out.replace("</ul>\n<ul>\n","")
- elif line.startswith("=>"):
- parts = line.split(None,2)
- try:
- url, text = parts[1:]
- except ValueError:
- try:
- url=parts[1]
- text=parts[1]
- except:
- # no link content at all
- # just put a literal => in there
- out+="<p></p>".format(escape(parts[0]))
- continue
- # now comes the fun part, use the link callback to mutilate these
- url, text = link_callback(url, text)
- # and now render
- out+="<p><a href='{}'>{}</a></p>".format(escape(url),escape(text))
- elif line.startswith(">"):
- out+="<blockquote><p>{}</p></blockquote>".format(escape(line))
- else: # any other line is a text line
- if line:
- out+="<p>{}</p>".format(escape(line))
- else:
- out+="<p><br></p>"
- out+="</body>"
- return out
+ pre = False
+ pre_alt = False
+ set_title = False
+ for line in lines:
+ if pre:
+ if line[:3]=="```":
+ pre=False
+ out+="</pre>\n"
+ if pre_alt:
+ out+="</figure>\n"
+ pre_alt=False
+ else:
+ out+=escape(line)+"\n"
+ else:
+ if line[:3]=="```":
+ if len(line)>3:
+ cap_id = rand_id()
+ out+="<figure role='img' aria-captionedby='{0}'><figcaption id='{0}' style='clip: rect(0 0 0 0); clip-path: inset(50%); height: 1px; overflow: hidden; position: absolute; white-space: nowrap; width: 1px;'>{1}</figcaption>\n".format(cap_id,escape(line[3:]))
+ pre_alt = True
+ pre = True
+ out+="<pre>\n"
+ elif line.startswith("#"):
+ if line[:3]=="###":
+ out+="<h3>{}</h3>".format(escape(line[3:].strip()))
+ elif line[:2]=="##":
+ out+="<h2>{}</h2>".format(escape(line[2:].strip()))
+ elif line[:1]=="#":
+ out+="<h1>{}</h1>".format(escape(line[1:].strip()))
+ if not set_title:
+ out+="<title>{}</title>".format(escape(line[1:].strip()))
+ set_title = True
+ elif line.startswith("* "):
+ out += "<ul>\n<li>{}</li>\n</ul>\n".format(escape(line[1:].strip()))
+ # combine consecutive unordered list items into one unordered list
+ out = out.replace("</ul>\n<ul>\n","")
+ elif line.startswith("=>"):
+ parts = line.split(None,2)
+ try:
+ url, text = parts[1:]
+ except ValueError:
+ try:
+ url=parts[1]
+ text=parts[1]
+ except:
+ # no link content at all
+ # just put a literal => in there
+ out+="<p></p>".format(escape(parts[0]))
+ continue
+ # now comes the fun part, use the link callback to mutilate these
+ url, text = link_callback(url, text)
+ # and now render
+ out+="<p><a href='{}'>{}</a></p>".format(escape(url),escape(text))
+ elif line.startswith(">"):
+ out+="<blockquote><p>{}</p></blockquote>".format(escape(line))
+ else: # any other line is a text line
+ if line:
+ out+="<p>{}</p>".format(escape(line))
+ else:
+ out+="<p><br></p>"
+ out+="</body>"
+ return out
diff --git a/index.cgi b/index.cgi
@@ -6,94 +6,94 @@ uses_relative.append("gemini")
uses_netloc.append("gemini")
class StartComparison(str):
- def __eq__(self,lhs):
- return lhs.startswith(self)
+ def __eq__(self,lhs):
+ return lhs.startswith(self)
class ResponseCodes:
- # use like: response.status==ResponseCodes.GENERIC_SUCCESS
- GENERIC_INPUT = StartComparison("1")
- GENERIC_SUCCESS = StartComparison("2")
- GENERIC_REDIRECT = StartComparison("3")
- GENERIC_TEMPFAIL = StartComparison("4")
- GENERIC_PERMFAIL = StartComparison("5")
- GENERIC_CERTFAIL = StartComparison("6")
+ # use like: response.status==ResponseCodes.GENERIC_SUCCESS
+ GENERIC_INPUT = StartComparison("1")
+ GENERIC_SUCCESS = StartComparison("2")
+ GENERIC_REDIRECT = StartComparison("3")
+ GENERIC_TEMPFAIL = StartComparison("4")
+ GENERIC_PERMFAIL = StartComparison("5")
+ GENERIC_CERTFAIL = StartComparison("6")
- # the rest of these are just normal strings
- INPUT_NEEDED = "10"
- INPUT_NEEDED_SENSITIVE = "11"
+ # the rest of these are just normal strings
+ INPUT_NEEDED = "10"
+ INPUT_NEEDED_SENSITIVE = "11"
- SUCCESS = "20"
+ SUCCESS = "20"
- REDIRECT_TEMP = "30"
- REDIRECT_PERM = "31"
+ REDIRECT_TEMP = "30"
+ REDIRECT_PERM = "31"
- TEMPFAIL_GENERIC = "40"
- TEMPFAIL_SERVER_UNAVAILABLE = "41"
- TEMPFAIL_CGI_ERROR = "42"
- TEMPFAIL_PROXY_ERROR = "43"
- TEMPFAIL_SLOW_DOWN = "44"
+ TEMPFAIL_GENERIC = "40"
+ TEMPFAIL_SERVER_UNAVAILABLE = "41"
+ TEMPFAIL_CGI_ERROR = "42"
+ TEMPFAIL_PROXY_ERROR = "43"
+ TEMPFAIL_SLOW_DOWN = "44"
- PERMFAIL_GENERIC = "50"
- PERMFAIL_NOT_FOUND = "51"
- PERMFAIL_GONE = "52"
- PERMFAIL_PROXY_REFUSED = "53"
- PERMFAIL_BAD_REQUEST = "59"
+ PERMFAIL_GENERIC = "50"
+ PERMFAIL_NOT_FOUND = "51"
+ PERMFAIL_GONE = "52"
+ PERMFAIL_PROXY_REFUSED = "53"
+ PERMFAIL_BAD_REQUEST = "59"
- CERTFAIL_NEED_CERT = "60"
- CERTFAIL_BAD_CERT = "61"
- CERTFAIL_INVALID_CERT = "62"
+ CERTFAIL_NEED_CERT = "60"
+ CERTFAIL_BAD_CERT = "61"
+ CERTFAIL_INVALID_CERT = "62"
class ResponseObject:
- def __init__(self,status,meta,content):
- self.status=status
- self.meta=meta
- self.content=content
+ def __init__(self,status,meta,content):
+ self.status=status
+ self.meta=meta
+ self.content=content
def connect_factory(url,parsed=None):
- if parsed is None: parsed = urlparse(url)
- event = threading.Event()
- def _connect():
- ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
- ctx.minimum_version = ssl.TLSVersion.TLSv1_2
- ctx.check_hostname = False
- ctx.verify_mode = ssl.CERT_NONE
- try:
- sock = socket.create_connection((parsed.hostname,parsed.port or 1965),2)
- except socket.timeout:
- threading.current_thread().ret="Socket connection timed out"
- return
- sock.settimeout(5)
- ssock = ctx.wrap_socket(sock,server_hostname=parsed.hostname)
- ssock.sendall((url+"\r\n").encode("utf-8"))
- out = b''
- try:
- while (data:=ssock.recv(1024)) and not event.is_set():
- out+=data
- except socket.timeout:
- threading.current_thread().ret="Read timed out, the page is probably too big."
- return
- ssock.shutdown(socket.SHUT_RDWR)
- ssock.close()
- header, data = out.split(b"\n",1)
- header = header.strip().decode("utf-8")
- status, meta = header.split(None,1)
- threading.current_thread().ret = ResponseObject(status,meta,data)
- return _connect, event
+ if parsed is None: parsed = urlparse(url)
+ event = threading.Event()
+ def _connect():
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ ctx.minimum_version = ssl.TLSVersion.TLSv1_2
+ ctx.check_hostname = False
+ ctx.verify_mode = ssl.CERT_NONE
+ try:
+ sock = socket.create_connection((parsed.hostname,parsed.port or 1965),2)
+ except socket.timeout:
+ threading.current_thread().ret="Socket connection timed out"
+ return
+ sock.settimeout(5)
+ ssock = ctx.wrap_socket(sock,server_hostname=parsed.hostname)
+ ssock.sendall((url+"\r\n").encode("utf-8"))
+ out = b''
+ try:
+ while (data:=ssock.recv(1024)) and not event.is_set():
+ out+=data
+ except socket.timeout:
+ threading.current_thread().ret="Read timed out, the page is probably too big."
+ return
+ ssock.shutdown(socket.SHUT_RDWR)
+ ssock.close()
+ header, data = out.split(b"\n",1)
+ header = header.strip().decode("utf-8")
+ status, meta = header.split(None,1)
+ threading.current_thread().ret = ResponseObject(status,meta,data)
+ return _connect, event
BASE_URL = "https://xfnw.ttm.sh/gem/?"
def link_callback(lurl, text):
- global url
- lurl = urljoin(url,lurl)
- parsed = urlparse(lurl)
- query = None
- if parsed.query:
- query = parsed.query
- parsed = parsed._replace(query=None)
- lurl = urlunparse(parsed)
- params = dict(addr=lurl)
- if query is not None:
- params["query"]=query
- return BASE_URL+urlencode(params), text
+ global url
+ lurl = urljoin(url,lurl)
+ parsed = urlparse(lurl)
+ query = None
+ if parsed.query:
+ query = parsed.query
+ parsed = parsed._replace(query=None)
+ lurl = urlunparse(parsed)
+ params = dict(addr=lurl)
+ if query is not None:
+ params["query"]=query
+ return BASE_URL+urlencode(params), text
qs = parse_qs(os.environ["QUERY_STRING"])
@@ -101,13 +101,13 @@ url = next(iter(qs.get("addr",["gemini://tilde.team/~xfnw/start.gmi"])))
parsed = urlparse(url)
query = next(iter(qs.get("query",[None]))) or parsed.query
if query:
- parsed = parsed._replace(query=query)
- url = urlunparse(parsed)
+ parsed = parsed._replace(query=query)
+ url = urlunparse(parsed)
if parsed.scheme and parsed.scheme!="gemini":
- print("Content-Type: text/html")
- print()
- print("""<!DOCTYPE html>
+ print("Content-Type: text/html")
+ print()
+ print("""<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="0;URL='{0}'">
@@ -120,7 +120,7 @@ if parsed.scheme and parsed.scheme!="gemini":
<p>If the redirect doesn't work, <a href="{0}">click here.</a></p>
</body>
</html>""".format(escape(url)))
- sys.exit()
+ sys.exit()
connect_func, killswitch = connect_factory(url,parsed)
connect_thread = threading.Thread(target=connect_func)
@@ -130,13 +130,13 @@ connect_thread.start()
connect_thread.join(5)
# now if the thread's still alive, we'll set the killswitch and join the thread again
if connect_thread.is_alive():
- killswitch.set()
- connect_thread.join()
+ killswitch.set()
+ connect_thread.join()
# now we know for a fact the thread is done
# get the return value as Thread.ret (done manually in the function definition)
response = getattr(connect_thread,"ret")
if type(response)!=ResponseObject:
- print("""Content-Type: text/html
+ print("""Content-Type: text/html
<!DOCTYPE html>
<html>
@@ -148,13 +148,13 @@ if type(response)!=ResponseObject:
</head>
<body>
<pre>""")
- print(response or "Unknown error occurred.")
- sys.exit()
+ print(response or "Unknown error occurred.")
+ sys.exit()
if response.status==ResponseCodes.GENERIC_INPUT:
- print("Content-Type: text/html")
- print()
- print("""<!DOCTYPE html>
+ print("Content-Type: text/html")
+ print()
+ print("""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
@@ -172,28 +172,28 @@ if response.status==ResponseCodes.GENERIC_INPUT:
</form>
<p>I take no responsibility if shoulder-surfers read your input.</p>""".format(escape(response.meta),"password" if response.status==ResponseCodes.INPUT_NEEDED_SENSITIVE else "text",escape(url)))
elif response.status==ResponseCodes.GENERIC_SUCCESS:
- mime = response.meta
- content = response.content
- mimeparsed = mimeparse.parse_mime(mime)
- if mimeparsed[0][0]=="text": # mimeparsed is ([type, subtype],parameters)
- content = content.decode(mimeparsed[1].get("charset","UTF-8"))
- if mimeparsed[0][1]=="html":
- mime = mime.replace("text/html","text/plain")
- elif mimeparsed[0][1]=="gemini":
- mime = "text/html; charset=UTF-8"
- content = gem2html.gem2html(content,link_callback)
- print("Content-Type: "+mime)
- print("")
- print(content)
- else:
- print("Content-Type: "+mime)
- print()
- sys.stdout.flush()
- sys.stdout.buffer.write(content)
+ mime = response.meta
+ content = response.content
+ mimeparsed = mimeparse.parse_mime(mime)
+ if mimeparsed[0][0]=="text": # mimeparsed is ([type, subtype],parameters)
+ content = content.decode(mimeparsed[1].get("charset","UTF-8"))
+ if mimeparsed[0][1]=="html":
+ mime = mime.replace("text/html","text/plain")
+ elif mimeparsed[0][1]=="gemini":
+ mime = "text/html; charset=UTF-8"
+ content = gem2html.gem2html(content,link_callback)
+ print("Content-Type: "+mime)
+ print("")
+ print(content)
+ else:
+ print("Content-Type: "+mime)
+ print()
+ sys.stdout.flush()
+ sys.stdout.buffer.write(content)
elif response.status==ResponseCodes.GENERIC_REDIRECT:
- print("Content-Type: text/html")
- print()
- print("""<!DOCTYPE html>
+ print("Content-Type: text/html")
+ print()
+ print("""<!DOCTYPE html>
<html>
<head>
<title>Redirecting...</title>
@@ -203,11 +203,11 @@ elif response.status==ResponseCodes.GENERIC_REDIRECT:
</head>
<body>
""")
- print("<p>The current page ({}) wishes to redirect you to {}.</p>".format(escape(url),escape(response.meta)))
- tmp, tmp2 = link_callback(response.meta,"Click here to follow this redirect.")
- print("<p><a href={}>{}</a></p>".format(escape(tmp),escape(tmp2)))
+ print("<p>The current page ({}) wishes to redirect you to {}.</p>".format(escape(url),escape(response.meta)))
+ tmp, tmp2 = link_callback(response.meta,"Click here to follow this redirect.")
+ print("<p><a href={}>{}</a></p>".format(escape(tmp),escape(tmp2)))
elif response.status==ResponseCodes.GENERIC_TEMPFAIL or response.status==ResponseCodes.GENERIC_PERMFAIL:
- print("""Content-Type: text/html
+ print("""Content-Type: text/html
<!DOCTYPE html>
<html>
@@ -219,27 +219,27 @@ elif response.status==ResponseCodes.GENERIC_TEMPFAIL or response.status==Respons
</head>
<body>
<pre>""")
- msg = "Unknown {} error".format("permanent" if response.status==ResponseCodes.GENERIC_PERMFAIL else "temporary")
- if response.status==ResponseCodes.TEMPFAIL_SERVER_UNAVAILABLE:
- msg = "Server unavailable"
- if response.status==ResponseCodes.TEMPFAIL_CGI_ERROR:
- msg = "CGI script error"
- if response.status==ResponseCodes.TEMPFAIL_PROXY_ERROR:
- msg = "Proxy error"
- if response.status==ResponseCodes.TEMPFAIL_SLOW_DOWN:
- msg = "Slow down"
- if response.status==ResponseCodes.PERMFAIL_NOT_FOUND:
- msg = "Not Found"
- if response.status==ResponseCodes.PERMFAIL_GONE:
- msg = "Gone"
- if response.status==ResponseCodes.PERMFAIL_PROXY_REFUSED:
- msg = "Proxy request refused"
- if response.status==ResponseCodes.PERMFAIL_BAD_REQUEST:
- msg = "Bad request"
- print(f"Error {response.status}: {msg}")
- print(f"Server says: {response.meta}")
+ msg = "Unknown {} error".format("permanent" if response.status==ResponseCodes.GENERIC_PERMFAIL else "temporary")
+ if response.status==ResponseCodes.TEMPFAIL_SERVER_UNAVAILABLE:
+ msg = "Server unavailable"
+ if response.status==ResponseCodes.TEMPFAIL_CGI_ERROR:
+ msg = "CGI script error"
+ if response.status==ResponseCodes.TEMPFAIL_PROXY_ERROR:
+ msg = "Proxy error"
+ if response.status==ResponseCodes.TEMPFAIL_SLOW_DOWN:
+ msg = "Slow down"
+ if response.status==ResponseCodes.PERMFAIL_NOT_FOUND:
+ msg = "Not Found"
+ if response.status==ResponseCodes.PERMFAIL_GONE:
+ msg = "Gone"
+ if response.status==ResponseCodes.PERMFAIL_PROXY_REFUSED:
+ msg = "Proxy request refused"
+ if response.status==ResponseCodes.PERMFAIL_BAD_REQUEST:
+ msg = "Bad request"
+ print(f"Error {response.status}: {msg}")
+ print(f"Server says: {response.meta}")
elif response.status==ResponseCodes.GENERIC_CERTFAIL:
- print("""Content-Type: text/html
+ print("""Content-Type: text/html
<!DOCTYPE html>
<html>
@@ -251,9 +251,9 @@ elif response.status==ResponseCodes.GENERIC_CERTFAIL:
</head>
<body>
<pre>""")
- print("Page requires the use of client certificates, which are outside the scope of this proxy.")
+ print("Page requires the use of client certificates, which are outside the scope of this proxy.")
else:
- print("""Content-Type: text/html
+ print("""Content-Type: text/html
<!DOCTYPE html>
<html>
@@ -265,7 +265,7 @@ else:
</head>
<body>
<pre>""")
- print("Page returned status code {} which is unimplemented.".format(response.status))
- print("META = {!r}".format(response.meta))
- print("Response body = {!r}".format(response.content))
+ print("Page returned status code {} which is unimplemented.".format(response.status))
+ print("META = {!r}".format(response.meta))
+ print("Response body = {!r}".format(response.content))
diff --git a/mimeparse.py b/mimeparse.py
@@ -1,62 +1,62 @@
import string
# Utility function to parse a MIME type
def parse_mime(mimetype):
- mimetype = mimetype.strip()
- index = 0
- type = ""
- # type is everything before the /
- while index<len(mimetype) and mimetype[index]!="/":
- type+=mimetype[index]
- index+=1
- index+=1
- subtype = ""
- # subtype is everything after the slash and before the semicolon (if the latter exists)
- while index<len(mimetype) and mimetype[index]!=";":
- subtype+=mimetype[index]
- index+=1
- index+=1
- # if there's no semicolon, there are no params
- if index>=len(mimetype): return [type,subtype], dict()
- params = dict()
- while index<len(mimetype):
- # skip whitespace
- while index<len(mimetype) and mimetype[index] in string.whitespace:
- index+=1
- paramName = ""
- # the parameter name is everything before the = or ;
- while index<len(mimetype) and mimetype[index] not in "=;":
- paramName+=mimetype[index]
- index+=1
- # if the string is over or there isn't an equals sign, there's no param value
- if index>=len(mimetype) or mimetype[index]==";":
- index+=1
- params[paramName]=None
- continue
- # otherwise, grab the param value
- index+=1
- paramValue = ""
- if mimetype[index]=='"':
- index+=1
- while True:
- while index<len(mimetype) and mimetype[index] not in '\\"':
- paramValue+=mimetype[index]
- index+=1
- if index>=len(mimetype): break
- c = mimetype[index]
- index+=1
- if c=="\\":
- if index>=len(mimetype):
- paramValue+=c
- break
- paramValue+=mimetype[index]
- index+=1
- else:
- break
- # skip until next ;
- while index<len(mimetype) and mimetype[index]!=";": index+=1
- else:
- while index<len(mimetype) and mimetype[index]!=";":
- paramValue+=mimetype[index]
- index+=1
- if paramName: params[paramName]=paramValue
- return [type, subtype], params
+ mimetype = mimetype.strip()
+ index = 0
+ type = ""
+ # type is everything before the /
+ while index<len(mimetype) and mimetype[index]!="/":
+ type+=mimetype[index]
+ index+=1
+ index+=1
+ subtype = ""
+ # subtype is everything after the slash and before the semicolon (if the latter exists)
+ while index<len(mimetype) and mimetype[index]!=";":
+ subtype+=mimetype[index]
+ index+=1
+ index+=1
+ # if there's no semicolon, there are no params
+ if index>=len(mimetype): return [type,subtype], dict()
+ params = dict()
+ while index<len(mimetype):
+ # skip whitespace
+ while index<len(mimetype) and mimetype[index] in string.whitespace:
+ index+=1
+ paramName = ""
+ # the parameter name is everything before the = or ;
+ while index<len(mimetype) and mimetype[index] not in "=;":
+ paramName+=mimetype[index]
+ index+=1
+ # if the string is over or there isn't an equals sign, there's no param value
+ if index>=len(mimetype) or mimetype[index]==";":
+ index+=1
+ params[paramName]=None
+ continue
+ # otherwise, grab the param value
+ index+=1
+ paramValue = ""
+ if mimetype[index]=='"':
+ index+=1
+ while True:
+ while index<len(mimetype) and mimetype[index] not in '\\"':
+ paramValue+=mimetype[index]
+ index+=1
+ if index>=len(mimetype): break
+ c = mimetype[index]
+ index+=1
+ if c=="\\":
+ if index>=len(mimetype):
+ paramValue+=c
+ break
+ paramValue+=mimetype[index]
+ index+=1
+ else:
+ break
+ # skip until next ;
+ while index<len(mimetype) and mimetype[index]!=";": index+=1
+ else:
+ while index<len(mimetype) and mimetype[index]!=";":
+ paramValue+=mimetype[index]
+ index+=1
+ if paramName: params[paramName]=paramValue
+ return [type, subtype], params