[spec] The Tragedy of &
Sean Conner sean at conman.org
Sat Jan 30 23:59:47 GMT 2021
It was thus said that the Great Gary Johnson once stated:
>
> The issue I'm raising is that there appears to be no way to pass more
> than one piece of information at a time in our query strings. This has a
> very significant impact on any writers of CGI scripts, which is how many
> Gemini servers allow users to add dynamic pages to their capsules.
>
> But why, you ask?
>
> Because each CGI script is available at a particular file path and
> therefore additional path segments can't be used to pass information to
> them. They have to get their inputs from the query string.
[ snip ]
> It means essentially that CGI scripts are currently second-class
> citizens, and the only people who can write dynamic capsules are server
> authors (or people willing to hack on server code). This is because
> encoding information using path segments requires injecting custom
> routing table code into the server's request handler.
Not if the CGI interface is properly written. All I had to do was write
this CGI script and drop it into my tests directory [1]:
gemini://gemini.conman.org/test/pathseg.cgi
It uses only three of the RFC-3875 defined variables, QUERY_STRING,
SCRIPT_NAME and PATH_INFO to do all the work. The script will ask for three
fields and then present a final page with all three fields. But the script
will only work if all three variables are defined per RFC-3975 (PATH_INFO is
the tricky one).
Yes, it's a bit ugly and yes, it's a second class citizen and yes, it
requires a proper CGI module to work, but it can be done without the
configuration you think it does. The script just simply appends each input
field as the path, so if you enter
and a one
and a two
skidoosh
as the values, the final URL will be:
gemini://gemini.conman.org/test/pathseg.cgi/and%20a%20one/and%20a%20two/skidoosh
Yes, I could have done a bit more processing, naming each segment:
/test/pathseg.cgi/name=and%20a%20one/age=and%20a%20two/action=skidoosh
but I was lazy and wanted to just do a proof-of-concept here.
> If you can write a CGI script that can correctly associate INPUT
> responses with their intended variables, please share it. I suspect it
> would be quite educational.
I have added it [3].
-spc
[1] I gave the script a .cgi extension just to drive the point
home---for my server, GLV-1.12556 [2], the extension of a CGI script
doesn't matter at all.
[2] https://github.com/spc476/GLV-1.12556
[3] Here you go. It's in Lua, but it's easy going except for the first
bit which is a bit of broilerplate I needed for encoding and
decoding various strings. The main logic is marked though, so you
can skip the first section.
#!/usr/bin/env lua
-- ************************************************************************
-- Decoding and Encoding crap, not much to see here, citizen! Move along!
-- ************************************************************************
local lpeg = require "lpeg"
local xdigit = lpeg.locale().xdigit
local char = lpeg.P"%" * lpeg.C(xdigit * xdigit)
/ function(c)
return string.char(tonumber(c,16))
end
+ lpeg.P"+" / " "
+ lpeg.P(1)
local decode_query = lpeg.Cs(char^1)
local function tohex(c)
return string.format("%%%02X",string.byte(c))
end
local unsafe = lpeg.P" " / "%%20"
+ lpeg.P"#" / "%%23"
+ lpeg.P"%" / "%%25"
+ lpeg.P"<" / "%%3C"
+ lpeg.P">" / "%%3E"
+ lpeg.P"[" / "%%5B"
+ lpeg.P"\\" / "%%5C"
+ lpeg.P"]" / "%%5D"
+ lpeg.P"^" / "%%5E"
+ lpeg.P"{" / "%%7B"
+ lpeg.P"|" / "%%7C"
+ lpeg.P"}" / "%%7D"
+ lpeg.P'"' / "%%22"
+ lpeg.R("\0\31","\127\255") / tohex
local char_path = lpeg.P"?" / "%%3F"
+ unsafe
+ lpeg.P(1)
local esc_path = lpeg.Cs(char_path^0)
-- ************************************************************************
-- The main script starts here
-- ************************************************************************
local query = os.getenv("QUERY_STRING")
local script_name = os.getenv("SCRIPT_NAME")
local pathinfo = os.getenv("PATH_INFO")
if not pathinfo and query == "" then
io.stdout:write("Status: 10\n")
io.stdout:write("Content-Type: Input field\n")
io.stdout:write("\n")
os.exit(0,true)
end
if not pathinfo then
query = decode_query:match(query)
query = esc_path:match(query)
io.stdout:write("Status: 30\n")
io.stdout:write(string.format("Location: %s/%s\n",script_name,query))
io.stdout:write("\n")
os.exit(0,true)
end
if pathinfo:match("^/[^/]*/[^/]*/[^/]*") then
local f1,f2,f3 = pathinfo:match("^/([^/]*)/([^/]*)/([^/]*)")
f1 = decode_query:match(f1)
f2 = decode_query:match(f2)
f3 = decode_query:match(f3)
io.stdout:write("Status: 20\n")
io.stdout:write("Content-Type: text/gemini\n")
io.stdout:write("\n")
io.stdout:write("The three fields you input:\n")
io.stdout:write("\n")
io.stdout:write(string.format("* %s\n",f1))
io.stdout:write(string.format("* %s\n",f2))
io.stdout:write(string.format("* %s\n",f3))
io.stdout:write("\n")
io.stdout:write(string.format("=> %s Try again\n",script_name))
os.exit(0,true)
end
if query == "" then
io.stdout:write("Status: 10\n")
io.stdout:write("Content-Type: Input next field\n")
io.stdout:write("\n")
os.exit(0,true)
else
query = decode_query:match(query)
query = esc_path:match(query)
pathinfo = esc_path:match(pathinfo)
io.stdout:write("Status: 30\n")
io.stdout:write(string.format("Location: %s%s/%s\n",script_name,pathinfo,query))
io.stdout:write("\n")
os.exit(0,true)
end
Archive home | About this archive