gem

a fork of khuxkm's gemini to web proxy
Log | Files | Refs

commit ee4d108b8abd15b7f3816260d0927ad3cc3d1536
parent f05af1e5e9433435c1c81dbdfd0e6b4ee89cfea6
Author: xfnw <xfnw@ttm.sh>
Date:   Thu, 24 Dec 2020 15:40:17 -0500

use spaces instead of tabs

Diffstat:
Mgem.css | 42+++++++++++++++++++++---------------------
Mgem2html.py | 140++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mindex.cgi | 274++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mmimeparse.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