[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Is there any CGI library for Lua?
- From: Mark Edgar <medgar@...>
- Date: Mon, 30 Oct 2006 14:52:45 -0700
I forgot to mention, my shttpd dispatcher of course uses the SAPI CGI
launcher from Kepler as well as CGILua. The entire system is actually a
layering of technologies:
daemontools and tcpserver (ucspi-tcp) from D. J. Bernstein
http://cr.yp.to/daemontools.html
http://cr.yp.to/ucspi-tcp.html
shttpd from SuperScript Technology
http://www.superscript.com/shttpd/intro.html
cgi-1.1-dispatch and cgi-1.1-fixup from me
(attached)
SAPI CGI launcher and CGILua from Kepler
http://www.keplerproject.org/
http://www.keplerproject.org/cgilua
slslpp.lua from Rici Lake
http://lua-users.org/wiki/SlightlyLessSimpleLuaPreprocessor
I will write up a wiki page on how to set it all up, if there is any
interest.
-Mark
/* cgi-1.1-dispatch.c Mark Edgar
*
* This program is intended to be run by cgi-httpd
* http://www.superscript.com/shttpd/intro.html
* It translates the environment variables PATH_INFO and SCRIPT_NAME:
*
* PATH_INFO=/path/to/executable/extra/info
* SCRIPT_NAME=
*
* to
*
* PATH_INFO=/extra/info
* SCRIPT_NAME=/path/to/executable
*
* and then executes ./bin/path/to/executable
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
const char *progname;
#define debug(...)
static void
Perror(const char *message)
{
char wd[PATH_MAX];
debug("%s: cwd >>%s<<\n",
progname, getcwd(wd, sizeof wd));
fprintf(stderr, "%s: ``%s'': %s\n",
progname, message, strerror(errno));
}
/*
* Replace /. with /: and // with /
*/
static void
sanitize_path(char *path)
{
size_t s, d;
int c;
for (s = d = 1; (c = path[s]); s++) {
if (path[s - 1] == '/') {
if (c == '.') c = ':';
if (c == '/') continue;
}
path[d++] = c;
}
path[d] = 0;
}
/*
* Copy a string; return a pointer to the null terminator written.
* The buffers may overlap as long as src > dst.
*/
char *
stpcpy(char *dst, const char *src)
{
while ((*dst = *src++)) dst++;
return dst;
}
/*
* Copy path components from path to name until name contains the path to a
* file, or a non-existent path. A pointer to the remainder of path is
* returned. name must contain a path name (or empty string) and must be large
* enough to store this and all the path elements from path which may be
* copied.
*/
static const char *
find_program(const char *path, char *name)
{
const char *p;
char *e;
struct stat st;
assert(path != 0);
e = strchr(name, '\0');
p = path;
do {
assert(*p == '/');
do *e++ = *p++;
while (*p && *p != '/');
if (lstat(name, &st) != 0 || S_ISLNK(st.st_mode)) {
Perror(name);
return 0;
}
if (!S_ISDIR(st.st_mode))
return p;
} while (*p);
/* This is where to add index.cgi support. */
Perror(name);
return 0;
}
static void
page(int status_code, const char *status_message)
{
const char *prot;
if ((prot = getenv("SERVER_PROTOCOL")) && 0 != strcmp(prot, "HTTP/0.9"))
printf(
"%s %d %s\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: %u\r\n"
"\r\n",
status_code > 0 ? prot : "Status:",
abs(status_code), status_message,
strlen(status_message) + 2);
else
printf("%u ", abs(status_code));
printf("%s\r\n", status_message);
}
static void
E(int e)
{
if (e == -1) {
Perror("Error");
page(500, "Internal server error: Failed to execute CGI program");
exit(21);
}
}
int
main(int argc, char **argv)
{
char *pi;
const char *new_pi;
char command[PATH_MAX], *script_name;
progname = argv[0];
if (!(pi = getenv("PATH_INFO")) || pi[0] != '/') {
page(500, "Internal server error: Missing or invalid PATH_INFO");
return 21;
}
sanitize_path(pi);
script_name = stpcpy(command, "./bin");
if (!(new_pi = find_program(pi, command))) {
page(404, "Not Found");
return 0;
}
/* modify PATH_INFO */
stpcpy(pi, new_pi);
/* create PATH_TRANSLATED */
setenv("PATH_TRANSLATED", pi + !!*pi, !0);
/* create SCRIPT_NAME */
setenv("SCRIPT_NAME", script_name, !0);
/* pipe output to fixup program, if specified. */
if (argv[1]) {
pid_t pid;
int p[2];
E(pipe(p));
switch (pid = fork()) {
case -1:
E(-1);
case 0:
dup2(p[0], STDIN_FILENO);
close(p[0]);
close(p[1]);
execvp(argv[1], argv + 1);
Perror("exec");
page(500, "Internal server error: Failed to execute CGI program");
_exit(111);
default:
dup2(p[1], STDOUT_FILENO);
close(p[0]);
close(p[1]);
break;
}
}
execl(command, script_name + 1, (char *)0);
Perror("exec failed");
page(-500, "Internal server error: Failed to execute CGI program");
return 21;
}
#!/usr/bin/env lua
--[[
This program implements the "backend" of the CGI/1.1 protocol.
It reads the output of a CGI program from its standard input, provides
an appropriate status line, supplies missing header fields (Server,
Date, Content-Length), and writes the result to standard output
using proper CR LF line endings in the header. The program correctly
handles HTTP/0.9.
The returned status code is taken from the Status header field if
it is output by the CGI program, or is 302 if the Location header
field is output, or 200 if a valid Content-Type header field exists,
otherwise the program returns a 500 error.
If the input begins with a valid HTTP status line, the entire input
is passed through to the output. This implements non-parsed-header
mode without the nph- filename rudeness. This is an extension to the
CGI protocol.
It is an error if the input contains neither a Status header field,
nor a Location header field nor a Content-Type header field.
If the SERVER_PROTOCOL environment variable does not exist, HTTP/0.9
is assumed.
If the Server header field is missing, the value is taken from the
SERVER_SOFTWARE environment variable if it exists, otherwise the value
"Unknown" is used.
If the Date header field is missing, its value is the current time in
RFC 2822 (RFC 822) format.
If the Content-Length header field is missing, its value is calculated
from the response body length.
If a response body is not provided by the CGI program, then one will be
provided only for a Location redirect request or in the presence of a Status
header field.
--]]
local function debug(...) io.stderr:write(unpack(arg)) end
local function find_all(str, pat)
return
function(state,_)
return (function(a, b, ...)
if not a then
return nil
end
state.a = a
state.b = b
return unpack(arg)
end)(string.find(state.str, state.pat, state.b + 1))
end,
{ str = str, pat = pat, b = 0 }
end
-- Are we at least HTTP/1.0?
local http1
do
local protocol = os.getenv("SERVER_PROTOCOL")
http1 = protocol and protocol ~= "HTTP/0.9"
end
local function http_status_line(code, message)
return "HTTP/1.1 " .. code .. " " .. message .. "\r\n"
end
local function write_header(field)
if http1 then
io.write(field)
end
end
-- Return a new response, body_index, and header table
local function cgi_error(message,extra)
local response_body = message .. "\r\n"
local response_header = (extra or "") .. "\r\n"
local index = string.len(response_header) + 1
io.write(http_status_line(500, message))
return response_header .. response_body, index, {}
end
local function append_body(response, code, message)
return response .. code .. " " .. message .. "\r\n"
end
local function append_redirect_body(response, location_index)
local _, _, location = string.find(response, "^[^:]+: *([^\r\n]+)", location_index)
return append_body(response, 302, "Moved temporarily: " .. location)
end
local function tr_eol(lines)
return (string.gsub(lines, "\r?\n", "\r\n"))
end
-- Translate line endings to CR LF in the header.
local function write_document(doc, body_index, content_type)
write_header(tr_eol(string.sub(doc, 1, body_index - 1)))
io.write(string.sub(doc, body_index))
end
-- We will track the values of certain header fields
local hdr = {
status = false,
server = false,
date = false,
location = false,
['content-length'] = false,
['content-type'] = false }
-- Read the entire response.
local response = io.read("*a")
-- If the first line is a status line, we pass everything else through.
-- This is basically non-parsed-header mode -- it allows the CGI program
-- to do whatever it pleases.
if string.find(response, "^HTTP/[^ ]+[ \t]+%d+[ \t]+[^\r\n]*\r\n") then
io.write(response)
os.exit(0)
end
-- Record the index of the start of the body in the response
local body_index
-- Process the header. Record the index of the start of the message body.
do
local iter, state, init = find_all(response, "^([^%s:]+):[ \t]*[^\r\n]*\r?\n")
for name in iter, state, init do
name = string.lower(name)
if hdr[name] == false then
hdr[name] = state.a
end
end
body_index = state.b + 1
end
-- Now we should be at the blank line which separates the header from the body.
local _, i = string.find(response, "^\r?\n", body_index)
if not _ then
if body_index > string.len(response) then
response, body_index, hdr = cgi_error("Missing end of header")
else
response, body_index, hdr = cgi_error("Invalid header")
end
else
body_index = i + 1
if hdr.status then
local _, _, code, desc = string.find(response, "^[^:]*: *(%d%d%d) ([^\r\n]*)", hdr.status)
if not _ then
response, body_index, hdr = cgi_error("Invalid Status header field")
else
write_header(http_status_line(code, desc))
-- Remove the Status header field from the response
local hdr_end = string.find(response, "\n", hdr.status, true)
response = string.sub(response, 1, hdr.status - 1) .. string.sub(response, hdr_end + 1)
body_index = body_index - (hdr_end + 1 - hdr.status)
if body_index > string.len(response) then
-- Generate a body if none supplied.
response = append_body(response, code, desc)
end
end
elseif hdr.location then
-- We assume the location is a (valid) URL. Also, if there is no response body, we add one.
write_header(http_status_line(302, "Moved temporarily"))
if body_index > string.len(response) then
response = append_redirect_body(response, hdr.location)
end
elseif hdr['content-type'] then
write_header(http_status_line(200, "Success"))
else
response, body_index, hdr = cgi_error("Missing Content-Type header field")
end
end
-- Write missing header fields
if not hdr.server then
write_header("Server: " .. (os.getenv("SERVER_SOFTWARE") or "Unknown") .. "\r\n")
end
if not hdr.date then
-- RFC 2822 compliant date string
write_header("Date: " .. os.date("%a, %d %b %Y %H:%M:%S %z") .. "\r\n")
end
if not hdr['content-type'] and body_index <= string.len(response) then
write_header("Content-Type: text/plain; charset=US-ASCII\r\n")
end
if not hdr['content-length'] then
local length = string.len(response) - body_index + 1
if length > 0 then
write_header("Content-Length: " .. length .. "\r\n")
end
end
-- Write the rest of the response
write_document(response, body_index, hdr['content-type'])