--- /dev/null
+.PHONY: _subtrees
+
+_subtrees: _cgilite
+
+cgilite:
+ git subtree add --squash -P $@ https://git.plutz.net/git/$@ master
+
+_cgilite: cgilite
+ git subtree pull --squash -P $< https://git.plutz.net/git/$< master
--- /dev/null
+#!/bin/sh
+
+# ACL_OVERRIDE="${ACL_OVERRIDE:-Admin:read,write}"
+ACL_DEFAULT="${ACL_DEFAULT:-All:read${BR}Known:read,write}"
+
+acl_cachepath=''
+acl_collection=''
+
+acl_collect(){
+ local path="${1:-${PATH_INFO}}"
+ # Get directory part of PATH_INFO
+ local path="${path%/*}/./"
+ local pagefile head acl
+
+ if [ "$acl_cachepath" = "$path" ]; then
+ printf '%s\n' "$ACL_OVERRIDE" "$acl_collection" "$ACL_DEFAULT"
+ return 0
+ else
+ acl_cachepath="$path"
+ acl_collection=''
+ fi
+
+ printf '%s\n' "$ACL_OVERRIDE"
+
+ while :; do
+ [ "$path" = / ] && break
+ path="${path%/*/}/"
+
+ if [ -f "$_DATA/pages/$path/#page.md" ]; then
+ pagefile="$_DATA/pages/$path/#page.md"
+ elif [ -f "$_EXEC/pages/$path/#page.md" ]; then
+ pagefile="$_EXEC/pages/$path/#page.md"
+ else
+ continue
+ fi
+
+ n=20; while read -r head acl; do
+ if [ "$head" = "%acl" ]; then
+ acl_collection="${acl%${CR}}${BR}"
+ printf "%s\n" "${acl%${CR}}"
+ n=$((n+1))
+ fi
+
+ n="$((n - 1))"
+ [ "$n" -eq 0 ] && break
+ done <"$pagefile"
+ done
+
+ printf '%s\n' "$ACL_DEFAULT"
+}
+
+acl_read(){
+ local page="${1:-${PATH_INFO}}"
+ local acl
+
+ while read -r acl; do
+ case ${acl##*:} in
+ read|*,read,*|read,*|*,read)
+ acl="${acl%%:*}:read";;
+ *) acl="${acl%%:*}:";;
+ esac
+ [ "$USER_NAME" ] && case $acl in
+ "Known:read") return 0;;
+ "Known:") return 1;;
+ "+Known:read") return 0;;
+ "-Known:read") return 1;;
+ "@${USER_NAME}:read") return 0;;
+ "@${USER_NAME}:") return 1;;
+ "+@{$USER_NAME}:read") return 0;;
+ "-@{$USER_NAME}:read") return 1;;
+ esac
+ case $acl in
+ "All:read") return 0;;
+ "All:") return 1;;
+ "+All:read") return 0;;
+ "-All:read") return 1;;
+ esac
+ done <<-EOF
+ $(acl_collect "$page")
+ EOF
+ return 1
+}
+
+acl_write(){
+ local page="${1:-${PATH_INFO}}"
+ local acl
+
+ while read -r acl; do
+ case ${acl##*:} in
+ write|*,write,*|write,*|*,write)
+ acl="${acl%%:*}:write";;
+ *) acl="${acl%%:*}:";;
+ esac
+ [ "$USER_NAME" ] && case ${acl} in
+ "Known:write") return 0;;
+ "Known:") return 1;;
+ "+Known:write") return 0;;
+ "-Known:write") return 1;;
+ "@${USER_NAME}:write") return 0;;
+ "@${USER_NAME}:") return 1;;
+ "+@{$USER_NAME}:write") return 0;;
+ "-@{$USER_NAME}:write") return 1;;
+ esac
+ case $acl in
+ "All:write") return 0;;
+ "All:") return 1;;
+ "+All:write") return 0;;
+ "-All:write") return 1;;
+ esac
+ done <<-EOF
+ $(acl_collect "$page")
+ EOF
+ return 1
+}
--- /dev/null
+#!/bin/sh
+
+attachment_convert(){
+ local attpath="$1"
+ local cachepath="${attpath%/#attachments/*}/#cache/${attpath#*/#attachments/}"
+ local res junk
+
+ case $attpath in
+ *.webm|*.mp4|*.mkv|*.avi)
+ cachepath="${cachepath}.webm"
+ ;;
+ esac
+
+ if [ -s "$cachepath" ]; then
+ printf %s "$cachepath"
+ return 0
+ elif [ -f "$cachepath" ]; then
+ printf %s "$attpath"
+ return 0
+ elif ! mkdir -p -- "${cachepath%/*}" && touch "$cachepath"; then
+ printf %s "$attpath"
+ return 0
+ fi
+
+ case $attpath in
+ *.jpg|*.jpeg|*.png)
+ read junk junk res junk <<-EOF
+ $(identify "$attpath")
+ EOF
+ if [ "${res%x*}" -gt 2048 ]; then
+ convert "$attpath" -resize 1920x-2 -quality 85 "$cachepath"
+ else
+ convert "$attpath" -quality 85 "$cachepath"
+ fi
+ printf %s "$cachepath"
+ return 0
+ ;;
+ *.webm|*.mp4|*.mkv|*.avi)
+ res=$(ffprobe -show_entries stream=width "$attpath" 2>&-)
+ res="${res#*width=}" res="${res%%${BR}*}"
+ if [ "$res" -gt 1280 ]; then
+ ( exec >&- 2>&1;
+ ffmpeg -y -nostdin -i "$attpath" \
+ -c:v libvpx -vf scale=1280:-2 -crf 28 -b:v 0 \
+ -c:a libvorbis -q:a 6 \
+ "${cachepath%.*}.tmp.webm" \
+ && mv -- "${cachepatch%.*}.tmp.webm" "${cachepath}" \
+ & ) &
+
+ else
+ ( exec >&- 2>&1;
+ ffmpeg -y -nostdin -i "$attpath" \
+ -c:v libvpx -crf 28 -b:v 0 \
+ -c:a libvorbis -q:a 6 \
+ "${cachepath%.*}.tmp.webm" \
+ && mv -- "${cachepatch%.*}.tmp.webm" "${cachepath}" \
+ & ) &
+ fi
+ printf %s "$attpath"
+ return 0
+ ;;
+ esac
+}
+
+if [ "${PATH_INFO%/\[attachment\]/}" != "${PATH_INFO}" ]; then
+ tsid="$(POST session_key)"; tsid="${tsid%% *}"
+ attachment_delete="$(POST delete)"
+
+ if [ "${CONTENT_TYPE%%;*}" = "multipart/form-data" ] && acl_write "${PATH_INFO%\[attachment\]/}"; then
+ . "$_EXEC/multipart.sh"
+ multipart_cache
+
+ # Validate session id from form to prevent CSRF
+ # Only validate if username is present, because no username means
+ # anonymous uploads are allowed via acl and cgilite/session.sh does not
+ # validate anonymous sessions from a multipart/formdata
+ if [ "$USER_NAME" -a "$(multipart session_id)" != "$SESSION_ID" ]; then
+ rm -- "$multipart_cachefile"
+ printf 'Refresh: %i\r\n' 4
+ theme_error 403
+ return 0
+ fi
+
+ mkdir -p "$_DATA/pages/${PATH_INFO%/\[attachment\]/}/#attachments/"
+ n=1; while filename=$(multipart_filename "file" "$n"); do
+ filename="$(printf %s "$filename" |tr /\\0 __)"
+ multipart "file" "$n" >"$_DATA/pages/${PATH_INFO%/\[attachment\]/}/#attachments/$filename"
+ n=$((n + 1))
+ done
+ rm -- "$multipart_cachefile"
+ REDIRECT "${_BASE}${PATH_INFO}"
+ elif [ "${CONTENT_TYPE%%;*}" = "multipart/form-data" ]; then
+ printf 'Refresh: %i\r\n' 4
+ theme_error 403
+ head -c $((CONTENT_LENGTH)) >/dev/null
+ return 0
+ elif [ "$attachment_delete" -a "$SESSION_ID" = "$tsid" ]; then
+ rm -- "$_DATA/pages/${PATH_INFO%/\[attachment\]/}/#attachments/$attachment_delete"
+ REDIRECT "${_BASE}${PATH_INFO}"
+ elif [ "$attachment_delete" ]; then
+ printf 'Refresh: %i\r\n' 4
+ theme_error 403
+ return 0
+ elif acl_read "${PATH_INFO%\[attachment\]/}"; then
+ theme_attachments "${PATH_INFO%\[attachment\]/}"
+ return 0
+ else
+ theme_error 404
+ return 0
+ fi
+
+elif [ "${PATH_INFO%/\[attachment\]/*}" != "${PATH_INFO}" ]; then
+ attpath="${PATH_INFO%/\[attachment\]/*}/#attachments/${PATH_INFO##*/}"
+
+ if ! acl_read "${PATH_INFO%/\[attachment\]/*}"; then
+ theme_error 403
+ return 0
+ elif [ -f "$_DATA/pages/$attpath" ]; then
+ FILE "$_DATA/pages/$attpath"
+ return 0
+ elif [ -f "$_EXEC/pages/$attpath" ]; then
+ FILE "$_EXEC/pages/$attpath"
+ return 0
+ else
+ theme_error 404
+ return 0
+ fi
+# exit 0;
+
+elif [ "${PATH_INFO%/}" = "${PATH_INFO}" ]; then
+ attpath="${PATH_INFO%/*}/#attachments/${PATH_INFO##*/}"
+
+ if ! acl_read "${PATH_INFO%/*}/"; then
+ theme_error 403
+ return 0
+ elif [ -f "$_DATA/pages/$attpath" ]; then
+ FILE "$(attachment_convert "$_DATA/pages/$attpath")"
+ return 0
+ elif [ -f "$_EXEC/pages/$attpath" ]; then
+ FILE "$(attachment_convert "$_EXEC/pages/$attpath")"
+ return 0
+ elif [ -d "$_DATA/pages/${PATH_INFO}" -o -d "$_EXEC/pages/${PATH_INFO}" ]; then
+ REDIRECT "${_BASE}${PATH_INFO}/"
+ elif [ "${PATH_INFO%\[*\]}" = "${PATH_INFO}" ]; then
+ theme_error 404
+ return 0
+ fi
+fi
+
+return 1
--- /dev/null
+#!/bin/sh
+
+. "${_EXEC:-${0%/*}}/cgilite/cgilite.sh"
+. "${_EXEC}/cgilite/session.sh"
+. "${_EXEC}/cgilite/file.sh"
+. "${_EXEC}/cgilite/users.sh"
+. "${_EXEC}/acl.sh"
+
+. "${_EXEC}/themes/default.sh"
+
+CACHE_AGE=${CACHE_AGE:-1800}
+export MD_MACROS="$_EXEC/macros"
+export MD_HTML="${MD_HTML:-false}"
+
+if [ "$(which awk)" ]; then
+ md() { awk -f "$_EXEC/md_macros.awk" -f "$_EXEC/cgilite/markdown.awk"; }
+elif [ "$(which busybox)" ]; then
+ md() { busybox awk -f "$_EXEC/md_macros.awk" -f "$_EXEC/cgilite/markdown.awk"; }
+else
+ md() { cat; }
+fi
+
+mdfile(){
+ local page="$(PATH "$1")"
+
+ if [ -f "$_DATA/pages/$page/#page.md" ]; then
+ printf %s\\n "$_DATA/pages/$page/#page.md"
+ elif [ -f "$_EXEC/pages/$page/#page.md" ]; then
+ printf %s\\n "$_EXEC/pages/$page/#page.md"
+ else
+ return 1
+ fi
+}
+
+wiki_text() {
+ # Print source text of a wiki page
+ # Get page from data or underlay dir
+ local page="$(PATH "$1")" mdfile
+
+ mdfile="$(mdfile "$page")" || return 4
+ acl_read "$page" || return 3
+ cat -- "$mdfile"
+}
+
+wiki() {
+ # Print content of a wiki page
+ # Get page from data or underlay dir, handle caching
+ local page="$(PATH "$1")" mdfile cache cachetime
+
+ cache="$_DATA/pages/$page/#page.${USER_ID}.cache"
+
+ mdfile="$(mdfile "$page")" || return 4
+ acl_read "$page" || return 3
+
+ cachetime="$(stat -c %Y -- "$mdfile" "$cache" 2>/dev/null)"
+
+ if [ "${cachetime#*${BR}}" -gt "${cachetime%${BR}*}" \
+ -a "${cachetime#*${BR}}" -gt "$((_DATE - CACHE_AGE))" ]; then
+ cat "${cache}"
+ else
+ mkdir -p -- "$_DATA/pages/$page/"
+ # Macros expect to find page directory as working dir
+ ( cd -- "$_DATA/pages/$page/";
+ sed -E '1,20{ /^%[a-z]+/d; }' "$mdfile" \
+ | md |tee -- "${cache}.$$"
+ )
+ grep -q '^%nocache' "$mdfile" \
+ && rm -- "${cache}.$$" \
+ || mv -- "${cache}.$$" "${cache}"
+ fi
+}
+
+case "${PATH_INFO}" in
+ /"[.]"/*)
+ FILE "${_EXEC}/${PATH_INFO#/\[.\]}"
+ ;;
+ */"#"*/*)
+ export ERROR_MSG="This page name is not allowed"
+ theme_error 400
+ ;;
+ /|*[^]]/)
+ theme_page "${PATH_INFO}"
+ ;;
+ */"[login]")
+ theme_page "/[wiki]/login/"
+ ;;
+ */"[register]")
+ theme_page "/[wiki]/register/"
+ ;;
+ */"[invite]")
+ theme_page "/[wiki]/invite/"
+ ;;
+ *)
+ . "$_EXEC/page_edit.sh" \
+ || . "$_EXEC/attachment.sh" \
+ || theme_error 404
+ ;;
+esac
+
--- /dev/null
+#!/bin/sh
+
+. "$_EXEC/cgilite/cgilite.sh"
+. "$_EXEC/tools.sh"
+
+page="$1"
+
+if [ "${page#/}" = "$page" ]; then
+ page="$(PATH "${PATH_INFO}/$page")"
+fi
+
+printf %s\\n '<ul class="macro attachment list">'
+
+for file in "$_EXEC/pages/$page/#attachments"/* "$_DATA/pages/$page/#attachments"/*; do
+ [ "$file" = "$_EXEC/pages/$page/#attachments/${file##*/}" \
+ -a -f "$_DATA/pages/$page/#attachments/${file##*/}" ] && continue
+ stat="$(stat -c '%s %Y' -- "$file" 2>&-)" || continue
+ size="${stat% *}" date="${stat#* }"
+
+ printf '<li><span class=name>%s</span>
+ <span class=size>%s</span><span class=date>%s</span></li>' \
+ "$(HTML "${file##*/}")" "$(size_human "$size")" "$(date -d @"$date" +"%F %T")"
+done
+
+printf %s\\n '</ul>'
--- /dev/null
+#!/bin/sh
+
+. "$_EXEC/cgilite/cgilite.sh"
+
+if [ "$1" -o "$ERROR_MSG" ]; then
+ printf '<p .error>%s</p>' "$(HTML "${1:-${ERROR_MSG}}")"
+fi
--- /dev/null
+#!/bin/sh
+
+. "$_EXEC/cgilite/cgilite.sh"
+
+title="$(HTML "$1")"
+ref="$(HEADER Referer)"
+
+printf '<a class=reflink href="%s">%s</a>' "${ref:-./}" "${title:-Return}"
--- /dev/null
+#!/bin/sh
+
+. "$_EXEC/cgilite/cgilite.sh"
+
+unset MD_MACROS
+
+if [ "$(which awk)" ]; then
+ md() { awk -f "$_EXEC/cgilite/markdown.awk"; }
+elif [ "$(which busybox)" ]; then
+ md() { busybox awk -f "$_EXEC/cgilite/markdown.awk"; }
+else
+ md() { cat; }
+fi
+
+md |sed -nE '
+ 1i<ul class="macro toc">
+ s;<(h[1-6]) id="([^"]*)">([^<]+)</h[1-6]>;<li class="toc \1"><a href="#\2">\3</a></li>;p
+ $i</ul>
+'
--- /dev/null
+#!/bin/sh
+
+action="$1"
+
+. "$_EXEC/cgilite/cgilite.sh"
+. "$_EXEC/cgilite/users.sh"
+
+case $action in
+ login)
+ w_user_login |"$_EXEC/cgilite/html-sh.sed"
+ ;;
+ register)
+ w_user_register |"$_EXEC/cgilite/html-sh.sed"
+ ;;
+ invite)
+ w_user_invite |"$_EXEC/cgilite/html-sh.sed"
+ ;;
+esac
--- /dev/null
+#!/bin/awk -f
+#!/opt/busybox/awk -f
+
+function sh_escape(arg){
+ return "'" gensub(/'/, "'\"'\"'", "g", arg) "'";
+}
+
+function argsplit(line, args, LOCAL, c, n, ctx) {
+ ctx="space"; n=0;
+
+ while ( length(line) > 0 ) {
+ c = substr(line, 1, 1);
+ line = substr(line, 2);
+ if (ctx == "space" )
+ if (c ~ /[ \t]/) ctx = "space";
+ else if (c ~ /\\/) { n++; ctx = "escbare"; }
+ else if (c ~ /"/) { n++; ctx = "dquot"; }
+ else if (c ~ /'/) { n++; ctx = "squot"; }
+ else { n++; args[n] = c; ctx = "bare"; }
+ else if (ctx == "bare")
+ if (c ~ /[ \t]/) ctx = "space";
+ else if (c ~ /\\/) ctx = "escbare";
+ else if (c ~ /"/) ctx = "dquot";
+ else if (c ~ /'/) ctx = "squot";
+ else args[n] = args[n] c;
+ else if (ctx == "dquot")
+ if (c ~ /"/) ctx = "bare";
+ else if (c ~ /\\/) ctx = "escdquot";
+ else args[n] = args[n] c;
+ else if (ctx == "squot")
+ if (c ~ /'/) ctx = "bare";
+ else args[n] = args[n] c;
+ else if (ctx == "escbare") {
+ args[n] = args[n] c;
+ ctx = "bare";
+ }
+ else if (ctx == "escdquot") {
+ args[n] = args[n] c;
+ ctx = "dquot";
+ }
+ }
+}
+
+function macro(call, LOCAL, line, args) {
+ argsplit(call, args);
+ call="";
+
+ for (n = 1; n in args; n++) call = call sh_escape(args[n]) " ";
+
+ if (args[1] in MACROS) {
+ RS=""; ORS=""; line="";
+ "printf '%s' " sh_escape(file) " | " sh_escape(ENVIRON["MD_MACROS"]) "/" call | getline line;
+ return line;
+ } else {
+ return HTML("<<" call ">>");
+ }
+}
+
+BEGIN {
+ if (ENVIRON["MD_MACROS"]) {
+ AllowMacros = "true";
+ "cd " sh_escape(ENVIRON["MD_MACROS"]) "; printf '%s/' *" |getline macro_list;
+ split(macro_list, MACROS, "/");
+ for (n in MACROS) { MACROS[MACROS[n]] = ""; delete MACROS[n]; }
+ delete MACROS[""];
+ }
+}
--- /dev/null
+#!/bin/sh
+
+[ "$include_multipart" ] && return 0
+inlude_multipart="$0"
+
+if [ "${CONTENT_TYPE}" -a ! "${CONTENT_TYPE##multipart/form-data;*}" ]; then
+ multipart_boundary="${CONTENT_TYPE#*; boundary=}"
+ multipart_boundary="${multipart_boundary%%;*}"
+ multipart_boundary="${multipart_boundary%${CR}}"
+fi
+multipart_cachefile="/tmp/multipart.$$"
+
+readbytes(){
+ # read n bytes, like `head -c` but do not consume input
+ local size="$1" block
+
+ for block in 65536 32768 16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1; do
+ if [ $size -ge $block ]; then
+ dd status=none bs="$block" count="$((size / block))"
+ size="$((size % block))"
+ fi
+ done
+}
+
+multipart_cache() {
+ multipart_cachefile="${1:-${multipart_cachefile}}" # global
+
+ if [ "${multipart_boundary}" ]; then
+ # readbytes "$(( CONTENT_LENGTH ))" >"${multipart_cachefile}"
+ head -c "$(( CONTENT_LENGTH ))" >"${multipart_cachefile}"
+ else
+ return 1
+ fi
+}
+
+multipart(){
+ local name="$1" count="${2:-1}"
+ local formdata state=begin
+
+ while IFS='' read -r formdata; do case "$formdata" in
+ "--${multipart_boundary}--${CR}")
+ [ $state = data ] && return 0 \
+ || return 1
+ ;;
+ "--${multipart_boundary}${CR}")
+ [ $state = data ] && return 0 \
+ || state=header
+ ;;
+ "Content-Disposition: form-data; name=\"${name}\""*"${CR}")
+ [ $state = header -a $count -eq 1 ] && state=dheader
+ [ $state = header -a $count -gt 1 ] && count=$((count - 1))
+ [ $state = data ] && printf "%s\n" "$formdata"
+ ;;
+ "${CR}")
+ if [ $state = dheader ]; then
+ sed -n "/--${multipart_boundary}\(--\)\?${CR}/q; p;" \
+ | head -c-2
+ return 0;
+ fi
+ [ $state = header ] && state=junk
+ ;;
+ esac; done <"${multipart_cachefile}"
+}
+
+multipart_filename(){
+ local name="$1" count="${2:-1}"
+ local formdata state=begin
+
+ while read -r formdata; do case "$formdata" in
+ "--${multipart_boundary}--${CR}")
+ return 1
+ ;;
+ "--${multipart_boundary}${CR}")
+ state=header
+ ;;
+ "Content-Disposition: form-data; name=\"${name}\"; filename=\""*"\""*"${CR}")
+ [ $state = header -a $count -eq 1 ] && break
+ [ $state = header -a $count -gt 1 ] && count=$((count - 1))
+ ;;
+ "${CR}")
+ [ $state = header ] && state=junk
+ ;;
+ esac; done <"${multipart_cachefile}"
+
+ filename="${formdata#*; filename=\"}"
+ filename="${filename%%\"${CR}}"
+ filename="${filename%%\";*}"
+
+ HEX_DECODE % "$filename"
+}
--- /dev/null
+#!/bin/sh
+
+. "${_EXEC}/themes/default.sh"
+. "${_EXEC}/session_lock.sh"
+
+wiki_text() {
+ # Print source text of a wiki page
+ # Get page from data or underlay dir
+ local page="$(PATH "$1")"
+
+ if [ -f "$_DATA/pages/$page/#page.md" ]; then
+ cat -- "$_DATA/pages/$page/#page.md"
+ elif [ -f "$_EXEC/pages/$page/#page.md" ]; then
+ cat -- "$_EXEC/pages/$page/#page.md"
+ else
+ return 1
+ fi
+}
+
+edit_page="${PATH_INFO%\[edit\]}"
+edit_file="$_DATA/pages/$edit_page/#page.md"
+[ "$REQUEST_METHOD" = POST ] && edit_action="$(POST action)"
+
+if [ "$edit_page" = "$PATH_INFO" ]; then
+ unset edit_page edit_action edit_file
+ # END EDIT SCRIPT, continue in index.cgi
+
+elif [ "$edit_action" = update ]; then
+ if mkdir -p -- "${edit_file%/#page.md}" \
+ && S_LOCK "$edit_file"; then
+ POST pagetext >"$edit_file"
+ S_RELEASE "$edit_file"
+ REDIRECT "${_BASE}${PATH_INFO%\[edit\]}"
+ else
+ export ERRMSG="ERR_NOLOCK"
+ REDIRECT "${_BASE}${PATH_INFO%\[edit\]}/[edit]"
+ fi
+
+elif [ "$edit_action" = cancel ]; then
+ S_RELEASE "$edit_file"
+ REDIRECT "${_BASE}${PATH_INFO%\[edit\]}"
+
+elif ! acl_write "$edit_page"; then
+ theme_error 403
+ return 0
+
+elif mkdir -p -- "${edit_file%/#page.md}" \
+ && S_LOCK "$edit_file"; then
+ theme_editor "$edit_page"
+ return 0
+
+else
+ printf 'Refresh: %i; url=%s\r\n' 4 ../
+ export ERROR_MSG="Unable to lock page for editing"
+ theme_error 409
+ return 0
+
+fi
+
+return 1
--- /dev/null
+It Works!
+=========
--- /dev/null
+%nocache
+
+400
+===
+
+**Bad Request**
+<<errormessage>>
+
+<<reflink>>
--- /dev/null
+%nocache
+
+403
+===
+
+**Forbidden**
+
+<<reflink>>
--- /dev/null
+%nocache
+
+404
+===
+
+**page not found**
+
+<<reflink>>
--- /dev/null
+%nocache
+
+409
+===
+
+**Conflict**
+<<errormessage>>
+
+<<reflink>>
--- /dev/null
+%nocache
+
+500
+===
+
+**Internal Server Error**
+<<errormessage>>
+
+<<reflink>>
--- /dev/null
+----
+Shellwiki
+Edit the Footer [here](/[wiki]/footer/[edit])
--- /dev/null
+# Shellwiki
+
+::: { .menu }
+ * [Login]([login])
+ * [Register]([register])
+:::
+
+Edit the Header [here](/[wiki]/header/[edit])
+
+----
--- /dev/null
+%nocache
+
+Set up an account
+-----------------
+<<wikiform invite>>
+
+[Return](./)
--- /dev/null
+%nocache
+
+Login
+-----
+<<wikiform login>>
+[Account registration]([register] "Sign up for a new user account")
+
+[Return](./)
--- /dev/null
+%nocache
+
+Set up an account
+-----------------
+<<wikiform register>>
+
+[Return](./)
--- /dev/null
+#!/bin/sh
+
+. "$_EXEC/cgilite/storage.sh"
+. "$_EXEC/cgilite/session.sh"
+
+LOCK_TIMEOUT="${LOCK_TIMEOUT:-1200}"
+
+S_LOCK(){
+ local file="$1" timeout="${2:-$LOCK_TIMEOUT}"
+ local date sid
+
+ printf "%i %s\n" "$_DATE" "$SESSION_ID" >>"${file}.lock"
+
+ if ! read date sid <"${file}.lock"; then
+ debug "Unable to access lock: ${file}.lock"
+
+ elif [ $((date + timeout)) -lt $_DATE ]; then
+ # Override stale lock
+ if LOCK "${file}.lock" 1; then
+ debug "Overriding stale lock: ${file}.lock"
+ printf "%i %s\n" "$_DATE" "$SESSION_ID" >"${file}.lock"
+ RELEASE "${file}.lock"
+ return 0
+ else
+ return 1
+ fi
+
+ elif [ "$sid" = "$SESSION_ID" -a "$date" -ne "$_DATE" ]; then
+ # Refresh aged lock
+ printf "%i %s\n" "$_DATE" "$SESSION_ID" >"${file}.lock"
+ return 0
+
+ elif [ "$sid" = "$SESSION_ID" ]; then
+ # Simple success
+ return 0
+
+ else
+ return 1
+ fi
+}
+
+S_RELEASE(){
+ local file="$1" timeout="${2:-$LOCK_TIMEOUT}"
+ local date sid
+
+ if ! read date sid <"${file}.lock"; then
+ # File was not locked
+ return 0
+
+ elif [ "$sid" = "$SESSION_ID" -a $((date + timeout)) -lt $_DATE ]; then
+ # if lock is stale, protect against stale override before release
+ if LOCK "${file}.lock" 1; then
+ rm -- "${file}.lock"
+ RELEASE "${file}.lock"
+ return 0
+ else
+ return 1
+ fi
+
+ elif [ "$sid" = "$SESSION_ID" ]; then
+ # Simple success
+ rm -- "${file}.lock"
+ return 0
+
+ else
+ return 1
+ fi
+}
--- /dev/null
+html { min-height: 100%; }
+
+body {
+ position: absolute;
+ width: 100%;
+ min-height: 100%;
+ padding-bottom: 6em;
+ background-color: #EEE;
+}
+
+header, footer {
+ background-color: #FFF;
+ box-shadow: 0 0 .75em;
+ width: 100%;
+ z-index: 1;
+}
+
+footer {
+ position: absolute;
+ bottom: 0;
+}
+
+header :last-child,
+main :last-child {
+ margin-bottom: 0;
+}
+
+header h2,
+header .menu {
+ display: inline-block;
+}
+
+header .menu { list-style: none; }
+
+header .menu li {
+ display: inline-block;
+ margin-right: .5em;
+}
+
+main .pagemenu {
+ list-style: none;
+ background-color: #666;
+ margin: 0;
+ box-shadow: 0 0 .5em;
+ padding: .25em 2em;
+}
+main .pagemenu li {
+ display: inline-block;
+ margin-right: 1em;
+}
+main .pagemenu li a { color: #FFF; }
+
+main article,
+[id$="/[attachment]/"] main form.upload {
+ margin: 1em;
+ padding: .125em 1em 1em 1em;
+ box-shadow: .25em .25em .75em;
+ background-color: #FFF;
+}
+
+[id$="/[attachment]/"] main .attachment.list {
+ margin: 1em;
+ padding: 1em 2em;
+}
+[id$="/[attachment]/"] main .attachment.list:before {
+ content: '';
+ position: absolute;
+ top: 0; bottom: 0; left: 0; right: 0;
+ background-color: #FFF;
+ box-shadow: .25em .25em .75em;
+}
+
+
+/* === Editor === */
+
+body.editor textarea {
+ width: 100%;
+ min-height: 20em;
+}
+
+/* === Attachments === */
+
+.attachment.list button[name=delete] {
+ font-size: .75em;
+ line-height: 1.25em;
+ margin-right: 1.25em;
+}
+.attachment.list .size,
+.attachment.list .date {
+ font-size: .875em;
+ top: -.25em;
+}
+
+.attachment.list .name:after {
+ white-space: pre-line;
+ content: "\0a";
+}
+.attachment.list .size {
+ margin-right: 1em;
+}
+
+
+/* === Macros === */
+
+.macro.toc {
+ display: inline-block;
+ list-style-position: inside;
+ margin-left: 0;
+ background-color: #DDD;
+ background-color: rgba(0, 0, 0, .125);
+ padding: .75em 1em;
+ border: 1pt solid;
+ border-radius: 2pt;
+}
+.macro.toc li.h2 { margin-left: 1.25em; }
+.macro.toc li.h3 { margin-left: 2.5em; }
+.macro.toc li.h4 { margin-left: 3.75em; }
+.macro.toc li.h5 { margin-left: 5em; }
+.macro.toc li.h6 { margin-left: 6.25em; }
+
--- /dev/null
+#!/bin/sh
+
+. "$_EXEC/tools.sh"
+
+theme_head(){
+ printf '
+ <meta name="viewport" content="width=device-width"/>
+ <link rel="stylesheet" type="text/css" href="%s/[.]/cgilite/common.css">
+ <link rel="stylesheet" type="text/css" href="%s/[.]/themes/default.css">
+ ' "$_BASE"
+}
+
+theme_header(){
+ printf '<header>%s</header>' "$(wiki '[wiki]/header/')"
+}
+
+theme_footer(){
+ printf '<footer>%s</footer>' "$(wiki '[wiki]/footer/' ||echo No footer)"
+}
+
+theme_page(){
+ local page="$1" title
+ title="${page%/}"; title="${title##*/}"
+
+ if [ ! "$(mdfile "$page")" ]; then
+ theme_error 404
+ return 0
+ elif ! acl_read "$page"; then
+ theme_error 403
+ return 0
+ fi
+
+ # Important! Web Server response including newline
+ printf "%s\r\n" "Content-Type: text/html; charset=utf-8" ""
+
+ cat <<-EOF
+ <!DOCTYPE HTML>
+ <html><head>
+ $(theme_head)
+ <title>$(HTML "${title}")</title>
+ </head><body id="$(HTML "$page")">
+ $(theme_header)
+ <main>
+ $(acl_write "$page" && printf %s \
+ '<ul class="pagemenu">
+ <li><a href="[edit]">Edit</a></li>
+ <li><a href="[attachment]/">Attachments</a></li>
+ </ul>'
+ )
+ <article>
+ $(wiki "$page" || printf 'Error while loading page <br> function "wiki" of index.sh returned with an error.')
+ </article>
+ </main>
+ $(theme_footer)
+ </body></html>
+ EOF
+}
+
+theme_editor(){
+ local page="$1" title
+ title="${page%/}"; title="${title##*/}"
+
+ if ! acl_write "$page"; then
+ theme_error 403
+ return 0
+ fi
+
+ # Important! Web Server response including newline
+ printf "%s\r\n" "Content-Type: text/html; charset=utf-8" ""
+
+ cat <<-EOF
+ <!DOCTYPE HTML>
+ <html><head>
+ $(theme_head)
+ <title>$(HTML "${title}")</title>
+ </head><body id="$(HTML "$page")" class="editor">
+ $(theme_header)
+ <main><form method=POST>
+ <input type=hidden name=session_key value="${SESSION_KEY}"/>
+ <textarea name=pagetext>$(wiki_text "$page" |HTML)</textarea>
+ <button type=submit name=action value=update>Update</button>
+ <button type=submit name=action value=cancel>Cancel</button>
+ </form></main>
+ $(theme_footer)
+ </body></html>
+ EOF
+}
+
+theme_attachments(){
+ local page="$1" title
+ title="${page%/}"; title="${title##*/}"
+
+ if [ ! "$(mdfile "$page")" ]; then
+ theme_error 404
+ return 0
+ elif ! acl_read "$page"; then
+ theme_error 403
+ return 0
+ fi
+
+ # Important! Web Server response including newline
+ printf "%s\r\n" "Content-Type: text/html; charset=utf-8" ""
+
+ if acl_write "$page"; then
+ cat <<-EOF
+ <!DOCTYPE HTML>
+ <html><head>
+ $(theme_head)
+ <title>Attachments $(HTML "${title}")</title>
+ </head><body id="$(HTML "$page")[attachment]/">
+ $(theme_header)
+ <main>
+ <form class=upload method=POST enctype="multipart/form-data">
+ <input type=hidden name=session_id value="$SESSION_ID">
+ <input type=file name=file multiple>
+ <button type=submit name=action value=upload>Upload</button>
+ </form>
+
+ <form method=POST><ul class="attachment list">
+ <input type=hidden name=session_key value="$SESSION_KEY">
+ $(for file in "$_EXEC/pages/$page/#attachments"/* "$_DATA/pages/$page/#attachments"/*; do
+ [ "$file" = "$_EXEC/pages/$page/#attachments/${file##*/}" \
+ -a -f "$_DATA/pages/$page/#attachments/${file##*/}" ] && continue
+ stat="$(stat -c '%s %Y' -- "$file" 2>&-)" || continue
+ size="${stat% *}" date="${stat#* }"
+
+ printf '<li><button type=submit name=delete value="%s">Delete</button><a class=name href="%s">%s</a>
+ <span class=size>%s</span><span class=date>%s</span></li>' \
+ "$(HTML "${file##*/}")" "$(HTML "${file##*/}")" "$(HTML "${file##*/}")" \
+ "$(size_human "$size")" "$(date -d @"$date" +"%F %T")"
+ done)
+ </ul></form>
+ </main>
+ $(theme_footer)
+ </body></html>
+ EOF
+ else
+ cat <<-EOF
+ <!DOCTYPE HTML>
+ <html><head>
+ $(theme_head)
+ <title>Attachments $(HTML "${title}")</title>
+ </head><body id="$(HTML "$page")[attachment]/">
+ $(theme_header)
+ <main>
+ <ul class="attachment list">
+ $(for file in "$_EXEC/pages/$page/#attachments"/* "$_DATA/pages/$page/#attachments"/*; do
+ [ "$file" = "$_EXEC/pages/$page/#attachments/${file##*/}" \
+ -a -f "$_DATA/pages/$page/#attachments/${file##*/}" ] && continue
+ stat="$(stat -c '%s %Y' -- "$file" 2>&-)" || continue
+ size="${stat% *}" date="${stat#* }"
+
+ printf '<li><a class=name href="%s">%s</a>
+ <span class=size>%s</span><span class=date>%s</span></li>' \
+ "$(HTML "${file##*/}")" "$(HTML "${file##*/}")" "$(size_human "$size")" "$(date -d @"$date" +"%F %T")"
+ done)
+ </ul>
+ </main>
+ $(theme_footer)
+ </body></html>
+ EOF
+ fi
+}
+
+theme_error(){
+ local errno="$1"
+
+ case $errno in
+ 400) printf "%s\r\n" "Status: 400 Bad Request";;
+ 403) printf "%s\r\n" "Status: 403 Forbidden";;
+ 404) printf "%s\r\n" "Status: 404 Not Found";;
+ 409) printf "%s\r\n" "Status: 409 Conflict";;
+ 500) printf "%s\r\n" "Status: 500 Internal Server Error";;
+ esac
+
+ if [ "$(mdfile "/[wiki]/$errno/")" ]; then
+ theme_page "/[wiki]/$errno/"
+ else
+ printf "Content-Length: 0\r\n\r\n"
+ fi
+}
--- /dev/null
+#!/bin/sh
+
+size_human(){
+ local size="$1"
+
+ if [ $size -gt $((1024 * 1024 * 1024)) ]; then
+ size=$((size / 1024 / 1024 / 1024 * 10 + size / 1024 / 1024 % 1024 / 100))
+ printf "%i.%i GB" "$((size / 10))" "$((size % 10))"
+
+ elif [ $size -gt $((1024 * 1024)) ]; then
+ size=$((size / 1024 / 1024 * 10 + size / 1024 % 1024 / 100))
+ printf "%i.%i MB" "$((size / 10))" "$((size % 10))"
+
+ elif [ $size -gt $((1024)) ]; then
+ size=$((size / 1024 * 10 + size % 1024 / 100))
+ printf "%i.%i KB" "$((size / 10))" "$((size % 10))"
+
+ else
+ printf "%i B" "$size"
+ fi
+}