initial commit

This commit is contained in:
Quentin W 2024-07-18 14:09:32 -04:00
commit 5b839e0543
174 changed files with 12261 additions and 0 deletions

33
werc/bin/aux/addwuser.rc Executable file
View file

@ -0,0 +1,33 @@
#!/bin/rc
if(! ~ $#werc_root 0)
cd $werc_root
fn usage {
if(! ~ $#* 0)
echo $0: $* >[1=2]
echo 'Usage:' $0 'user_name user_password [groups ...]' >[1=2]
exit usage
}
if(! test -d etc/users/)
usage 'Run for root of werc installation or set $werc_root'
user_name=$1
shift
user_pass=$1
shift
user_groups=$*
if(~ $"user_name '' || ~ $"user_pass '')
usage
mkdir etc/users/$user_name
echo $user_pass > etc/users/$user_name/password
if(! ~ $#user_groups 0)
for(g in $user_groups) {
mkdir -p etc/users/$g
echo $user_name >> etc/users/$g/members
}

64
werc/bin/aux/bpst.rc Executable file
View file

@ -0,0 +1,64 @@
#!/bin/rc
path=( $PLAN9/bin $path )
base=.
if(~ $#user 0)
user=`{whoami}
file=(); title=();
bloguser=$user
while(! ~ $#* 0) {
switch($1) {
case -u
base=/gsoc/www/people/$user/blog/
case -b
shift
base=$1
case -f
shift
file=$1
}
shift
}
if(~ $"EDITOR '')
EDITOR=vi
if(~ $#file 0 || ! test -f $file) {
file=/tmp/blogtmp.$pid
rm $file >[2]/dev/null
touch $file
}
$EDITOR $file
aspell -c $file
rm $file.bak >[2]/dev/null
fn mkbpost {
umask 002 # Let group write
bptext=$1
if(! ~ $#2 0)
bpid=`{echo -n '-'^$"bpid | sed 's/'$forbidden_uri_chars'+/_/g; 1q'}
d=`{/bin/date +%F|sed 's,-,/,g'}
ddir=$blagh_root^$d^'/'
n=`{ls $ddir >[2]/dev/null |wc -l}
mkdir -p $ddir/$"n^$"bpid/
{
# TODO: Enable metadata
#echo '* Posted:' `{date}
#if(! ~ $#logged_user 0)
# echo '* Author: '$logged_user
cat $bptext
}> $ddir/$"n^$"bpid/index.md
}
forbidden_uri_chars='[^a-zA-Z0-9_+\-\/\.]'
blagh_root=$base
if(test -s $file)
mkbpost $file
if not
echo Empty file!

14
werc/bin/aux/gensitemaptxt.rc Executable file
View file

@ -0,0 +1,14 @@
#!/bin/rc
# DEPRECATED: sitemap.tpl now generates and updates a sitemap.txt when requested, and is also more smart than this simplistic script.
for(d in sites/*/) {
echo $d
9 du -a $d | awk '/\.(md|html)$/ { print $2 }; {}' | 9 sed -e 's/\.(md|html)$//' -e 's,/index$,/,' -e 's,^sites/,http://,' > $d/sitemap.txt
if(! test -f $d/robots.txt) {
echo generating missing robots.txt for $d
echo $d|sed 's,sites/,Sitemap: http://,; s/$/sitemap.txt/;' > $d/robots.txt
cat $d/robots.txt
}
}

16
werc/bin/aux/runtsts.rc Executable file
View file

@ -0,0 +1,16 @@
#!/bin/rc
tstdom='http://test.cat-v.org'
cd sites/tst.cat-v.org
tstfiles=`{du -a |awk '/\.tst$/ { print $2 }; {} ' | sed 's/^\.//; s/\.tst$//'}
for(f in $tstfiles) {
ifs='
' { tsts=`{cat ./$f.tst} }
for(t in $tsts) {
echo tst $t
}
}

236
werc/bin/cgilib.rc Executable file
View file

@ -0,0 +1,236 @@
# Useful CGI stuff
fn dprint { echo $* >[1=2] }
fn dprintv { { for(v in $*) { echo -n $v^'#'^$#$v^'=' $$v '; ' }; echo } >[1=2] }
fn echo {if(! ~ $1 -n || ! ~ $2 '') /bin/echo $*}
fn escape_html { sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g' $* }
fn http_redirect {
if(~ $1 http://* https://*)
t=$1
if not if(~ $1 /*)
t=$"base_url^$1
if not
t=$"base_url^$"req_path^$1
exec /bin/echo 'Status: '^$2^'
Location: '^$t^'
'
exit
}
fn perm_redirect { http_redirect $1 '301 Moved Permanantly' }
fn post_redirect { http_redirect $1 '303 See Other' }
# Note: should check if content type is application/x-www-form-urlencoded?
# Should compare with http://www.shelldorado.com/scripts/cmds/urlgetopt.txt
fn load_post_args {
if(~ $REQUEST_METHOD POST && ~ $#post_args 0) {
ifs='&
' for(pair in `{cat}) {
ifs='=' { pair=`{echo -n $pair} }
n='post_arg_'^`{echo $pair(1)|nurldecode|tr -cd 'a-zA-Z0-9_'}
post_args=( $post_args $n )
ifs=() { $n=`{echo -n $pair(2)|nurldecode|tr -d ' '} }
}
pair=()
}
if not
status='No POST or post args already loaded'
}
# Status is () if at least one arg is found. DEPRECATED: access vars directly.
fn get_post_args {
load_post_args
_status='No post arg matches'
for(n in $*) {
v=post_arg_$n
if(! ~ $#$v 0) {
$n=$$v
_status=()
}
}
status=$_status
}
# This seems slightly improve performance, but might depend on httpd buffering behavior.
fn awk_buffer {
awk '{
buf = buf $0"\n"
if(length(buf) > 1400) {
printf "%s", buf
buf = ""
}
}
END { printf "%s", buf }'
}
fn nurldecode { urlencode -d || url_decode} # GROSS
fn url_decode {
awk '
BEGIN {
hextab ["0"] = 0; hextab ["8"] = 8;
hextab ["1"] = 1; hextab ["9"] = 9;
hextab ["2"] = 2; hextab ["A"] = hextab ["a"] = 10
hextab ["3"] = 3; hextab ["B"] = hextab ["b"] = 11;
hextab ["4"] = 4; hextab ["C"] = hextab ["c"] = 12;
hextab ["5"] = 5; hextab ["D"] = hextab ["d"] = 13;
hextab ["6"] = 6; hextab ["E"] = hextab ["e"] = 14;
hextab ["7"] = 7; hextab ["F"] = hextab ["f"] = 15;
}
{
decoded = ""
i = 1
len = length ($0)
while ( i <= len ) {
c = substr ($0, i, 1)
if ( c == "%" ) {
if ( i+2 <= len ) {
c1 = substr ($0, i+1, 1)
c2 = substr ($0, i+2, 1)
if ( hextab [c1] == "" || hextab [c2] == "" ) {
print "WARNING: invalid hex encoding: %" c1 c2 | "cat >&2"
} else {
code = 0 + hextab [c1] * 16 + hextab [c2] + 0
c = sprintf ("%c", code)
i = i + 2
}
} else {
print "WARNING: invalid % encoding: " substr ($0, i, len - i)
}
} else if ( c == "+" ) {
c = " "
}
decoded = decoded c
++i
}
printf "%s", decoded
}
'
}
fn nurlencode { urlencode || url_encode } # GROSS
fn url_encode {
awk '
BEGIN {
# We assume an awk implementation that is just plain dumb.
# We will convert an character to its ASCII value with the
# table ord[], and produce two-digit hexadecimal output
# without the printf("%02X") feature.
EOL = "%0A" # "end of line" string (encoded)
split ("1 2 3 4 5 6 7 8 9 A B C D E F", hextab, " ")
hextab [0] = 0
for ( i=1; i<=255; ++i ) ord [ sprintf ("%c", i) "" ] = i + 0
if ("'^$"EncodeEOL^'" == "yes") EncodeEOL = 1; else EncodeEOL = 0
}
{
encoded = ""
for ( i=1; i<=length ($0); ++i ) {
c = substr ($0, i, 1)
if ( c ~ /[a-zA-Z0-9.-]/ ) {
encoded = encoded c # safe character
} else if ( c == " " ) {
encoded = encoded "+" # special handling
} else {
# unsafe character, encode it as a two-digit hex-number
lo = ord [c] % 16
hi = int (ord [c] / 16);
encoded = encoded "%" hextab [hi] hextab [lo]
}
}
if ( EncodeEOL ) {
printf ("%s", encoded EOL)
} else {
print encoded
}
}
END {
#if ( EncodeEOL ) print ""
}
' $*
}
# Cookies
fn set_cookie {
# TODO: should check input values more carefully
name=$1
val=$2
extraHttpHeaders=( $extraHttpHeaders 'Set-cookie: '^$"name^'='^$"val^'; path=/;' )
}
fn get_cookie {
ifs=';' { co=`{echo $HTTP_COOKIE} }
# XXX: we might be adding a trailing new line?
# The ' ?' is needed to deal with '; ' inter-cookie delimiter
{ for(c in $co) echo $c } | sed -n 's/^ ?'$1'=//p'
}
fn static_file {
echo -n 'Content-Type: '
select_mime $1
echo
exec cat $1
}
fn select_mime {
m='text/plain'
if(~ $1 *.css)
m='text/css'
if not if(~ $1 *.ico)
m='image/x-icon'
if not if(~ $1 *.png)
m='image/png'
if not if(~ $1 *.jpg *.jpeg)
m='image/jpeg'
if not if(~ $1 *.gif)
m='image/gif'
if not if(~ $1 *.pdf)
m='application/pdf'
echo $m
}
##############################################
# Generic rc programming helpers
# Manage nested lists
fn ll_add {
_l=$1^_^$#$1
$_l=$*(2-)
$1=( $$1 $_l )
}
# Add to the head: dangerous if you shrink list by hand!
fn ll_addh {
_l=$1^_^$#$1
$_l=$*(2-)
$1=( $_l $$1 )
}
NEW_LINE='
'
# crop_text [max_lenght [ellipsis]]
# TODO: Option to crop only at word-delimiters.
fn crop_text {
m=512
e='...'
if(! ~ $#1 0)
m=$1
if(! ~ $#2 0)
e=$2
awk -v 'max='^$"m -v 'ellipsis='$e '
{
nc += 1 + length;
if(nc > max) {
print substr($0, 1, nc - max) " " ellipsis
exit
}
print
}'
}

27
werc/bin/contrib/fix-rc-scripts Executable file
View file

@ -0,0 +1,27 @@
#!/usr/local/plan9/bin/rc
# Fix rc shell scripts to find rc without launching env every time.
# Invoke with rc and plan9 versions of grep and ed in $PATH
# If your system lacks which (e.g. some gnu/linux)
# substitute the full path to rc in this line:
rc=/usr/local/plan9/bin/rc
firstline='#!'$"rc
if(~ $#* 0) files = *
if not files = $*
myname = `{basename $0}
for(file in $files) {
if(test -d $file) $0 $file/*
if not if(~ $file *$myname) {}
if not if(sed 1q $file | grep '^#!/.*[/ ]rc$' > /dev/null) {
{
echo 1c
echo $firstline
echo .
echo wq
} | ed $file > /dev/null
}
}

12
werc/bin/contrib/hgweb.config Executable file
View file

@ -0,0 +1,12 @@
[web]
style = gitweb
allow_archive = bz2
#[paths]
#w9 = /gsoc/hg/w9/
[collections]
#allow_archive = bz2 zip
/gsoc/hg = /gsoc/hg/
#/var/hg = /var/hg/

47
werc/bin/contrib/hgwebdir.cgi Executable file
View file

@ -0,0 +1,47 @@
#!/usr/bin/env python
#
# An example CGI script to export multiple hgweb repos, edit as necessary
# send python tracebacks to the browser if an error occurs:
import cgitb
cgitb.enable()
# adjust python path if not a system-wide install:
#import sys
#sys.path.insert(0, "/path/to/python/lib")
# If you'd like to serve pages with UTF-8 instead of your default
# locale charset, you can do so by uncommenting the following lines.
# Note that this will cause your .hgrc files to be interpreted in
# UTF-8 and all your repo files to be displayed using UTF-8.
#
#import os
#os.environ["HGENCODING"] = "UTF-8"
from mercurial.hgweb.hgwebdir_mod import hgwebdir
from mercurial.hgweb.request import wsgiapplication
import mercurial.hgweb.wsgicgi as wsgicgi
# The config file looks like this. You can have paths to individual
# repos, collections of repos in a directory tree, or both.
#
# [paths]
# virtual/path = /real/path
# virtual/path = /real/path
#
# [collections]
# /prefix/to/strip/off = /root/of/tree/full/of/repos
#
# collections example: say directory tree /foo contains repos /foo/bar,
# /foo/quux/baz. Give this config section:
# [collections]
# /foo = /foo
# Then repos will list as bar and quux/baz.
#
# Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
# or use a dictionary with entries like 'virtual/path': '/real/path'
def make_web_app():
return hgwebdir("hgweb.config")
wsgicgi.launch(wsgiapplication(make_web_app))

1447
werc/bin/contrib/markdown.pl Executable file

File diff suppressed because it is too large Load diff

427
werc/bin/contrib/md2html.awk Executable file
View file

@ -0,0 +1,427 @@
#!/bin/awk -f
#
# by: Jesus Galan (yiyus) 2009
#
# Usage: md2html.awk file.md > file.html
# See: http://4l77.com/src/md2html.awk
function eschtml(t) {
gsub("&", "\\&amp;", t);
gsub("<", "\\&lt;", t);
return t;
}
function oprint(t){
if(nr == 0)
print t;
else
otext = otext "\n" t;
}
function subref(id){
for(; nr > 0 && sub("<<" id, ref[id], otext); nr--);
if(nr == 0 && otext) {
print otext;
otext = "";
}
}
function nextil(t) {
if(!match(t, /[`<&\[*_\\-]|(\!\[)/))
return t;
t1 = substr(t, 1, RSTART - 1);
tag = substr(t, RSTART, RLENGTH);
t2 = substr(t, RSTART + RLENGTH);
if(ilcode && tag != "`")
return eschtml(t1 tag) nextil(t2);
# Backslash escaping
if(tag == "\\"){
if(match(t2, /^[\\`*_{}\[\]()#+\-\.!]/)){
tag = substr(t2, 1, 1);
t2 = substr(t2, 2);
}
return t1 tag nextil(t2);
}
# Dashes
if(tag == "-"){
if(sub(/^-/, "", t2))
tag = "&#8212;";
return t1 tag nextil(t2);
}
# Inline Code
if(tag == "`"){
if(sub(/^`/, "", t2)){
if(!match(t2, /``/))
return t1 "&#8221;" nextil(t2);
ilcode2 = !ilcode2;
}
else if(ilcode2)
return t1 tag nextil(t2);
tag = "<code>";
if(ilcode){
t1 = eschtml(t1);
tag = "</code>";
}
ilcode = !ilcode;
return t1 tag nextil(t2);
}
if(tag == "<"){
# Autolinks
if(match(t2, /^[^ ]+[\.@][^ ]+>/)){
url = eschtml(substr(t2, 1, RLENGTH - 1));
t2 = substr(t2, RLENGTH + 1);
linktext = url;
if(match(url, /@/) && !match(url, /^mailto:/))
url = "mailto:" url;
return t1 "<a href=\"" url "\">" linktext "</a>" nextil(t2);
}
# Html tags
if(match(t2, /^[A-Za-z\/!][^>]*>/)){
tag = tag substr(t2, RSTART, RLENGTH);
t2 = substr(t2, RLENGTH + 1);
return t1 tag nextil(t2);
}
return t1 "&lt;" nextil(t2);
}
# Html special entities
if(tag == "&"){
if(match(t2, /^#?[A-Za-z0-9]+;/)){
tag = tag substr(t2, RSTART, RLENGTH);
t2 = substr(t2, RLENGTH + 1);
return t1 tag nextil(t2);
}
return t1 "&amp;" nextil(t2);
}
# Images
if(tag == "!["){
if(!match(t2, /(\[.*\])|(\(.*\))/))
return t1 tag nextil(t2);
match(t2, /^[^\]]*/);
alt = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(match(t2, /^\(/)){
# Inline
sub(/^\(/, "", t2);
match(t2, /^[^\)]+/);
url = eschtml(substr(t2, 1, RLENGTH));
t2 = substr(t2, RLENGTH + 2);
title = "";
if(match(url, /[ ]+\".*\"[ ]*$/)) {
title = substr(url, RSTART, RLENGTH);
url = substr(url, 1, RSTART - 1);
match(title, /\".*\"/);
title = " title=\"" substr(title, RSTART + 1, RLENGTH - 2) "\"";
}
if(match(url, /^<.*>$/))
url = substr(url, 2, RLENGTH - 2);
return t1 "<img src=\"" url "\" alt=\"" alt "\"" title " />" nextil(t2);
}
else{
# Referenced
sub(/^ ?\[/, "", t2);
id = alt;
if(match(t2, /^[^\]]+/))
id = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(ref[id])
r = ref[id];
else{
r = "<<" id;
nr++;
}
return t1 "<img src=\"" r "\" alt=\"" alt "\" />" nextil(t2);
}
}
# Links
if(tag == "["){
if(!match(t2, /(\[.*\])|(\(.*\))/))
return t1 tag nextil(t2);
match(t2, /^[^\]]*(\[[^\]]*\][^\]]*)*/);
linktext = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(match(t2, /^\(/)){
# Inline
match(t2, /^[^\)]+(\([^\)]+\)[^\)]*)*/);
url = substr(t2, 2, RLENGTH - 1);
pt2 = substr(t2, RLENGTH + 2);
title = "";
if(match(url, /[ ]+\".*\"[ ]*$/)) {
title = substr(url, RSTART, RLENGTH);
url = substr(url, 1, RSTART - 1);
match(title, /\".*\"/);
title = " title=\"" substr(title, RSTART + 1, RLENGTH - 2) "\"";
}
if(match(url, /^<.*>$/))
url = substr(url, 2, RLENGTH - 2);
url = eschtml(url);
return t1 "<a href=\"" url "\"" title ">" nextil(linktext) "</a>" nextil(pt2);
}
else{
# Referenced
sub(/^ ?\[/, "", t2);
id = linktext;
if(match(t2, /^[^\]]+/))
id = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(ref[id])
r = ref[id];
else{
r = "<<" id;
nr++;
}
pt2 = t2;
return t1 "<a href=\"" r "\" />" nextil(linktext) "</a>" nextil(pt2);
}
}
# Emphasis
if(match(tag, /[*_]/)){
ntag = tag;
if(sub("^" tag, "", t2)){
if(stag[ns] == tag && match(t2, "^" tag))
t2 = tag t2;
else
ntag = tag tag
}
n = length(ntag);
tag = (n == 2) ? "strong" : "em";
if(match(t1, / $/) && match(t2, /^ /))
return t1 tag nextil(t2);
if(stag[ns] == ntag){
tag = "/" tag;
ns--;
}
else
stag[++ns] = ntag;
tag = "<" tag ">";
return t1 tag nextil(t2);
}
}
function inline(t) {
ilcode = 0;
ilcode2 = 0;
ns = 0;
return nextil(t);
}
function printp(tag) {
if(!match(text, /^[ ]*$/)){
text = inline(text);
if(tag != "")
oprint("<" tag ">" text "</" tag ">");
else
oprint(text);
}
text = "";
}
BEGIN {
blank = 0;
code = 0;
hr = 0;
html = 0;
nl = 0;
nr = 0;
otext = "";
text = "";
par = "p";
}
# References
!code && /^ *\[[^\]]*\]:[ ]+/ {
sub(/^ *\[/, "");
match($0, /\]/);
id = substr($0, 1, RSTART - 1);
sub(id "\\]:[ ]+", "");
title = "";
if(match($0, /\".*\"$/))
title = "\" title=\"" substr($0, RSTART + 1, RLENGTH - 2);
sub(/[ ]+\".*\"$/, "");
url = eschtml($0);
ref[id] = url title;
subref(id);
next;
}
# html
!html && /^<(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
isindex|menu|noframes|noscript|ol|p|pre|table|ul|!--)/ {
if(code)
oprint("</pre></code>");
for(; !text && block[nl] == "blockquote"; nl--)
oprint("</blockquote>");
match($0, /^<(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
isindex|menu|noframes|noscript|ol|p|pre|table|ul|!--)/);
htag = substr($0, 2, RLENGTH - 1);
if(!match($0, "(<\\/" htag ">)|((^<hr ?\\/?)|(--)>$)"))
html = 1;
if(html && match($0, /^<hr/))
hr = 1;
oprint($0);
next;
}
html && (/(^<\/(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
isindex|menu|noframes|noscript|ol|p|pre|table|ul).*)|(--)>$/ ||
(hr && />$/)) {
html = 0;
hr = 0;
oprint($0);
next;
}
html {
oprint($0);
next;
}
# List and quote blocks
# Remove indentation
{
for(nnl = 0; nnl < nl; nnl++)
if((match(block[nnl + 1], /[ou]l/) && !sub(/^( | )/, "")) || \
(block[nnl + 1] == "blockquote" && !sub(/^> ?/, "")))
break;
}
nnl < nl && !blank && text && ! /^ ? ? ?([*+-]|([0-9]+\.)+)( +| )/ { nnl = nl; }
# Quote blocks
{
while(sub(/^> /, ""))
nblock[++nnl] = "blockquote";
}
# Horizontal rules
{ hr = 0; }
(blank || (!text && !code)) && /^ ? ? ?([-*_][ ]*)([-*_][ ]*)([-*_][ ]*)+$/ {
if(code){
oprint("</pre></code>");
code = 0;
}
blank = 0;
nnl = 0;
hr = 1;
}
# List items
block[nl] ~ /[ou]l/ && /^$/ {
blank = 1;
next;
}
{ newli = 0; }
!hr && (nnl != nl || !text || block[nl] ~ /[ou]l/) && /^ ? ? ?[*+-]( +| )/ {
sub(/^ ? ? ?[*+-]( +| )/, "");
nnl++;
nblock[nnl] = "ul";
newli = 1;
}
(nnl != nl || !text || block[nl] ~ /[ou]l/) && /^ ? ? ?([0-9]+\.)+( +| )/ {
sub(/^ ? ? ?([0-9]+\.)+( +| )/, "");
nnl++;
nblock[nnl] = "ol";
newli = 1;
}
newli {
if(blank && nnl == nl && !par)
par = "p";
blank = 0;
printp(par);
if(nnl == nl && block[nl] == nblock[nl])
oprint("</li><li>");
}
blank && ! /^$/ {
if(match(block[nnl], /[ou]l/) && !par)
par = "p";
printp(par);
par = "p";
blank = 0;
}
# Close old blocks and open new ones
nnl != nl || nblock[nl] != block[nl] {
if(code){
oprint("</pre></code>");
code = 0;
}
printp(par);
b = (nnl > nl) ? nblock[nnl] : block[nnl];
par = (match(b, /[ou]l/)) ? "" : "p";
}
nnl < nl || (nnl == nl && nblock[nl] != block[nl]) {
for(; nl > nnl || (nnl == nl && pblock[nl] != block[nl]); nl--){
if(match(block[nl], /[ou]l/))
oprint("</li>");
oprint("</" block[nl] ">");
}
}
nnl > nl {
for(; nl < nnl; nl++){
block[nl + 1] = nblock[nl + 1];
oprint("<" block[nl + 1] ">");
if(match(block[nl + 1], /[ou]l/))
oprint("<li>");
}
}
hr {
oprint("<hr>");
next;
}
# Code blocks
code && /^$/ {
if(blanK)
oprint("");
blank = 1;
next;
}
!text && sub(/^( | )/, "") {
if(blanK)
oprint("");
blank = 0;
if(!code)
oprint("<code><pre>");
code = 1;
$0 = eschtml($0);
oprint($0);
next;
}
code {
oprint("</pre></code>");
code = 0;
}
# Setex-style Headers
text && /^=+$/ {printp("h1"); next;}
text && /^-+$/ {printp("h2"); next;}
# Atx-Style headers
/^#+/ && (!newli || par=="p" || /^##/) {
for(n = 0; n < 6 && sub(/^# */, ""); n++)
sub(/#$/, "");
par = "h" n;
}
# Paragraph
/^$/ {
printp(par);
par = "p";
next;
}
# Add text
{ text = (text ? text " " : "") $0; }
END {
if(code){
oprint("</pre></code>");
code = 0;
}
printp(par);
for(; nl > 0; nl--){
if(match(block[nl], /[ou]l/))
oprint("</li>");
oprint("</" block[nl] ">");
}
gsub(/<<[^\"]*/, "", otext);
print(otext);
}

View file

@ -0,0 +1,6 @@
#!/bin/rc
if(~ $REMOTE_USER ''){
extra_headers=($extra_headers 'WWW-Authenticate: Basic realm="'$"SERVER_NAME'"')
error 401
exit
}

View file

@ -0,0 +1,46 @@
#!/bin/rc
fn filter_headers{
response=(200 OK)
lines=''
done=false
while(~ $done false){
line=`{getline}
head=`{echo $line | awk '{print tolower($1)}'}
if(~ $head status:*)
response=`{echo $line | awk '{$1="" ; print}'}
if not if(~ $line '')
done=true
if not
lines=$"lines^$"line^$cr^'
'
}
echo 'HTTP/1.1' $"response^$cr
echo -n $"lines
do_log $response(1)
}
fn run_cgi {
path=$cgi_path exec $"cgi_bin $params || echo 'Status: 500'
}
cgi_bin=$1
cgi_dir=.
if(! ~ $#* 1)
cgi_dir=$*($#*)
if not if(~ $"cgi_bin /*){
cgi_dir=`{basename -d $"cgi_bin}
cgi_dir=$"cgi_dir
}
if(! ~ $"cgi_bin */*)
cgi_bin=./$"cgi_bin
if(! builtin cd $"cgi_dir >[2]/dev/null || ! test -x $"cgi_bin){
error 500
exit
}
run_cgi | {
filter_headers
emit_extra_headers
echo $cr
exec cat
}

View file

@ -0,0 +1,111 @@
#!/bin/rc
PATH_INFO=`{echo $PATH_INFO | urldecode.awk}
full_path=$"FS_ROOT^$"PATH_INFO
full_path=$"full_path
if(! test -d $full_path){
error 404
exit
}
if(! test -r $full_path -x $full_path){
error 503
exit
}
do_log 200
builtin cd $full_path
if(~ $"NOINDEXFILE ^ $"NOINDEX ''){
ifile=index.htm*
if(! ~ $ifile(1) *'*'){
PATH_INFO=$ifile(1)
FS_ROOT=''
exec serve-static
}
}
title=`{echo $SITE_TITLE | sed s,%s,^$"PATH_INFO^,}
title=$"title
lso=()
switch($2){
case size
# ls has no option to sort by size
# could pipe it through sort, I suppose
case date
lso=-t
}
echo 'HTTP/1.1 200 OK'^$cr
emit_extra_headers
echo 'Content-type: text/html'^$cr
echo $cr
echo '<html>
<head>
<title>'^$title^'</title>
<style type="text/css">
.size {
text-align: right;
padding-right: 4pt;
}
.day {
text-align: right;
padding-right: 3pt;
}
.datetime {
text-align: right;
}
.name {
text-align: right;
padding-left: 3pt;
}
</style>
</head>
<body>'
echo '<h1>'^$title^'</h1>'
if(! ~ $PATH_INFO /)
echo '<a href="../">Parent directory</a>'
echo '<table>'
ls -lQ $lso | awk '
function urlencode(loc){
# very minimal encoding, just enough for our static-file purposes
url=loc
gsub("%", "%25", url) # this one first!
gsub("\\$", "%24", url)
gsub("&", "%26", url)
gsub("\\+", "%2B", url)
gsub("\\?", "%3F", url)
gsub(" ", "%20", url)
gsub("\"", "%22", url)
gsub("#", "%23", url)
return url
}
function hrsize(size){
if(size > 1073741824) return sprintf("%.1fGB", size/1073741824)
if(size > 10485760) return sprintf("%iMB", size/1048576)
if(size > 1048576) return sprintf("%.1fMB", size/1048576)
if(size > 10240) return sprintf("%iKB", size/1024)
if(size > 1024) return sprintf("%.1fKB", size/1024)
return sprintf("%iB", size)
}
/^(-|a)/ {
print "<tr>"
print "<td class=\"size\">"hrsize($6)"</td>"
print "<td class=\"month\">"$7"</td>"
print "<td class=\"day\">"$8"</td>"
print "<td class=\"datetime\">"$9"</td>"
$1="" ; $2="" ; $3="" ; $4="" ; $5="" ; $6="" ; $7="" ; $8="" ; $9=""
sub("^ *?", "")
print "<td><a class=\"file name\" href=\""urlencode($0)"\">"$0"</a></td>"
print "</tr>"
$0=""
}
/^d/ {
print "<tr>"
print "<td class=\"size\"> </td>"
print "<td class=\"month\">"$7"</td>"
print "<td class=\"day\">"$8"</td>"
print "<td class=\"datetime\">"$9"</td>"
$1="" ; $2="" ; $3="" ; $4="" ; $5="" ; $6="" ; $7="" ; $8="" ; $9=""
sub("^ *?", "")
print "<td><a class=\"dir name\" href=\""urlencode($0)"/\">"$0"/</a></td>"
print "</tr>"
}'
echo '</table>
</body>
</html>'

View file

@ -0,0 +1,43 @@
#!/bin/rc
# DO NOT make this script callable directly from the web!
fn do_error{
echo 'HTTP/1.1 '^$1^$cr
emit_extra_headers
echo 'Content-type: text/html'^$cr
echo $cr
echo '<html>
<head>
<title>'^$1^'</title>
</head>
<body>
<h1>'^$1^'</h1>'
echo $2
echo '<p><i>rc-httpd at' $SERVER_NAME '</i>'
echo '
</body>
</html>
'
}
fn 401{
do_error '401 Unauthorized' \
'The requested path '^$"location^' requires authorization.'
}
fn 404{
do_error '404 Not Found' \
'The requested path '^$"location^' was not found on this server.'
}
fn 500{
do_error '500 Internal Server Error' \
'The server has encountered an internal misconfiguration and is unable to satisfy your request.'
}
fn 503{
do_error '503 Forbidden' \
'You do not have permission to access '^$"location^' on this server.'
}
do_log $1
$1

View file

@ -0,0 +1,30 @@
#!/bin/rc
if(~ $#2 0){
error 500
exit
}
switch($1){
case perm*
do_log 301
echo 'HTTP/1.1 301 Moved Permanently'^$cr
case temp*
do_log 302
echo 'HTTP/1.1 302 Moved Temporarily'^$cr
case seeother
do_log 303
echo 'HTTP/1.1 303 See Other'^$cr
case *
error 500
exit
}
echo 'Location: ' ^ $2 ^ $cr
emit_extra_headers
echo 'Content-type: text/html'^$cr
echo $cr
echo '<html><body>'
if(~ $#3 0)
echo 'Browser did not accept redirect.'
if not
echo $3
echo '<a href="'^$"location^'/">Click here</a>'
echo '</body></html>'

View file

@ -0,0 +1,43 @@
#!/bin/rc
full_path=`{echo $"FS_ROOT^$"PATH_INFO | urldecode.awk}
full_path=$"full_path
if(~ $full_path */)
error 503
if(test -d $full_path){
redirect perm $"location^'/' \
'URL not quite right, and browser did not accept redirect.'
exit
}
if(! test -e $full_path){
error 404
exit
}
if(! test -r $full_path){
error 503
exit
}
do_log 200
switch($full_path){
case *.html *.htm
type=text/html
case *.css
type=text/css
case *.txt
type='text/plain; charset=utf-8'
case *.jpg *.jpeg
type=image/jpeg
case *.gif
type=image/gif
case *.png
type=image/png
case *
type=`{file -m $full_path || file -i $full_path} # GROSS
}
max_age=3600 # 1 hour
echo 'HTTP/1.1 200 OK'^$cr
emit_extra_headers
echo 'Content-type: '^$type^'; charset=utf-8'^$cr
echo 'Content-length: '^`{ls -l $full_path | awk '{print $6}'}
echo 'Cache-control: max-age='^$max_age^$cr
echo $cr
exec cat $full_path

View file

@ -0,0 +1,14 @@
#!/bin/rc
cgiargs=$*
fn error{
if(~ $1 404)
exec cgi $cgiargs
if not
$rc_httpd_dir/handlers/error $1
}
if(~ $location */)
exec cgi $cgiargs
if not
exec serve-static

View file

@ -0,0 +1,5 @@
#!/bin/rc
if(~ $PATH_INFO */)
exec dir-index $params
if not
exec serve-static

View file

@ -0,0 +1,39 @@
# taken from werc
BEGIN {
hextab ["0"] = 0; hextab ["8"] = 8;
hextab ["1"] = 1; hextab ["9"] = 9;
hextab ["2"] = 2; hextab ["A"] = hextab ["a"] = 10
hextab ["3"] = 3; hextab ["B"] = hextab ["b"] = 11;
hextab ["4"] = 4; hextab ["C"] = hextab ["c"] = 12;
hextab ["5"] = 5; hextab ["D"] = hextab ["d"] = 13;
hextab ["6"] = 6; hextab ["E"] = hextab ["e"] = 14;
hextab ["7"] = 7; hextab ["F"] = hextab ["f"] = 15;
}
{
decoded = ""
i = 1
len = length ($0)
while ( i <= len ) {
c = substr ($0, i, 1)
if ( c == "%" ) {
if ( i+2 <= len ) {
c1 = substr ($0, i+1, 1)
c2 = substr ($0, i+2, 1)
if ( hextab [c1] == "" || hextab [c2] == "" ) {
print "WARNING: invalid hex encoding: %" c1 c2 | "cat >&2"
} else {
code = 0 + hextab [c1] * 16 + hextab [c2] + 0
c = sprintf ("%c", code)
i = i + 2
}
} else {
print "WARNING: invalid % encoding: " substr ($0, i, len - i)
}
} else if ( c == "+" ) {
c = " "
}
decoded = decoded c
++i
}
printf "%s", decoded
}

View file

@ -0,0 +1,102 @@
#!/bin/rc
rc_httpd_dir=/home/sl/www/werc/bin/contrib/rc-httpd
libdir = $rc_httpd_dir/lib
path=($PLAN9/bin $rc_httpd_dir/handlers $PATH)
cgi_path=$PLAN9/bin
SERVER_PORT=80 # default for CGI scripts, may be overridden by the Host header
extra_headers='Server: rc-httpd'
cr=
fn do_log{
echo `{date} :: $SERVER_NAME :: $request :: \
$HTTP_USER_AGENT :: $1 :: $HTTP_REFERER >[1=2]
}
fn emit_extra_headers{
for(header in $extra_headers)
echo $"header^$cr
}
fn getline{ read | sed 's/'^$"cr^'$//g' }
fn terminate{
echo `{date} connection terminated >[1=2]
exit terminate
}
fn trim_input{ dd -bs 1 -count $CONTENT_LENGTH }
request=`{getline}
if(~ $#request 0)
terminate
REQUEST_METHOD=$request(1)
REQUEST_URI=$request(2)
reqlines=''
HTTP_COOKIE=''
REMOTE_USER=''
done=false
chunked=no
while(~ $"done false){
line=`{getline}
if(~ $#line 0)
done=true
reqlines=$"reqlines$"line'
'
h=`{echo $line | awk '{print tolower($1)}'}
switch($h){
case ''
done=true
case host:
SERVER_NAME=$line(2)
case referer:
HTTP_REFERER=$line(2)
case user-agent:
HTTP_USER_AGENT=`{echo $line | sed 's;[^:]+:[ ]+;;'}
case content-length:
CONTENT_LENGTH=$line(2)
case content-type:
CONTENT_TYPE=$line(2)
case cookie:
cookie=`{echo $line | sed 's;^[^:]+:[ ]*;;'}
HTTP_COOKIE=$"HTTP_COOKIE^$"cookie^'; '
case authorization:
REMOTE_USER=`{auth/httpauth $line(3)}
case transfer-encoding:
~ $line(2) chunked && chunked=yes
}
}
if(~ $REQUEST_URI *://* //*){
SERVER_NAME=`{echo $REQUEST_URI | sed '
s;^[^:]+:;;
s;^//([^/]+).*;\1;'}
REQUEST_URI=`{echo $REQUEST_URI | sed '
s;^[^:]+:;;
s;^//[^/]+/?;/;'}
}
QUERY_STRING=`{echo $REQUEST_URI | sed 's;[^?]*\??;;'}
params=`{echo $QUERY_STRING | sed 's;\+; ;g'}
location=`{echo $REQUEST_URI | sed 's;\?.*;;'}
location=`{echo $location | sed '
s;[^/]+/\.\./;/;g
s;/\./;/;g
s;//+;/;g
'}
SERVER_NAME=`{echo $SERVER_NAME | sed 's;^(\[[^\]]+\]|[^:]+)\:([0-9]+)$;\1 \2;'}
if(~ $#SERVER_NAME 2){
SERVER_PORT=$SERVER_NAME(2)
SERVER_NAME=$SERVER_NAME(1)
}
if(~ $REQUEST_METHOD (PUT POST)){
if(! ~ $"CONTENT_LENGTH '')
trim_input | exec $rc_httpd_dir/select-handler
if not{
if(~ $chunked yes){
echo 'HTTP/1.1 411 Length required'^$cr
echo $cr
exit
}
exec $rc_httpd_dir/select-handler
}
}
if not
. $rc_httpd_dir/select-handler

View file

@ -0,0 +1,20 @@
#!/bin/rc
rfork n
# Route requests to werc.
# Change paths to match your system.
if(~ $SERVER_NAME 9base.werc.cat-v.org)
PLAN9=/usr/local/9base
if(~ $SERVER_NAME frontbase.werc.cat-v.org)
PLAN9=/usr/local/plan9front
if(~ $SERVER_NAME plan9port.werc.cat-v.org)
PLAN9=/usr/local/plan9
if(~ $SERVER_NAME *){
PATH_INFO=$location
FS_ROOT=/home/sl/www/werc/sites/$SERVER_NAME
exec static-or-cgi /home/sl/www/werc/bin/werc.rc
}
if not
error 503

7
werc/bin/contrib/tcp80 Executable file
View file

@ -0,0 +1,7 @@
#!/bin/rc
# For use with listen(8).
# Change paths to match your system.
# Eitdit rc-httpd/rc-httpd to match your system.
PLAN9=/usr/local/plan9
PATH=($PATH /home/sl/www/werc/bin/contrib)
exec /home/sl/www/werc/bin/contrib/rc-httpd/rc-httpd >>[2]/var/log/rc-httpd

39
werc/bin/contrib/urldecode.awk Executable file
View file

@ -0,0 +1,39 @@
#!/bin/awk -f
BEGIN {
hextab ["0"] = 0; hextab ["8"] = 8;
hextab ["1"] = 1; hextab ["9"] = 9;
hextab ["2"] = 2; hextab ["A"] = hextab ["a"] = 10
hextab ["3"] = 3; hextab ["B"] = hextab ["b"] = 11;
hextab ["4"] = 4; hextab ["C"] = hextab ["c"] = 12;
hextab ["5"] = 5; hextab ["D"] = hextab ["d"] = 13;
hextab ["6"] = 6; hextab ["E"] = hextab ["e"] = 14;
hextab ["7"] = 7; hextab ["F"] = hextab ["f"] = 15;
}
{
decoded = ""
i = 1
len = length ($0)
while ( i <= len ) {
c = substr ($0, i, 1)
if ( c == "%" ) {
if ( i+2 <= len ) {
c1 = substr ($0, i+1, 1)
c2 = substr ($0, i+2, 1)
if ( hextab [c1] == "" || hextab [c2] == "" ) {
print "WARNING: invalid hex encoding: %" c1 c2 | "cat >&2"
} else {
code = 0 + hextab [c1] * 16 + hextab [c2] + 0
c = sprintf ("%c", code)
i = i + 2
}
} else {
print "WARNING: invalid % encoding: " substr ($0, i, len - i)
}
} else if ( c == "+" ) {
c = " "
}
decoded = decoded c
++i
}
print decoded
}

126
werc/bin/contrib/urlencode.awk Executable file
View file

@ -0,0 +1,126 @@
# Taken from http://www.shelldorado.com/scripts/cmds/urlencode
##########################################################################
# Title : urlencode - encode URL data
# Author : Heiner Steven (heiner.steven@odn.de)
# Date : 2000-03-15
# Requires : awk
# Categories : File Conversion, WWW, CGI
# SCCS-Id. : @(#) urlencode 1.4 06/10/29
##########################################################################
# Description
# Encode data according to
# RFC 1738: "Uniform Resource Locators (URL)" and
# RFC 1866: "Hypertext Markup Language - 2.0" (HTML)
#
# This encoding is used i.e. for the MIME type
# "application/x-www-form-urlencoded"
#
# Notes
# o The default behaviour is not to encode the line endings. This
# may not be what was intended, because the result will be
# multiple lines of output (which cannot be used in an URL or a
# HTTP "POST" request). If the desired output should be one
# line, use the "-l" option.
#
# o The "-l" option assumes, that the end-of-line is denoted by
# the character LF (ASCII 10). This is not true for Windows or
# Mac systems, where the end of a line is denoted by the two
# characters CR LF (ASCII 13 10).
# We use this for symmetry; data processed in the following way:
# cat | urlencode -l | urldecode -l
# should (and will) result in the original data
#
# o Large lines (or binary files) will break many AWK
# implementations. If you get the message
# awk: record `...' too long
# record number xxx
# consider using GNU AWK (gawk).
#
# o urlencode will always terminate it's output with an EOL
# character
#
# Thanks to Stefan Brozinski for pointing out a bug related to non-standard
# locales.
#
# See also
# urldecode
##########################################################################
PN=`basename "$0"` # Program name
VER='1.4'
: ${AWK=awk}
Usage () {
echo >&2 "$PN - encode URL data, $VER
usage: $PN [-l] [file ...]
-l: encode line endings (result will be one line of output)
The default is to encode each input line on its own."
exit 1
}
Msg () {
for MsgLine
do echo "$PN: $MsgLine" >&2
done
}
Fatal () { Msg "$@"; exit 1; }
set -- `getopt hl "$@" 2>/dev/null` || Usage
[ $# -lt 1 ] && Usage # "getopt" detected an error
EncodeEOL=no
while [ $# -gt 0 ]
do
case "$1" in
-l) EncodeEOL=yes;;
--) shift; break;;
-h) Usage;;
-*) Usage;;
*) break;; # First file name
esac
shift
done
LANG=C export LANG
$AWK '
BEGIN {
# We assume an awk implementation that is just plain dumb.
# We will convert an character to its ASCII value with the
# table ord[], and produce two-digit hexadecimal output
# without the printf("%02X") feature.
EOL = "%0A" # "end of line" string (encoded)
split ("1 2 3 4 5 6 7 8 9 A B C D E F", hextab, " ")
hextab [0] = 0
for ( i=1; i<=255; ++i ) ord [ sprintf ("%c", i) "" ] = i + 0
if ("'"$EncodeEOL"'" == "yes") EncodeEOL = 1; else EncodeEOL = 0
}
{
encoded = ""
for ( i=1; i<=length ($0); ++i ) {
c = substr ($0, i, 1)
if ( c ~ /[a-zA-Z0-9.-]/ ) {
encoded = encoded c # safe character
} else if ( c == " " ) {
encoded = encoded "+" # special handling
} else {
# unsafe character, encode it as a two-digit hex-number
lo = ord [c] % 16
hi = int (ord [c] / 16);
encoded = encoded "%" hextab [hi] hextab [lo]
}
}
if ( EncodeEOL ) {
printf ("%s", encoded EOL)
} else {
print encoded
}
}
END {
#if ( EncodeEOL ) print ""
}
' "$@"

30
werc/bin/contrib/webserver.rc Executable file
View file

@ -0,0 +1,30 @@
#!/bin/rc
# A web server in rc by maht
# Originally from http://www.proweb.co.uk/~matt/rc/webserver.rc
ifs=' '
request=`{sed 1q}
url=$request(2)
file=`{echo $url | sed 's/http:\/\/[^\/]*//' | tr -d \012}
if(test -d $file){
file=$file ^'/index.html'
}
if(test -e $file) {
response='200'
}
if not {
response='404'
file='404.html'
}
echo 'HTTP/1.1 ' ^$response
echo 'Date: ' `{date}
echo 'Server: rc shell'
echo 'Content-Length: ' `{cat $file | wc -c | tr -d ' '}
echo 'Content-Type: ' `{file -i $file | awk '{ print $2 }'}
echo 'Connection: close'
echo
cat $file

157
werc/bin/corehandlers.rc Executable file
View file

@ -0,0 +1,157 @@
# Werc builtin handlers
fn nav_tree {
if(! ~ $#sideBarNavTitle 0)
echo '<p class="sideBarTitle">'$"sideBarNavTitle':</p>'
# Ignore stderr, last path element might be a file that doesn't exist (eg., foo for foo.md)
# /./ to deal with p9p's ls failure to follow dir symlinks otherwise
ls -F $sitedir/./$req_paths_list >[2]/dev/null \
| {
sed $dirfilter'/\/[^_.\/][^\/]*(\.(md|txt|html)|\/)$/!d; s!^'$sitedir'!!; '$dirclean
if(! ~ $#synth_paths 0) echo $synth_paths | tr ' ' $NEW_LINE
} | sort -u | awk -F/ '
function p(x, y, s) { for(i=0; i < x-y; i+=1) print s }
BEGIN { lNF=2; print "<ul><li><a href='/'> <span class='dt'>home</span></a></li>" }
{
d = ""
if(match($0, "/$"))
d = "/"
sub("/$", "") # Strip trailing / for dirs so NF is consistent
p(NF, lNF, "<li><ul>")
p(lNF, NF, "</ul></li>")
lNF = NF
bname = $NF d
path = $0 d
gsub(/[\-_]/, " ", bname)
# To avoid false matches add trailing / even for plain files to act as delimiter
pa = path
gsub(/[^\/]$/, "&/", pa)
if(index(ENVIRON["req_path"] "/", pa) == 1)
print "<li><a href=\"" path "\" class=\"thisPage\">&raquo; <i><span class='dt'>" bname "</span></i></a></li>"
else
print "<li><a href=\"" path "\">&rsaquo; <span class='dt'>" bname "</span></a></li>"
}
END { p(lNF, 2, "</ul></li>"); print "</ul>" }'
}
fn link_bar {
if(~ $1 -t) {
echo '<p class="sideBarTitle">'$2'</p>'
shift; shift
}
echo '<ul>'
while(! ~ $#* 0) {
echo '<li><a href="'$2'">- '$1'</a></li>'
shift; shift
}
echo '</ul>'
}
fn md_handler { $formatter $1 }
fn tpl_handler { template $* }
fn html_handler {
# body states: 0 = no <body> found, 2 = after <body>, 1 = after <body></body>, -1 = after </body>
awk 'gsub(".*<[Bb][Oo][Dd][Yy][^>]*>", "") > 0 {body=2}
gsub("</ *[Bb][Oo][Dd][Yy][^>]*>.*", "") > 0 {print; body=body-1}
body==2 {print}
body==0 {buf=buf "\n" $0}
END {if(body<=0) {print buf}}' < $1
}
fn txt_handler {
# Note: Words are not broken, even if they are way beyond 82 chars long
echo '<pre>'
sed 's/</\&lt;/g; s/>/\&gt;/g' < $1 | fmt -l 82 -j
echo '</pre>'
}
fn dir_listing_handler {
d=`{basename -d $1}
if(~ $#d 0)
d='/'
echo $d|sed 's,.*//,,g; s,/$,,; s,/, / ,g; s,.*,<h1 class="dir-list-head">&</h1> <ul class="dir-list">,'
# Symlinks suck: '/.' forces ls to list the linked dir if $d is a symlink.
ls -F $dir_listing_ls_opts $sitedir$d/. | sed $dirfilter$dirclean' s,.*/([^/]+/?)$,<li><a href="\1">\1</a></li>,'
echo '</ul>'
}
fn notices_handler {
for(type in notify_errors notify_notes notify_success)
for(n in $$type)
echo '<div class="'$type'"><b>'$"n'</b></div>'
}
fn setup_handlers {
if(test -f $local_path.md) {
local_file=$local_path.md
handler_body_main=(md_handler $local_file)
}
if not if(test -f $local_path.tpl) {
local_file=$local_path.tpl
handler_body_main=(tpl_handler $local_file)
}
if not if(test -f $local_path.html) {
local_file=$local_path.html
handler_body_main=(html_handler $local_file)
}
# Global tpl (eg sitemap.tpl), should take precedence over txt handler!
if not if(test -f tpl^$req_path^.tpl)
# XXX Should we set $local_file for global .tpls?
handler_body_main=(tpl_handler tpl^$req_path^.tpl)
if not if(test -f $local_path.txt) {
local_file=$local_path.txt
handler_body_main=(txt_handler $local_file)
}
# XXX Should check that $enabled_apps exist in $werc_apps?
# XXX Should split init of apps that provide main handler (eg., blog) and apps that don't (eg., comments)?
if(! ~ $#enabled_apps 0)
for(a in $enabled_apps)
$a^'_init'
if(! ~ $#handler_body_main 0) {
# WE ARE NOT DONE I WANT MY HEADERS
if(test -f $sitedir$req_path'_header.md')
ll_add handlers_body_head md_handler $sitedir$req_path'_header.md'
if(test -f $sitedir$req_path'_footer.md')
ll_add handlers_body_foot md_handler $sitedir$req_path'_footer.md'
} # We are done
# Dir listing
if not if(~ $local_path */index) {
handler_body_main=(dir_listing_handler $req_path)
if(test -f $sitedir$req_path'_header.md')
ll_add handlers_body_head md_handler $sitedir$req_path'_header.md'
if(test -f $sitedir$req_path'_footer.md')
ll_add handlers_body_foot md_handler $sitedir$req_path'_footer.md'
}
# Canonize explicit .html urls, the web server might handle this first!
if not if(~ $local_path *.html && test -f $local_path)
perm_redirect `{ echo $req_path|sed 's/.html$//' }
# Fallback static file handler
if not if(test -f $local_path)
static_file $local_path
if not if(~ $req_path /pub/* && test -f .$req_path)
static_file .$req_path
# File not found
if not
setup_404_handler
}
# This function allows config files to define their own 404 handlers.
fn setup_404_handler {
handler_body_main=(tpl_handler `{get_lib_file 404.tpl})
echo 'Status: 404 Not Found'
dprint 'NOT FOUND: '$SERVER_NAME^$"REQUEST_URI^' - '^$"HTTP_REFERER^' - '^$"HTTP_USER_AGENT
}
fn run_handlers { for(h in $*) run_handler $$h }
fn run_handler { $*(1) $*(2-) }

37
werc/bin/fltr_cache.rc Executable file
View file

@ -0,0 +1,37 @@
#!/bin/rc
fn fltr_cache {
a=()
tmpf=()
proc=$1
shift
if(~ $#* 0) {
tmpf=/tmp/fmttmp.$pid
f=$tmpf
score=`{{tee $tmpf || exit 1} | sha1sum}
}
if not {
f=$1
if(~ $f */) {
score=`{du -an $f | sha1sum || exit 1} # XXX using -n(bytes) instead of -t(lastmod) because sitemap proc touches files in tree.
a=$f
f=/dev/null
}
if not {
score=`{sha1sum $f || exit 1}
score=$score(1)
}
}
cachedir=/tmp/fltr_cache/$score
mkdir -p $cachedir >[2]/dev/null
if(test -s $cachedir/$proc)
cat $cachedir/$proc
if not
if($proc $a < $f | tee $cachedir/$pid)
mv $cachedir/$pid $cachedir/$proc
rm $tmpf $cachedir/$pid >[2]/dev/null &
}

55
werc/bin/template.awk Executable file
View file

@ -0,0 +1,55 @@
#!/bin/awk -f
function pr(str) {
if(lastc !~ "[{(]")
gsub(/'/, "''", str)
printf "%s", str
}
function trans(c) {
printf "%s", end
lastc = c
end = "\n"
if(c == "%")
end = ""
else if(c == "(")
printf "echo -n "
else if(c ~ "[})]") {
end = "'\n"
printf "echo -n '"
}
}
BEGIN {
lastc = "{"
trans("}")
}
END {
print end
}
/^%/ && $0 !~ /^%[{()}%]/ && lastc !~ /[({]/ {
trans("%")
print substr($0, 2)
next
}
{
if(lastc == "%")
trans("}")
n = split($0, a, "%")
pr(a[1])
for(i=2; i<=n; i++) {
c = substr(a[i], 1, 1)
rest = substr(a[i], 2)
if((lastc !~ "[({]" && c ~ "[({]") ||
(lastc == "{" && c == "}") ||
(lastc == "(" && c == ")"))
trans(c)
else if(c == "%")
pr("%")
else
pr("%" c)
pr(rest)
}
pr("\n")
}

138
werc/bin/werc.rc Executable file
View file

@ -0,0 +1,138 @@
#!/bin/rc
. ./cgilib.rc
. ./werclib.rc
. ./wercconf.rc
. ./corehandlers.rc
. ./fltr_cache.rc
cd ..
forbidden_uri_chars='[^a-zA-Z0-9_+\-\/\.,:]'
difs=$ifs # Used to restore default ifs when needed
# Expected input: ls -F style, $sitedir/path/to/files/
# <ls -F+x><symlink hack><Useless?><hiden files >
dirfilter='s/\*$//; s,/+\./+,/,g; s,^\./,,; /\/[._][^\/]/d; /'$forbidden_uri_chars'/d; /\/sitemap\.xml$/d; /\/index\.(md|html|txt|tpl)$/d; /\/(robots|sitemap)\.txt$/d; /_werc\/?$/d; '
dirclean=' s/\.(md|html|txt)$//; '
# Careful, the proper p9p path might not be set until initrc.local is sourced
path=(. /bin ./bin)
res_tail='</body></html>'
http_content_type='text/html'
ll_add handlers_bar_left nav_tree
werc_apps=( apps/* )
werc_root=`{pwd}
sitesdir=sites
. ./etc/initrc
if(test -f etc/initrc.local)
. ./etc/initrc.local
for(a in $werc_apps)
. ./$a/app.rc
fn werc_exec_request {
site=$SERVER_NAME
base_url=http://$site:$SERVER_PORT
sitedir=$sitesdir/$site
headers=`{get_lib_file headers.tpl}
master_template=`{get_lib_file default_master.tpl}
current_date_time=`{date}
# Note: $REQUEST_URI is not officially in CGI 1.1, but seems to be de-facto
# Note: We only urldecode %5F->'_' because some sites (stackoverflow.com?) urlencode it in their links,
# perhaps we should completel urldecode the whole url.
req_path=`{echo -n $REQUEST_URI | sed 's/\?.*//; s!//+!/!g; s/%5[Ff]/_/g; s/'^$forbidden_uri_chars^'//g; s/\.\.*/./g; 1q'}
req_url=$base_url^$req_path
local_path=$sitedir$req_path
local_file=''
ifs='/' { args=`{echo -n $req_path} }
# Preload post args for templates where cgi's stdin is not accessible
if(~ $REQUEST_METHOD POST) {
load_post_args
login_user
}
if(~ $req_path */index)
perm_redirect `{echo $req_path | sed 's,/index$,/,'}
if(~ $local_path */) {
if(test -d $local_path)
local_path=$local_path^'index'
# XXX: This redir might step on apps with synthetic dirs.
if not if(ls `{basename -d $local_path}^* >/dev/null >[2]/dev/null)
perm_redirect `{echo $req_path|sed 's,/+$,,'}
}
if not if(~ $req_path *'.' *',' *';' *':')
perm_redirect `{echo $req_path | sed 's/[.,;:)]$//'}
if not if(test -d $local_path)
perm_redirect $req_path^'/'
if(! ~ $#args 0)
ifs=$NEW_LINE { pageTitle=`{ echo $args|sed -e 's/ / - /g' -e 's/([a-z])-([a-z])/\1 \2/g' -e 's/_/ /g' } }
cd $sitedir
req_paths_list='/' # Note: req_paths_list doesn't include 'stnythetic' dirs.
conf_wd='/' # Used in config files to know where we are in the document tree.
if(test -f _werc/config)
. _werc/config
for(i in $args) {
conf_wd=$conf_wd^$i
req_paths_list=($req_paths_list $conf_wd)
if(test -d $i) {
conf_wd=$conf_wd'/'
cd $i
if(test -f _werc/config)
. _werc/config
}
}
cd $werc_root
if(~ $#perm_redir_to 1)
perm_redirect $perm_redir_to
for(l in $perm_redir_patterns) {
p=$$l
r=$p(1)
# If target is absolute, then patern must match whole string
if(~ $p(2) http://* https://*)
r='^'$r
t=`{ echo $req_path | sed 's!'^$r^'!'^$p(2)^'!' } # Malicious danger!
if(! ~ $"t '' $req_path)
perm_redirect $t
}
setup_handlers
# Set Page title
if(! ~ $local_file '') {
t=`{get_file_title $local_file}
if(! ~ $"t '')
pageTitle=$t
}
# XXX Is this never true? because we set pageTitle earlier based on url.
if(~ $"pageTitle '')
pageTitle=$"siteTitle' '$"siteSubTitle
# if not
# pageTitle=$"pageTitle' | '$"siteTitle' '$"siteSubTitle
for(h in $extraHttpHeaders)
echo $h
echo Content-Type: $http_content_type
echo # End of HTTP headers
if(! ~ $#debug 0)
dprint $"SERVER_NAME^$"REQUEST_URI - $"HTTP_USER_AGENT - $"REQUEST_METHOD - $"handler_body_main - $"master_template
if(~ $REQUEST_METHOD HEAD)
exit
template $headers $master_template # | awk_buffer
echo $res_tail
}
werc_exec_request

5
werc/bin/werc_errlog_wrap.rc Executable file
View file

@ -0,0 +1,5 @@
#!/bin/rc
# This is a wrapper script for broken http servers like recent lighttpd versions which throw away cgi's stderr.
./werc.rc >>[2]/tmp/wlog.txt

19
werc/bin/wercconf.rc Executable file
View file

@ -0,0 +1,19 @@
# To be used from config files
fn conf_perm_redirect {
if(~ $#* 1)
perm_redir_to=$1
if not
ll_addh perm_redir_patterns $1 $2
}
fn conf_hide_paths {
for(i in $*)
dirfilter=$dirfilter^'/'^`{echo $sitedir$conf_wd$i|sed 's!/+!\\/!g'}^'/d; '
}
# Usually will be called from within conf_enable_foo
fn conf_enable_app {
# Note: maybe we should add test -d apps/$1/?
if(! ~ $1 $enabled_apps)
enabled_apps=( $enabled_apps $1 )
}

393
werc/bin/werclib.rc Executable file
View file

@ -0,0 +1,393 @@
fn get_lib_file {
if(! ~ $#sitedir 0 && test -f $sitedir/_werc/lib/$1)
echo -n $sitedir/_werc/lib/$1
if not if(! ~ $#masterSite 0 && test -f $sitesdir/$masterSite/_werc/lib/$1)
echo -n $sitesdir/$masterSite/_werc/lib/$1
if not if(test -f lib/$1)
echo -n lib/$1
if not if(~ $#* 2)
echo -n $2
if not
status='Can''t find lib file: '$1
}
fn template { awk -f bin/template.awk $* | rc $rcargs }
# Auth code
# TODO: check http://cookies.lcs.mit.edu/pubs/webauth:tr.pdf
allowed_user_chars='[a-zA-Z0-9_]'
# Cookie format: WERC_USER: name:timestamp:hash(name.timestamp.password)
# login_user can't be used from a template because it sets a cookie
fn login_user {
# Note: we set the cookie even if it is already there.
if(get_user $*)
set_cookie werc_user $"logged_user^':0:'^$"logged_password
}
# Check login status, if called with group arg we check membership too
fn check_user {
get_user
g=($* admin)
_status=$status
if(! ~ $"_status '')
_status=(Not logged in: $"_status)
if not if(! ~ $#* 0 && ! ~ $logged_user $* && ! grep -s '^'^$logged_user^'$' $werc_root/etc/users/$g/members >[2]/dev/null)
_status=(User $logged_user not in: $*)
status=$_status
}
# If not logged in, try to get user login info from POST or from cookie
fn get_user {
if(~ $#logged_user 0) {
if(~ $#* 2) {
user_name=$1
user_password=$2
}
if not if(~ $REQUEST_METHOD POST)
get_post_args user_name user_password
if(~ $#user_name 0) {
ifs=':' { cu=`{ifs=$difs {get_cookie werc_user} | tr -d $NEW_LINE} }
if(! ~ $#cu 0) {
user_name=$cu(1)
user_password=$cu(3)
}
}
auth_user $user_name $user_password
}
if not
status=()
}
# Check if user_name and user_password represent a valid user account
# If valid, 'log in' by setting logged_user
fn auth_user {
user_name=$1
user_password=$2
pfile=$werc_root/etc/users/$"user_name/password
if(~ $#user_name 0 || ~ $#user_password 0)
status=('Auth: missing user name or pass: '^$"user_name^' / '^$"user_password)
if not if(! test -f $pfile)
status=('Auth: cant find '^$pfile)
if not if(! test -s $pfile || ! ~ $user_password `{cat $pfile})
status=('Auth: Pass '$user_password' doesnt match '^`{cat $pfile})
if not {
logged_user=$user_name
logged_password=$user_password
dprint Auth: success
status=()
}
}
fn user_controls {
echo User: $"logged_user
}
# .md '(meta-)data' extract
fn get_md_file_attr {
sed -n '/^\* '$2': /p; /^\* '$2': /q; /^$/q' < $1
}
# File title extraction
fn get_md_title {
#sed 's/^(................................................................[^ ]*).*$/\1/g; 1q' < $1
sed -n -e '1N; /^.*\n===*$/N; /.*\n===*\n *$/!b' -e 's/\n==*\n//p' < $1
}
fn get_html_title {
t=`{sed -n '32q; s/^.*<[Tt][Ii][Tt][Ll][Ee]> *([^<]+) *(<\/[Tt][Ii][Tt][Ll][Ee]>.*)?$/\1/p' < $1}
# As a backup we might want to pick the first 'non-tag' text in the file with:
if(~ $"t '')
t=`{sed -n -e 's/^(<[^>]+>)*([^<]+).*/\2/p; 32q' < $1 | sed 1q}
echo $t
}
fn get_file_title {
if (~ $1 *.md)
get_md_title $1
if not if(~ $1 *.html)
get_html_title $1
if not if(~ $1 */) {
if(test -f $1/index.md)
get_md_title $1/index.md
if not if(test -f $1/index.html)
get_html_title $1/index.html
}
}
fn ndate {
if(~ $#* 7)
date=$*(2-)
if not
date=`{date}
switch($date(2)){
case Jan; mo=01
case Feb; mo=02
case Mar; mo=03
case Apr; mo=04
case May; mo=05
case Jun; mo=06
case Jul; mo=07
case Aug; mo=08
case Sep; mo=09
case Oct; mo=10
case Nov; mo=11
case Dec; mo=12
}
switch($date(3)){
case [0-9]
da=0^$date(3)
case *
da=$date(3)
}
switch($date(5)){
case A; tz=+0100
case ADT; tz=-0300
case AFT; tz=+430
case AKDT; tz=-0800
case AKST; tz=-0900
case ALMT; tz=+0600
case AMST; tz=-0300
case AMT; tz=-0400
case ANAST; tz=+1200
case ANAT; tz=+1200
case AQTT; tz=+0500
case ART; tz=-0300
case AST; tz=-0400
case AZOST; tz=+0000
case AZOT; tz=-0100
case AZST; tz=+0500
case AZT; tz=+0400
case B; tz=+0200
case BNT; tz=+0800
case BOT; tz=-0400
case BRST; tz=-0200
case BRT; tz=-0300
case BST; tz=+0100
case BTT; tz=+0600
case C; tz=+0300
case CAST; tz=+0800
case CAT; tz=+0200
case CCT; tz=+0630
case CDT; tz=-0500
case CEST; tz=+0200
case CET; tz=+0100
case CHADT; tz=+1345
case CHAST; tz=+1245
case CKT; tz=-1000
case CLST; tz=-0300
case CLT; tz=-0400
case COT; tz=-0500
case CST; tz=-0600
case CVT; tz=-0100
case CXT; tz=+0700
case ChST; tz=+1000
case D; tz=+0400
case DAVT; tz=+0700
case E; tz=+0500
case EASST; tz=-0500
case EAST; tz=-0600
case EAT; tz=+0300
case ECT; tz=-0500
case EDT; tz=-0400
case EEST; tz=+0300
case EET; tz=+0200
case EGST; tz=+0000
case EGT; tz=-0100
case EST; tz=-0500
case ET; tz=-0500
case F; tz=+0600
case FJST; tz=+1300
case FJT; tz=+1200
case FKST; tz=-0300
case FKT; tz=-0400
case FNT; tz=-0200
case G; tz=+0700
case GALT; tz=-0600
case GAMT; tz=-0900
case GET; tz=+0400
case GFT; tz=-0300
case GILT; tz=+1200
case GMT; tz=+0000
case GST; tz=+0400
case GYT; tz=-0400
case H; tz=+0800
case HAA; tz=-0300
case HAC; tz=-0500
case HADT; tz=-0900
case HAE; tz=-0400
case HAP; tz=-0700
case HAR; tz=-0600
case HAST; tz=-1000
case HAT; tz=-0230
case HAY; tz=-0800
case HKT; tz=+0800
case HLV; tz=-0430
case HNA; tz=-0400
case HNC; tz=-0600
case HNE; tz=-0500
case HNP; tz=-0800
case HNR; tz=-0700
case HNT; tz=-0330
case HNY; tz=-0900
case HOVT; tz=+0700
case I; tz=+0900
case ICT; tz=+0700
case IDT; tz=+0300
case IOT; tz=+0600
case IRDT; tz=+0430
case IRKST; tz=+0900
case IRKT; tz=+0800
case IRST; tz=+0330
case IST; tz=+0200
case JST; tz=+0900
case K; tz=+1000
case KGT; tz=+0600
case KRAST; tz=+0800
case KRAT; tz=+0700
case KST; tz=+0900
case KUYT; tz=+0400
case L; tz=+1100
case LHDT; tz=+1100
case LHST; tz=+1030
case LINT; tz=+1400
case M; tz=+1200
case MAGST; tz=+1200
case MAGT; tz=+1100
case MART; tz=-0930
case MAWT; tz=+0500
case MDT; tz=-0600
case MHT; tz=+1200
case MMT; tz=+0630
case MSD; tz=+0400
case MSK; tz=+0300
case MST; tz=-0700
case MUT; tz=+0400
case MVT; tz=+0500
case MYT; tz=+0800
case N; tz=-0100
case NCT; tz=+1100
case NDT; tz=-0230
case NFT; tz=+1130
case NOVST; tz=+0700
case NOVT; tz=+0600
case NPT; tz=+0545
case NST; tz=-0330
case NUT; tz=-1100
case NZDT; tz=+1300
case NZST; tz=+1200
case O; tz=-0200
case OMSST; tz=+0700
case OMST; tz=+0600
case P; tz=-0300
case PDT; tz=-0700
case PET; tz=-0500
case PETST; tz=+1200
case PETT; tz=+1200
case PGT; tz=+1000
case PHOT; tz=+1300
case PHT; tz=+0800
case PKT; tz=+0500
case PMDT; tz=-0200
case PMST; tz=-0300
case PONT; tz=+1100
case PST; tz=-0800
case PT; tz=-0800
case PWT; tz=+0900
case PYST; tz=-0300
case PYT; tz=-0400
case Q; tz=-0400
case R; tz=-0500
case RET; tz=+0400
case S; tz=-0600
case SAMT; tz=+0400
case SAST; tz=+0200
case SBT; tz=+1100
case SCT; tz=+0400
case SGT; tz=+0800
case SRT; tz=-0300
case SST; tz=-1100
case T; tz=-0700
case TAHT; tz=-1000
case TFT; tz=+0500
case TJT; tz=+0500
case TKT; tz=-1000
case TLT; tz=+0900
case TMT; tz=+0500
case TVT; tz=+1200
case U; tz=-0800
case ULAT; tz=+0800
case UYST; tz=-0200
case UYT; tz=-0300
case UZT; tz=+0500
case V; tz=-0900
case VET; tz=-0430
case VLAST; tz=+1100
case VLAT; tz=+1000
case VUT; tz=+1100
case W; tz=-1000
case WAST; tz=+0200
case WAT; tz=+0100
case WDT; tz=+0900
case WEST; tz=+0100
case WET; tz=+0000
case WFT; tz=+1200
case WGST; tz=-0200
case WGT; tz=-0300
case WIB; tz=+0700
case WIT; tz=+0900
case WITA; tz=+0800
case WST; tz=+0800
case WT; tz=+0000
case X; tz=-1100
case Y; tz=-1200
case YAKST; tz=+1000
case YAKT; tz=+0900
case YAPT; tz=+1000
case YEKST; tz=+0600
case YEKT; tz=+0500
case Z; tz=+0000
}
switch($1){
case -a # rfc3339
tz=`{echo $tz | sed 's/00$/:00/'}
echo $date(6)^-$mo-$da^T^$date(4)^$tz
case -i # iso-8601 lite
echo $date(6)^-$mo-$da
case -m # rfc2822
echo $date(1)^, $da $date(2) $date(6) $date(4) $tz
case -t # iso-8601
echo $date(6)^-$mo-$da^T^$date(4)^$tz
}
}
##########################################################################
##########################################################################
#app_blog_methods = ( _post index.rss )
#fn app_blog__post {
# echo
#}
#
#app_blog___default {
# if (~ $blog)
# call_app blogpost
#}
#
## --
#app_blogpost_methods = ( comment _edit )
#
#fn app_blogpost_comment {
# call_app comments
#}
#
## --
#app_comments_methods = ( _post _edit )
#
#fn app_comments___default {
#
#}