PS-HTTPD
PS-HTTPD is a web server written in PostScript. It started at a coffee table discussion at my work. We first talked about web servers and how everything seems to get one. Then we discussed the new Xerox monster printer we just got installed. I started to wonder if it would be possible to make a simple web server in PostScript. After a bit too many hours of stack craze I found out that it was!
Here follows the PostScript sourcecode.
%!
%===================================================
% PS-HTTPD V1.4
% Copyright 2000-2003 Anders Karlsson, pugo@pugo.org
% License: GNU General Public License
%===================================================
% This dictionary maps between extensions and mime-types
% Observe that "html" isn't part of this dict, that's because
% it's default in print_header.
/extensiondict 29 dict def
extensiondict begin
/jpg (Content-type: image/jpeg\n) def
/jpeg (Content-type: image/jpeg\n) def
/gif (Content-type: image/gif\n) def
/png (Content-type: image/png\n) def
/tif (Content-type: image/tiff\n) def
/tiff (Content-type: image/tiff\n) def
/txt (Content-type: text/plain\n) def
/css (Content-type: text/css\n) def
/ps (Content-type: application/postscript\n) def
/pdf (Content-type: application/pdf\n) def
/eps (Content-type: application/postscript\n) def
/tar (Content-type: application/x-tar\n) def
/gz (Content-type: application/x-tar\n) def
/tgz (Content-type: application/x-tar\n) def
/rpm (Content-type: application/x-rpm\n) def
/zip (Content-type: application/zip\n) def
/mp3 (Content-type: audio/mpeg\n) def
/mp2 (Content-type: audio/mpeg\n) def
/mid (Content-type: audio/midi\n) def
/midi (Content-type: audio/midi\n) def
/wav (Content-type: audio/x-wav\n) def
/au (Content-type: audio/basic\n) def
/ram (Content-type: audio/x-pn-realaudio\n) def
/ra (Content-type: audio/x-realaudio\n) def
/mpg (Content-type: video/mpeg\n) def
/mpeg (Content-type: video/mpeg\n) def
/qt (Content-type: video/quicktime\n) def
/mov (Content-type: video/quicktime\n) def
/avi (Content-type: video/x-msvideo\n) def
end
/get_file % read file /infile and send it to %stdout
{
{ % loop
infile inbuff readstring
{ stdout exch writestring }
{ stdout exch writestring infile closefile exit } ifelse
} bind loop
flush
} bind def
/concatstr % A better string-concat
{
exch dup length 2 index length add string
dup dup 4 2 roll copy length 4 -1 roll putinterval
} bind def
/hitcount % Add 1 to the hitcount-file
{
(/home/pugo/psweb/hits) (r+) file
dup dup
dup 16 string readline pop
1 index 0 setfileposition
cvi 1 add 16 string cvs
writestring (\n) writestring
closefile
} bind def
% Return extension of file on stack
/get_extension % (filepath.ext) -- (bool) (ext)
{
dup
{ % loop
(.) search
{ pop pop }
{ exit } ifelse
} loop
exch 1 index ne
} bind def
% Print a HTTP-header
/print_header % (filename) (size) --
{
stdout persistent {(HTTP/1.1 200 OK\n)} {(HTTP/1.0 200 OK\n)} ifelse writestring
stdout (MIME-Version: 1.0\n) writestring
stdout (Server: PS-HTTPD/1.4\n) writestring
stdout (Content-Length: ) writestring
stdout exch 16 string cvs writestring
stdout (\n) writestring
get_extension
{
% If the extension exists in dictionary, then use it,
% otherwise hope that text/html is good enough.
dup extensiondict exch known
{ extensiondict exch get stdout exch writestring }
{ pop stdout (Content-type: text/html\n) writestring } ifelse
}
{ % Couldn't get extension, guess it's text/html
pop stdout (Content-type: text/plain\n) writestring
} ifelse
stdout (\n) writestring flush
} bind def
/read_command % read command from stdin and define it to /command
{
/stdin (%stdin) (r) file def
1024 string
{
% read lines until empty line
stdin 256 string readline pop
dup () eq { pop exit } { concatstr } ifelse
} loop
/command exch def
} bind def
% Parse the HTTP-command read from user
/parse_result
{
% Check if we should do HTTP 1.1 persistent connections
command (HTTP/1.1) search
{
pop pop pop
command (Connection: close) search % Check for Connection: close
{ pop pop pop /persistent false def }
{ pop /persistent true def } ifelse
}
{ pop /persistent false def } ifelse
command token
{
(GET) eq
{
( ) search
{
root exch concatstr % build path
/filename exch def pop pop % define filename and clean stack
filename filename length 1 sub 1 getinterval (/) eq
{ filename (index.html) concatstr
/filename exch def } if % add index.html
filename (..) search % Check if user tries to use ".."
{ stdout (4711 Stupid user error!\n\n) writestring quit } if pop
/infile filename (r) file def % open file
filename infile bytesavailable print_header
get_file
} if
} if
} if
} bind def
% Redefine handleerror in errordict to quit on all errors.
% Otherwise it will be possible to telnet and get a postscript-prompt
errordict begin
/handleerror { stdout (\n\nPS-HTTPD ERROR: Probably wrong URL\n\n) writestring quit } def
end
% Buffer used to read data from file. Around 2048 bytes should be good.
/inbuff 2048 string def
% Init environment
/stdout (%stdout) (w) file def
/command () def
% Root-path (root of WWW-pages)
/root (/home/pugo/psweb/www) def
%hitcount % add one to the hitcount
% Read a command from the server and parse result
{
read_command
parse_result
persistent not { exit } if % exit if not persistent, otherwise loop again
} loop % Loop until persistent close
quit