From: Paul Hänsch Date: Tue, 29 Aug 2023 22:21:47 +0000 (+0200) Subject: Merge commit 'a7e74b2735ecaae6ef235cb0c4e54bc187d3fa16' X-Git-Url: https://git.plutz.net/?a=commitdiff_plain;h=225fc80e7ea2ba2da28ff7c67859b588338f5494;hp=a7e74b2735ecaae6ef235cb0c4e54bc187d3fa16;p=shellwiki Merge commit 'a7e74b2735ecaae6ef235cb0c4e54bc187d3fa16' --- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..24781a9 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +.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 diff --git a/acl.sh b/acl.sh new file mode 100755 index 0000000..1017c6e --- /dev/null +++ b/acl.sh @@ -0,0 +1,125 @@ +#!/bin/sh + +[ "$include_acl" ] && return 0 +include_acl="$0" + +# ACL_OVERRIDE="${ACL_OVERRIDE:-Admin:read,write}" +ACL_DEFAULT="${ACL_DEFAULT:-Known:read,write${BR}All:read}" + +acl_cachepath='' +acl_collection='' + +acl_collect(){ + local path="$1" + # Get directory part of PATH_INFO + local path="${path%/*}/./" + local pagefile head acl + + printf '%s\n' "$ACL_OVERRIDE" + + while :; do + [ "$path" = / ] && break + path="${path%/*/}/" + + # Do not use `mdfile` function here because of specialties + # in translation handler (`handlers/10_translations.sh`) + 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 + + acl="$(sed -En ' + s;\r$;;; + /^%acl([\t ]+.*)?$/bACL; + 20q; + b; + + :ACL + s;(%(acl)?)?[\t ]*;; + p; n; s;\r$;;; + /^(%[ \t]+|%acl[ \t]+|[ \t]+)[^ \t\r]+$/bACL; + /^(%[ \t]*|%acl[ \t]*)$/bACL; + ' <"$pagefile")" + + printf %s\\n "${acl}" + done + + printf '%s\n' "$ACL_DEFAULT" +} + +acl_read(){ + local page="${1:-${PATH_INFO}}" + local acl + + if [ "$acl_cachepath" != "$page" ]; then + acl_cachepath="$page" + acl_collection="$(acl_collect "$page")" + fi + + 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_collection} + EOF + return 1 +} + +acl_write(){ + local page="${1:-${PATH_INFO}}" + local acl + + if [ "$acl_cachepath" != "$page" ]; then + acl_cachepath="$page" + acl_collection="$(acl_collect "$page")" + fi + + 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_collection} + EOF + return 1 +} diff --git a/.gitignore b/cgilite/.gitignore similarity index 100% rename from .gitignore rename to cgilite/.gitignore diff --git a/cgilite.sh b/cgilite/cgilite.sh similarity index 100% rename from cgilite.sh rename to cgilite/cgilite.sh diff --git a/common.css b/cgilite/common.css similarity index 100% rename from common.css rename to cgilite/common.css diff --git a/file.sh b/cgilite/file.sh similarity index 100% rename from file.sh rename to cgilite/file.sh diff --git a/html-sh.sed b/cgilite/html-sh.sed similarity index 100% rename from html-sh.sed rename to cgilite/html-sh.sed diff --git a/logging.sh b/cgilite/logging.sh similarity index 100% rename from logging.sh rename to cgilite/logging.sh diff --git a/markdown.awk b/cgilite/markdown.awk similarity index 100% rename from markdown.awk rename to cgilite/markdown.awk diff --git a/session.sh b/cgilite/session.sh similarity index 100% rename from session.sh rename to cgilite/session.sh diff --git a/storage.sh b/cgilite/storage.sh similarity index 100% rename from storage.sh rename to cgilite/storage.sh diff --git a/users.sh b/cgilite/users.sh similarity index 100% rename from users.sh rename to cgilite/users.sh diff --git a/handlers/10_css.sh b/handlers/10_css.sh new file mode 100755 index 0000000..2efc07b --- /dev/null +++ b/handlers/10_css.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +css(){ + local path="${1:-${PATH_INFO}}" + local pagefile css='' + # Get directory part of PATH_INFO + path="${path%/*}/./" + + while :; do + [ "$path" = / ] && break + path="${path%/*/}/" + + if ! acl_read "$path"; then + continue + elif [ -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 + + css="$(sed -En ' + s;\r$;;; + /^%css([\t ]+.*)?$/bCSS; + 20q; + b; + + :CSS + s;(%(css)?)?[\t ]*;; + p; n; s;\r$;;; + /^(%[ \t]+|%css[ \t]+|[ \t]+)[^ \t\r]+$/bCSS; + /^(%[ \t]*|%css[ \t]*)$/bCSS; + ' <"$pagefile")${BR}${css}" + done + + printf %s\\n "${css}" +} + +PAGE_CSS="$(css "${PATH_INFO}")" + +return 1 diff --git a/handlers/10_translations.sh b/handlers/10_translations.sh new file mode 100755 index 0000000..5067677 --- /dev/null +++ b/handlers/10_translations.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +_(){ printf %s\\n "$*"; } + +# Set LANGUAGE_DEFAULT to enable Plugin +[ ! "$LANGUAGE_DEFAULT" ] && return 1 + +export LANGUAGE_DEFAULT="${LANGUAGE_DEFAULT:-en}" +export HTTP_REFERER="${HTTP_REFERER:-$(HEADER Referer)}" +export LANGUAGE ERROR_MSG + +case ${HTTP_REFERER} in + */:*/*):;; + */:*) + LANGUAGE_REFERRED="${HTTP_REFERER##*/:}" + ;; +esac + +LANGUAGE="${LANGUAGE_REFERRED:-${LANGUAGE_DEFAULT}}" + +case ${PATH_INFO} in + */:?*/\[attachment\]/?*) + LANGUAGE="${PATH_INFO#*/:}" + LANGUAGE="${LANGUAGE%%/*}" + PATH_INFO="${PATH_INFO%%:?*/*}${PATH_INFO#*/:?*/}" + ;; + */:?*/\[attachment\]) + LANGUAGE="${PATH_INFO#*/:}" + LANGUAGE="${LANGUAGE%%/*}" + PATH_INFO="${PATH_INFO%:?*/\[attachment\]}[attachment]" + ;; + */:?*/\[*\]) + LANGUAGE="${PATH_INFO#*/:}" + LANGUAGE="${LANGUAGE%%/*}" + ;; + */:?*/:?*) + # Accidental double language link, last one stays valid! + REDIRECT "${_BASE}${PATH_INFO%/:?*/:?*}/:${PATH_INFO##*/:}" + ;; + */:?*/?*) + :;; # Default attachment handler + */:?*/) # Faulty URL build + REDIRECT "${_BASE}${PATH_INFO%/}" + ;; +# */:"${LANGUAGE_DEFAULT}") +# REDIRECT "${_BASE}${PATH_INFO%:*}" +# ;; + */:?*) + LANGUAGE="${PATH_INFO##*/:}" + PATH_INFO="${PATH_INFO%:*}" + + [ "$LANGUAGE" != "$LANGUAGE_DEFAULT" ] \ + && case "$(mdfile "${PATH_INFO}")" in + *"/:$LANGUAGE/#page.md") + :;; + '') + :;; + *)ERROR_MSG="TRANSLATION NOT FOUND" + ;; + esac + ;; + /|*/*/) # Keep Language from Referer + if [ "$LANGUAGE_REFERRED" -a "$LANGUAGE_REFERRED" != "$LANGUAGE_DEFAULT" ]; then + REDIRECT "${_BASE}${PATH_INFO}:${LANGUAGE_REFERRED}" + fi + ;; + */\[*\]) # Keep Language from Referer + if [ "$LANGUAGE_REFERRED" -a "$LANGUAGE_REFERRED" != "$LANGUAGE_DEFAULT" ]; then + REDIRECT "${_BASE}${PATH_INFO%\[*\]}:${LANGUAGE_REFERRED}/[${PATH_INFO##*/\[}" + fi + ;; +esac + +[ -r "${_EXEC}/l10n/${LANGUAGE}.sh" ] && . "${_EXEC}/l10n/${LANGUAGE}.sh" + +return 1 diff --git a/handlers/20_redirect.sh b/handlers/20_redirect.sh new file mode 100755 index 0000000..9f3497e --- /dev/null +++ b/handlers/20_redirect.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +case $PATH_INFO in + *"/["*"]") + return 1; + ;; +esac + +if acl_read ${PATH_INFO}; then + mdfile="$(mdfile "${PATH_INFO%/*}")" +else + return 1 +fi + +if [ "$mdfile" ]; then + REDIRECT="$( + sed -nE ' + s;^%redirect[ \t]+([[:graph:]][[:print:]]+)\r?$;\1;p; tQ; + b; :Q q; + ' "$mdfile" + )" +else + return 1 +fi + +if [ "$REDIRECT" ]; then + REDIRECT "$REDIRECT" +fi + +return 1 diff --git a/handlers/20_title.sh b/handlers/20_title.sh new file mode 100755 index 0000000..a7c98a5 --- /dev/null +++ b/handlers/20_title.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +if acl_read ${PATH_INFO}; then + mdfile="$(mdfile "${PATH_INFO%/*}")" +else + PAGE_TITLE="${SITE_TITLE}" + return 1 +fi + +if [ "$mdfile" ]; then + PAGE_TITLE="$( + sed -nE ' + s;^%title[ \t]+([[:graph:]][[:print:]]+)\r?$;\1;p; tQ; + b; :Q q; + ' "$mdfile" + )" + [ ! "${PAGE_TITLE}" ] && PAGE_TITLE="$( + MD_MACROS="" md <"$mdfile" \ + | sed -nE ' + s;^.*]*>(.*>)?([^<]+)(<.*)?.*$;\2;p; tQ; + s;^.*]*>(.*>)?([^<]+)(<.*)?.*$;\2;p; tQ; + b; :Q q; + ' + )" +else + PAGE_TITLE="${SITE_TITLE}" + return 1 +fi + +case $PATH_INFO in + *"/[attachment]") + PAGE_TITLE="${PAGE_TITLE} ($(_ Attachments))" + ;; + *"/[revision]") + PAGE_TITLE="${PAGE_TITLE} ($(_ Revisions))" + ;; +esac + +[ "$PAGE_TITLE" ] \ +&& PAGE_TITLE="${PAGE_TITLE}${SITE_TITLE:+ - ${SITE_TITLE}}" \ +|| PAGE_TITLE="${SITE_TITLE}" + +return 1 diff --git a/handlers/30_page.sh b/handlers/30_page.sh new file mode 100755 index 0000000..5c3da0c --- /dev/null +++ b/handlers/30_page.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +. "$_EXEC/cgilite/file.sh" + +CACHE_AGE=${CACHE_AGE:-300} +export MD_MACROS="$_EXEC/macros" +export MD_HTML="${MD_HTML:-false}" + +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:${LANGUAGE}.${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/"; + md <"$mdfile" \ + | tee -- "${cache}.$$" + ) + grep -q '^%nocache' "$mdfile" \ + && rm -- "${cache}.$$" \ + || mv -- "${cache}.$$" "${cache}" + fi +} + +case "${PATH_INFO}" in + /"[.]"/*) + # usually some file related to theme + # let file server handle errors + FILE "${_EXEC}/${PATH_INFO#/\[.\]}" + return 0 + ;; + *${BR}*) + export ERROR_MSG='Page names containing newline character are not allowed' + theme_error 400 + return 0 + ;; + */\#*) + export ERROR_MSG='Page names starting with "#" are not allowed' + theme_error 400 + return 0 + ;; + */\[*\]/*|*/\[*\]) + # looks like some kind of handler + return 1 + ;; + */) + if ! mdfile "$PATH_INFO" >&-; then + theme_error 404 + elif ! acl_read "$PATH_INFO"; then + theme_error 403 + else + theme_page "${PATH_INFO}" + fi + return 0 + ;; +esac + +return 1 diff --git a/handlers/40_account.sh b/handlers/40_account.sh new file mode 100755 index 0000000..c4982fd --- /dev/null +++ b/handlers/40_account.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +case "${PATH_INFO}" in + */"[login]") + acl_read "/wiki/login/" \ + && theme_page "/[wiki]/login/" \ + || theme_error 403 + return 0 + ;; + */"[register]") + acl_read "/wiki/register/" \ + && theme_page "/[wiki]/register/" \ + || theme_error 403 + return 0 + ;; + */"[invite]") + acl_read "/wiki/invite/" \ + && theme_page "/[wiki]/invite/" \ + || theme_error 403 + return 0 + ;; + */"[settings]") + acl_read "/wiki/settings/" \ + && theme_page "/[wiki]/settings/" \ + || theme_error 403 + return 0 + ;; +esac + +return 1 diff --git a/handlers/40_attachment.sh b/handlers/40_attachment.sh new file mode 100755 index 0000000..1e07190 --- /dev/null +++ b/handlers/40_attachment.sh @@ -0,0 +1,135 @@ +#!/bin/sh + +. "$_EXEC/cgilite/file.sh" + +# REV_ATTACHMENTS="${REV_ATTACHMENTS:-false}" + +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 + ;; + *) printf "$attpath";; + esac +} + +case ${PATH_INFO} in + */\[attachment\]/) + # no trailing slash + REDIRECT "${_BASE}${PATH_INFO%/}" + ;; + */*/) + # attached files never end on / + return 1 + ;; + */\[attachment\]) + # show attachment page + page="${PATH_INFO%\[attachment\]}" + + if [ ! -d "$_DATA/pages${page}" -a ! -d "$_DATA/pages${page}" ]; then + # base page does not exist + return 1 + elif [ "${CONTENT_TYPE%%;*}" = "multipart/form-data" ]; then + # pass uploads to next handler + return 1 + elif [ "$(POST action)" ]; then + # pass edits to next handler + return 1 + elif ! acl_read "${page}"; then + theme_error 403 + return 0 + else + theme_attachments "${page}" + return 0 + fi + ;; + + */\[attachment\]/*) + attpath="${PATH_INFO%/\[attachment\]/*}/#attachments/${PATH_INFO##*/}" + + if [ ! -f "$_DATA/pages/$attpath" -a ! -f "$_EXEC/pages/$attpath" ]; then + return 1 + elif ! 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 + fi + ;; + */*) + attpath="${PATH_INFO%/*}/#attachments/${PATH_INFO##*/}" + + if [ ! -f "$_DATA/pages/$attpath" -a ! -f "$_EXEC/pages/$attpath" ]; then + return 1 + elif ! 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 + fi + ;; +esac + +return 1 diff --git a/handlers/40_edit_attachment.sh b/handlers/40_edit_attachment.sh new file mode 100755 index 0000000..2696a4d --- /dev/null +++ b/handlers/40_edit_attachment.sh @@ -0,0 +1,220 @@ +#!/bin/sh + +REV_ATTACHMENTS="${REV_ATTACHMENTS:-false}" + +if [ "${PATH_INFO##*/\[attachment\]}" ]; then + # Skip any action not happening on attachment page + return 1 +fi + +page="${PATH_INFO%\[attachment\]}" +action="$(POST action)" + +tsid="$(POST session_key)"; tsid="${tsid%% *}" + + +if ! acl_write "${PATH_INFO%\[attachment\]}"; then + # Deny access to write protected pages + printf 'Refresh: %i\r\n' 4 + theme_error 403 + [ "${CONTENT_TYPE%%;*}" = "multipart/form-data" ] \ + && head -c $((CONTENT_LENGTH)) >/dev/null + return 0 + +elif [ "${CONTENT_TYPE%%;*}" = "multipart/form-data" ]; then + . "$_EXEC/multipart.sh" + multipart_cache + + # Use positional parameters for filename collection + # The positional array is the only array available + # in plain posix shells, see the documentation for + # your shells "set" builtin for a hint to this + # obscure use mode + set -- + + # 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${page}#attachments/" + n=1; while filename=$(multipart_filename "file" "$n"); do + filename="$(printf %s "$filename" |tr /\\0 __)" + set -- "$@" "pages${page}#attachments/$filename" + multipart "file" "$n" >"$_DATA/pages${page}#attachments/$filename" + n=$((n + 1)) + done + rm -- "$multipart_cachefile" + if [ "$REV_ATTACHMENTS" = true ]; then + git -C "$_DATA" add -- "$@" + git -C "$_DATA" commit -qm "Attachments to # $page # uploaded by @ $USER_NAME @" -- "$@" + fi + REDIRECT "${_BASE}${PATH_INFO}" + +elif [ "$SESSION_ID" != "$tsid" ]; then + # Match session key from POST-Data to prevent CSRF: + # For authenticated users the POST session_key must match + # the session key used for authentication (usually from a + # cookie). This should ensure that POST requests were not + # triggered by malicious 3rd party sites freeriding on an + # existing user authentication. + # For pages that are writable by anonymous users, this is + # not reliable. + + printf 'Refresh: %i\r\n' 4 + theme_error 403 + return 0 +fi + +if [ "$action" = delete -o "$action" = move ]; then + set -- + n="$(POST_COUNT select)"; while [ $n -gt 0 ]; do + select="$(POST select $n |PATH)" + set -- "$@" "pages${page}#attachments/${select##*/}" + n=$((n - 1)) + done +fi + +if [ "$action" = delete ]; then + if [ "$REV_ATTACHMENTS" = true ]; then + git -C "$_DATA" rm -- "$@" + git -C "$_DATA" commit -qm \ + "Attachment to # $page # deleted by @ $USER_NAME @" -- "$@" + else + ( cd "$_DATA" && rm -- "$@"; ) + fi + REDIRECT "${_BASE}${PATH_INFO}" + +elif [ "$action" = move ]; then + moveto="$(POST moveto |PATH)" + + if ! acl_write "$moveto"; then + printf 'Refresh: %i\r\n' 4 + theme_error 403 + return 0 + + elif [ ! -d "${_DATA}/pages${moveto}" ]; then + printf 'Refresh: %i\r\n' 4 + theme_error 404 + return 0 + + elif [ "$REV_ATTACHMENTS" = true ]; then + mkdir -p -- "${_DATA}/pages${moveto}/#attachments" + git -C "$_DATA" mv -f -- "$@" "pages${moveto}/#attachments/" + + cnt=$#; while [ $cnt -gt 0 ]; do + set -- "$@" "$1" "pages/${moveto}/#attachments/${1##*/}" + cnt=$((cnt - 1)); shift 1 + done + + git -C "$_DATA" commit -qm \ + "Attachment moved from # $page # to # $moveto # by @ $USER_NAME @" -- "$@" + else + mkdir -p -- "${_DATA}/pages${moveto}/#attachments" + ( cd "$_DATA" && mv -- "$@" "pages${moveto}/#attachments/"; ) + fi + REDIRECT "${_BASE}${PATH_INFO}" + +elif [ "$action" = rename ]; then + fail='' success='' + set -- + + for file in "${_DATA}/pages${page}#attachments"/*; do + rename="$(POST rename_"$(slopecode "${file##*/}" |sed 's;=;%3D;g')")" + + if [ "$REV_ATTACHMENTS" = true -a \ + -f "${file}" -a \ + "$rename" -a \ + "${rename%/*}" = "${rename}" -a \ + ! -e "${_DATA}/pages${page}#attachments/${rename}" ] \ + && git -C "$_DATA" mv -- "pages${page}#attachments/${file##*/}" "pages${page}#attachments/${rename}"; then + success="${success}$(HTML "${file##*/}/${rename}")${BR}" + set -- "$@" "pages${page}#attachments/${file##*/}" "pages${page}#attachments/${rename}" + + elif [ "$REV_ATTACHMENTS" = true -a "${rename}" ]; then + fail="${fail}$(HTML "${file##*/}/${rename}")${BR}" + + elif [ -f "${file}" -a \ + "$rename" -a \ + "${rename%/*}" = "${rename}" -a \ + ! -e "${_DATA}/pages${page}#attachments/${rename}" ] \ + && mv -- "${file}" "${_DATA}/pages${page}#attachments/${rename}"; then + success="${success}$(HTML "${file##*/}/${rename}")${BR}" + + elif [ "${rename}" ]; then + fail="${fail}$(HTML "${file##*/}/${rename}")${BR}" + + fi + done + + if [ "$REV_ATTACHMENTS" = true -a $# -gt 2 ]; then + git -C "$_DATA" commit -qm \ + "Attachment files renamed by @ $USER_NAME @" -- "$@" + elif [ "$REV_ATTACHMENTS" = true -a $# -eq 2 ]; then + git -C "$_DATA" commit -qm \ + "Attachment file renamed by @ $USER_NAME @" -- "$@" + fi + + if [ "$success" -a "$fail" ]; then + printf "%s\r\n" "Status: 500 Internal Server Error" + theme_page - "$(_ "Attachment rename")" <<-EOF +

$(_ Some files could not be renamed)

+

$(_ Successfully renamed:)

+
    + $(printf %s "$success" |while read html; do + printf '
  • %s -> %s
  • ' \ + "${html%%/*}" "${html##*/}" + done) +
+

$(_ Errors:)

+
    + $(printf %s "$fail" |while read html; do + printf '
  • %s -> %s
  • ' \ + "${html%%/*}" "${html##*/}" + done) +
+ $(_ OK) + EOF + exit 0 + + elif [ "$fail" ]; then + printf "%s\r\n" "Status: 500 Internal Server Error" + theme_page - "$(_ "Attachment rename")" <<-EOF +

$(_ "Files could not be renamed")

+
    + $(printf %s "$fail" |while read html; do + printf '
  • %s -> %s
  • ' \ + "${html%%/*}" "${html##*/}" + done) +
+ $(_ OK) + EOF + exit 0 + + elif [ "$success" ]; then + printf 'Refresh: %i\r\n' 4 + theme_page - "Attachment rename" <<-EOF +

$(_ Files were renamed)

+
    + $(printf %s "$success" |while read html; do + printf '
  • %s -> %s
  • ' \ + "${html%%/*}" "${html##*/}" + done) +
+ $(_ OK) + EOF + exit 0 + + else + REDIRECT "${_BASE}${PATH_INFO}" + + fi +fi + +return 1 diff --git a/handlers/40_revision.sh b/handlers/40_revision.sh new file mode 100755 index 0000000..e3b8d96 --- /dev/null +++ b/handlers/40_revision.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +case "${PATH_INFO}" in + */\[revision\]/) + REDIRECT "${_BASE}${PATH_INFO%/}" + ;; + */\[revision\]) + if ! acl_read "${PATH_INFO%\[revision\]}"; then + theme_error 403 + else + "$_EXEC/macros/revisions" --list --diff "$page" \ + | theme_revisions - + fi + return 0 + ;; + */\[revision\]/\[*\]|*/\[revision\]/*/*) + REDIRECT "${_BASE}${PATH_INFO%%\[revision\]/*}${PATH_INFO##*/\[revision\]/}" + ;; + */\[revision\]/*) + page="${PATH_INFO%\[revision\]/*}" + rev="${PATH_INFO##*/}" + if ! acl_read "${page}"; then + theme_error 403 + else + ( export PATH_INFO="${page}" + cd "${_DATA}/pages${page}" || cd "${_DATA}/pages/" + git -C "${_DATA}" show "${rev}:pages${PATH_INFO}#page.md" \ + | { printf '
'; md; printf '
'; } \ + | theme_page - "${page##*/}" + ) + fi + return 0 + ;; +esac + +return 1 diff --git a/handlers/60_edit.sh b/handlers/60_edit.sh new file mode 100755 index 0000000..1ed639f --- /dev/null +++ b/handlers/60_edit.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +. "${_EXEC}/session_lock.sh" + +case $PATH_INFO in + */\[edit\]) : ;; + *) return 1 ;; +esac + +edit_page="${PATH_INFO%\[edit\]}" +edit_file="$_DATA/pages/$edit_page/#page.md" +[ "$REQUEST_METHOD" = POST ] && edit_action="$(POST action)" + +if ! acl_write "$edit_page"; then + theme_error 403 + return 0 + +elif [ "$edit_action" = cancel ]; then + S_RELEASE "$edit_file" + REDIRECT "${_BASE}${PATH_INFO%\[edit\]}" + +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" + else + export ERRMSG="ERR_NOLOCK" + REDIRECT "${_BASE}${PATH_INFO%\[edit\]}/[edit]" + fi + + if [ "$REV_PAGES" = true ]; then + git -C "$_DATA" add \ + -- "pages/$edit_page/#page.md" + git -C "$_DATA" commit -qm \ + "Page # ${edit_page} # updated by user @ ${USER_NAME} @" \ + -- "pages/$edit_page/#page.md" + fi 1>&2 + + REDIRECT "${_BASE}${edit_page}" + +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 diff --git a/handlers/60_move_rename_delete.sh b/handlers/60_move_rename_delete.sh new file mode 100755 index 0000000..33f79d0 --- /dev/null +++ b/handlers/60_move_rename_delete.sh @@ -0,0 +1,198 @@ +#!/bin/sh + +l10n_immutablepage >/dev/null 2>&1 \ +|| l10n_immutablepage(){ #TRANSLATION + cat <<-EOF +

Immutable Page

+ This is a core page of the wiki system. Its name and position cannot be changed. + You may however update this page and you can use ACLs to hide it from various listings. + EOF +} + +case "${PATH_INFO}" in + */\[move\]|*/\[rename\]|*/\[delete\]) + page="${PATH_INFO%\[*\]}" + if [ ! -d "$_DATA/pages/${page}" -a ! -d "$_EXEC/pages/${page}" ]; then + theme_error 404 + return 0 + elif ! acl_write "$page"; then + printf 'Refresh: %i, url=%s\r\n' 4 ./ + theme_error 403 + return 0 + elif [ -d "$_EXEC/pages/${page}/" ]; then + theme_page - <<-EOF +
+

+ $(l10n_immutablepage) +

+
+ EOF + return 0 + fi + ;; + *) return 1;; +esac + +l10n_movepage >/dev/null 2>&1 \ +|| l10n_movepage(){ # TRANSLATION + cat <<-EOF +

Move Page

+

$(HTML "${page}")

+ +
    +
  • A page with the same name must not already exist at the new location.
  • +
  • You must have permission to create new pages at this location.
  • +
  • All subpages will become available under the new path name.
  • +
  • Subpages will become unavailable under their current name.
  • +
+ + + EOF +} +l10n_renamepage >/dev/null 2>&1 \ +|| l10n_renamepage(){ # TRANSLATION + cat <<-EOF +

Rename Page

+

$(HTML "${page}")

+ +
    +
  • A page with the new name must not already exist.
  • +
  • You must have permission to create new pages at this location.
  • +
  • All subpages will become available under the new path name.
  • +
  • Subpages will become unavailable under their current name.
  • +
+ + + EOF +} +l10n_deletepage >/dev/null 2>&1 \ +|| l10n_deletepage(){ # TRANSLATION + cat <<-EOF +

Delete Page

+

$(HTML "${page}")

+

This page and its attachments will be deleted

+
    +
  • Past revisions of the page text (including the current one) will remain accessible and can be restored.
  • +
  • Attachments will be deleted completely, and cannot be restored.
  • +
  • Subpages will not be affected and can still be accessed normally.
  • +
+ + + EOF +} + +if [ "$REQUEST_METHOD" = POST ]; then + action="$(POST action)" + newname="$(POST newname |grep -m1 -xE '[^#/]*')" + newlocation="$(POST newlocation |grep -m1 -xE '/[^#]*')" +else case "${PATH_INFO}" in + */\[move\]) + location="${page%/}" location="${location%/*}/" + theme_page - <<-EOF +
+ + $(l10n_movepage) +
+ EOF + return 0 + ;; + */\[rename\]) + name="${page%/}" name="${name##*/}" + theme_page - <<-EOF +
+ + $(l10n_renamepage) +
+ EOF + return 0 + ;; + */\[delete\]) + theme_page - <<-EOF +
+ + $(l10n_deletepage) +
+ EOF + return 0 + ;; + esac +fi + +if [ "$action" = rename -a "$newname" ]; then + oldname="${PATH_INFO%\[*\]}" + newname="${oldname%/*/}$(PATH "${newname}/")" + + if [ -d "$_DATA/pages/$newname" ]; then + printf 'Refresh: %i\r\n' 4 + export ERRORMSG="A location of that name already exists." + theme_error 400 + return 0 + elif ! acl_write "$oldname" || ! acl_write "$newname"; then + printf 'Refresh: %i\r\n' 4 + theme_error 403 + return 0 + elif [ "$REV_PAGES" = true ]; then + git -C "$_DATA" mv "pages/$oldname" "pages/$newname" + git -C "$_DATA" commit -m 'Page # '"$oldname"' # renamed to # '"$newname"' # by user @ '"$USER_NAME"' @' \ + -- "pages/$oldname" "pages/$newname" + REDIRECT "$_BASE${newname}" + else + mv -- "$_DATA/pages/$oldname" "$_DATA/pages/$newname" + REDIRECT "$_BASE${newname}" + fi +elif [ "$action" = move -a "$newlocation" ]; then + oldname="${PATH_INFO%\[*\]}" + newlocation="$(PATH "$newlocation")" + newname="${oldname%/}" + newname="${newlocation%/}/${newname##*/}/" + + if [ -d "$_DATA/pages/$newname" ]; then + printf 'Refresh: %i\r\n' 4 + export ERRORMSG="A page of that name already exists at the given location." + theme_error 400 + return 0 + elif [ ! -d "$_DATA/pages/$newlocation" ]; then + printf 'Refresh: %i\r\n' 4 + export ERRORMSG="The given location does not exist." + theme_error 400 + return 0 + elif ! acl_write "$oldname" || ! acl_write "$newname"; then + printf 'Refresh: %i\r\n' 4 + theme_error 403 + return 0 + elif [ "$REV_PAGES" = true ]; then + git -C "$_DATA" mv "pages/${oldname}" "pages/${newname}" + git -C "$_DATA" commit -m 'Page # '"$oldname"' # moved to # '"$newname"' # by user @ '"$USER_NAME"' @' \ + -- "pages/${oldname}" "pages/${newname}" + REDIRECT "$_BASE${newname}" + else + mv -- "$_DATA/pages/$oldname" "$_DATA/pages/$newname" + REDIRECT "$_BASE${newname}" + fi +elif [ "$action" = delete ]; then + oldname="${PATH_INFO%\[*\]}" + if ! acl_write "$oldname"; then + printf 'Refresh: %i\r\n' 4 + theme_error 403 + return 0 + elif [ "$REV_PAGES" = true ]; then + git -C "$_DATA" rm "pages/${oldname}/#page.md" + git -C "$_DATA" commit -m 'Page # '"$oldname"' # deleted by user @ '"$USER_NAME"' @' \ + -- "pages/${oldname}/#page.md" + rm -r -- "$_DATA/pages/${oldname}"/\#* + rmdir -- "$_DATA/pages/${oldname}/" || true + REDIRECT ./ + else + rm -- "$_DATA/pages/${oldname}/#page.md" + rm -r -- "$_DATA/pages/${oldname}"/\#* + rmdir -- "$_DATA/pages/${oldname}/" || true + REDIRECT ./ + fi +elif [ "$action" = cancel ]; then + REDIRECT ./ +elif [ "$action" ]; then + printf 'Refresh: %i\r\n' 4 + export ERRORMSG="Missing parameters." + theme_error 400 + return 0 +fi diff --git a/handlers/60_newpage.sh b/handlers/60_newpage.sh new file mode 100755 index 0000000..42eed3f --- /dev/null +++ b/handlers/60_newpage.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +. "$_EXEC/session_lock.sh" + +case $PATH_INFO in + */\[newpage\]):;; + *) return 1;; +esac + +if [ "$(POST action)" != newpage ]; then + printf 'Refresh: %i; url=%s\r\n' 4 ./ + export ERROR_MSG="Formdata invalid" + theme_error 400 + return 0 +fi + +pattern="$(POST pattern)" +template="$(POST template)" +page="$(POST page)" + +if [ "$page" ]; then + # either a page name has been entered + pattern="$(date +"$pattern")" + page="$(printf -- "$pattern" "$page")" + +elif [ "${pattern%%"%%s"*}" = "${pattern}" ]; then + # or a page name is not part of the pattern + pattern="$(date +"$pattern")" + page="$pattern" + +else + printf 'Refresh: %i; url=%s\r\n' 4 ./ + export ERROR_MSG="Page name required" + theme_error 400 + return 0 +fi + +page="$(page_abs "$page")" +[ "$template" ] \ +&& template="$(page_abs "$template")" \ +|| template="$page" + +if [ -f "$_DATA/pages/$page/#page.md" -o \ + -f "$_EXEC/pages/$page/#page.md" ]; then + printf 'Refresh: %i; url=%s\r\n' 4 ./ + export ERROR_MSG="Page exists already" + theme_error 409 + return 0 + +elif ! acl_write "$page"; then + printf 'Refresh: %i; url=%s\r\n' 4 ./ + export ERROR_MSG="You don't have permission to write to this page" + theme_error 403 + return 0 + +elif mkdir -p -- "$_DATA/pages/${page}" \ + && S_LOCK "$_DATA/pages/$page/#page.md"; then + theme_editor "$page" "$template" + 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 diff --git a/handlers/90_brackets.sh b/handlers/90_brackets.sh new file mode 100755 index 0000000..ce49b0e --- /dev/null +++ b/handlers/90_brackets.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# special case for odd pages that are not handled otherwise +# usually those pages look like handlers (i.e. containing brackets) +# but are not +# attachment and edit (and really all) handlers should take precedence + +case "${PATH_INFO}" in + */\[view\]) + # explicit view handler for linking + REDIRECT "${_BASE}${PATH_INFO%\[view\]}" + ;; + */) + if ! mdfile "${PATH_INFO}" >&-; then + theme_error 404 + elif ! acl_read "${PATH_INFO}"; then + theme_error 403 + else + theme_page "${PATH_INFO}" + fi + return 0 + ;; + *) + if [ -d "$_DATA/pages${PATH_INFO}/" -o -d "$_EXEC/pages${PATH_INFO}/" ]; then + REDIRECT "${_BASE}${PATH_INFO}/" + fi + ;; +esac + +return 1 diff --git a/index.cgi b/index.cgi new file mode 100755 index 0000000..a27a75d --- /dev/null +++ b/index.cgi @@ -0,0 +1,59 @@ +#!/bin/sh + +. "${_EXEC:-${0%/*}}/cgilite/cgilite.sh" +. "${_EXEC}/cgilite/session.sh" +. "${_EXEC}/cgilite/users.sh" +. "${_EXEC}/tools.sh" +. "${_EXEC}/acl.sh" + +export REV_PAGES=${REV_PAGES:-true} +export REV_ATTACHMENTS=${REV_ATTACHMENTS:-false} +export WIKI_THEME="${WIKI_THEME:-default}" + +which git >/dev/null || REV_PAGES=false +[ "$REV_PAGES" != true ] && REV_ATTACHMENTS=false + +. "${_EXEC}/themes/${WIKI_THEME}.sh" + +# Renew session cookie, only if cookie already set +[ "$(COOKIE session)" ] && SESSION_COOKIE + +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" +} + +if [ "$REV_PAGES" = true -a ! -f "$_DATA/.gitignore" ]; then + cat >"$_DATA/.gitignore" <<-EOF + users.db + serverkey + **/#cache/ + **/#page.lock + **/#page.*.cache + **/#page.*.cache.* + **/#page:*.*.cache + **/#page:*.*.cache.* + EOF + [ "$REV_ATTACHMENTS" != true ] \ + && printf '**/#attachments/\n' >>"$_DATA/.gitignore" + git init "$_DATA" + git -C "$_DATA" add .gitignore + printf '%s\n' "" "[user]" \ + "email = \"shellwiki@localhost\"" \ + "name = \"Shellwiki\"" \ + >>"$_DATA/.git/config" + git -C "$_DATA" commit -m 'initialization' -- .gitignore +fi 1>&2 + +for handler in "$_EXEC"/handlers/*; do + . "$handler" && break +done +if [ $? != 0 ]; then + export ERROR_MSG="The presented URL schema cannot be handled" + theme_error 400 +fi diff --git a/l10n/de.sh b/l10n/de.sh new file mode 100644 index 0000000..9cd715b --- /dev/null +++ b/l10n/de.sh @@ -0,0 +1,248 @@ +#!/bin/sh + +_l10n_de(){ +case $* in + 'Page names containing newline character are not allowed') printf 'Seitennamen mit Zeilenumbruch sind nicht erlaubt';; + 'Page names starting with "#" are not allowed') printf 'Seitennamen dürfen nicht mit "#" anfangen';; + 'Formdata invalid') printf 'Formulardaten ungültig';; + 'Page name required') printf 'Seitenname erforderlich';; + 'Page exists already') printf 'Seite existiert schon';; + "You don't have permission to write to this page") printf 'Keine Berechtigung auf diese Seite zu schreiben';; + 'Unable to lock page for editing') printf 'Kann Seite nicht zum bearbeiten sperren';; + 'TRANSLATION NOT FOUND') printf 'Übersetzung nicht gefunden';; + 'The presented URL schema cannot be handled') printf 'Das angegeben URL Schema kann nicht verarbeitet werden';; + 'missing') printf 'fehlt';; + 'outdated') printf 'veraltet';; + 'current') printf 'aktuell';; + 'View') printf 'Anzeigen';; + 'Edit') printf 'Bearbeiten';; + 'Attachments') printf 'Anhänge';; + 'Revisions') printf 'Revisionen';; + 'Rename') printf 'Umbenennen';; + 'Move') printf 'Verschieben';; + 'Delete') printf 'Löschen';; + 'Update') printf 'Aktualisieren';; + 'Cancel') printf 'Abbrechen';; + 'Editor') printf 'Editor';; + 'Syntax') printf 'Syntax';; + 'page name') printf 'Seitenname';; + 'Upload') printf 'Hochladen';; + 'Move To:') printf 'Verschieben nach:';; + 'Latest changes to the original language page') printf 'Letzte Änderungen der originalsprachlichen Seite';; + 'GIT is not available to handle revisioning.') printf 'GIT steht nicht zur Verfügung um Revisionierung zu handhaben';; + '(never edited)') printf '(nie bearbeitet)';; + "Attachment rename") printf "Anhänge umbenennen";; + "Errors:") printf "Fehler:";; + "Files could not be renamed") printf "Dateien konnten nicht umbenannt werden";; + "Files were renamed") printf "Dateien wurden umbenannt";; + "OK") printf "OK";; + "Some files could not be renamed") printf "Einige Dateien konnten nicht umbenannt werden";; + "Successfully renamed:") printf "Erfolgreich umbenannt:";; + *) printf %s\\n "$*";; +esac +} + +_(){ _l10n_de "$@"; } + +user_register_email() { # TRANSLATION + "$SENDMAIL" -t -f "$MAILFROM" <<-EOF + From: ${MAILFROM} + To: ${email} + Subject: Ihre Benutzeranmeldung für ${HTTP_HOST%:*} + + Jemand hat versucht ein Benutzerkonto mit dieser Email-Adresse zu erstellen. + + Sie können Ihr Benutzerkonto aktivieren, indem Sie auf diesen Link klicken: + + ${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid") + + Der Registrierungslink wird nach $((USER_CONFIRMEXPIRE / 3600)) Stunden ungültig. + + Falls Sie kein Konto bei ${HTTP_HOST%:*} beantragt haben, hat wahrscheinlich + jemand anderes versehentlich Ihre Emain-Adresse dort eingegeben. In diesem Fall + ignorieren Sie bitte diese Email und wir löschen Ihre Email-Adresse in den + nächsten Tagen aus unserer Datenbank. + + Dies ist eine automatische Email. Eine direkte Antwort wird nicht empfangen. + -- + Automat zur Kontenregistrierung. + EOF +} +user_invite_email(){ # TRANSLATION + "$SENDMAIL" -t -f "$MAILFROM" <<-EOF + From: ${MAILFROM} + To: ${email} + Subject: Sie wurden zu ${HTTP_HOST%:*} eingeladen + + ${USER_NAME:-Jemand} hat eine Einladung an diese Email-Adresse ausgesprochen. + + ${message} + + Sie können Ihr Benutzerkonto aktivieren, indem Sie auf diesen Link klicken: + + ${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid") + + Der Registrierungslink wird nach $((USER_CONFIRMEXPIRE / 3600)) Stunden ungültig. + + Falls Sie nicht wissen worum es hier geht, hat wahrscheinlich jemand anderes + versehentlich Ihre Emain-Adresse dort eingegeben. In diesem Fall ignorieren + Sie bitte diese Email und wir löschen Ihre Email-Adresse in den nächsten + Tagen aus unserer Datenbank. + + Dies ist eine automatische Email. Eine direkte Antwort wird nicht empfangen. + -- + Automat zur Kontenregistrierung. + EOF +} +w_user_register_disabled(){ # TRANSLATION + cat <<-EOF + [div #user_register .disabled + Die Registrierung von Benutzerkonten ist deaktiviert. + ] + EOF +} +w_user_register_sendmail(){ # TRANSLATION + cat <<-EOF + [form #user_register .registeremail method=POST + [p Wir schicken eine Aktivierungsmail an Ihre Email-Adresse. + Sie können mit der Registierung fortfahren, sobald Sie den + Aktivierungslink in dieser Email anklicken.] + [input type=email name=email placeholder="Email Adresse"] + [submit "action" "user_register" Registrieren] + ] + EOF +} +w_user_register_direct(){ # TRANSLATION + cat <<-EOF + [form #user_register .registername method=POST + [input name=uname placeholder="Benutzername wählen" tooltip="Ihr Benutzername darf jedes Zeichen, außer dem @-Zeichen enthalten. Er muss mindestens drei Zeichen lang sein und mit einem Buchstaben anfangen." pattern="^\[\\\\p{L}\]\[\\\\p{L}0-9 -~\]{2,127}$" autocomplete=off] + [input type=password name=pw placeholder="Passwort wählen" pattern=".{6,}"] + [input type=password name=pwconfirm placeholder="Passwort bestätigen" pattern=".{6,}"] + [submit "action" "user_register" Registrieren] + ] + EOF +} +w_user_confirm_proceed(){ # TRANSLATION + cat <<-EOF + [form #user_confirm method=POST + [input type=hidden name=uid value="${uid}"] + [input type=hidden name=signature value="${signature}"] + $([ "$EMAIL" != '\' ] && printf \ + '[input disabled=disabled value="%s" placeholder="Email Adresse"]' "$(UNSTRING "$EMAIL" |HTML)" + ) + [input name=uname placeholder="Benutzername wählen" tooltip="Ihr Benutzername darf jedes Zeichen, außer dem @-Zeichen enthalten. Er muss mindestens drei Zeichen lang sein und mit einem Buchstaben anfangen." pattern="^\[\\\\p{L}\]\[\\\\p{L}0-9 -~\]{2,127}$" autocomplete=off] + [input type=password name=pw placeholder="Passwort wählen" pattern=".{6,}"] + [input type=password name=pwconfirm placeholder="Passwort bestätigen" pattern=".{6,}"] + [submit "action" "user_confirm" Registrierung Abschließen] + ] + EOF +} +w_user_confirm_expired(){ # TRANSLATION + cat <<-EOF + [div #user_confirm .expired + [p Diser Aktivierungslink ist nicht mehr gültig.] + ] + EOF +} +w_user_confirm_invalid(){ # TRANSLATION + cat <<-EOF + [div #user_confirm .invalid + [p Dieser Aktivierungslink ist ungültig. Stellen Sie sicher, dass Sie den gesamten Aktivierungslink aus Ihrer Email kopiert haben und achten Sie darauf, keine Zeilenumbrüche mit zu kopieren.] + ] + EOF +} +w_user_invite_email(){ # TRANSLATION + cat <<-EOF + [form #user_invite method=POST + [input placeholder="Email-Empfänger" name=email autocomplete=off] + [textarea name="message" placeholder="Nachricht an Empfänger" . ] + [submit "action" "user_invite" Einladung Senden] + ] + EOF +} +w_user_invite_link(){ # TRANSLATION + cat <<-EOF + [div #user_invite .link + [p Ein anonymes Benutzerkonto wurde angelegt. Schicken Sie den folgenden Link an den vorgesehene Person, so dass sie ihr Benutzerkonto annehmen kann. Der Link ist für $((USER_CONFIRMEXPIRE / 3600)) Stunden gültig.] + [a href="$(HTML "$invlink")" . $(HTML "$invlink")] + + [p [a href="#" . Ein weiteres Konto anlegen]] + ] + EOF +} +w_user_invite_deny(){ # TRANSLATION + cat <<-EOF + [div #user_invite .notallowed + Nur angemeldete Benutzer können einen Einladungslink an Andere versenden. + ] + EOF +} +w_user_login_logon(){ # TRANSLATION + cat <<-EOF + [form #user_login .login method=POST + [input name=uname placeholder="Benutzername oder Email-Adresse" autocomplete=off] + [input type=password name=pw placeholder="Passwort"] + [submit "action" "user_login" Anmelden] + ] + EOF +} +w_user_login_logoff(){ # TRANSLATION + cat <<-EOF + [form #user_login .logout method=POST + [p Logged in as [span . $(HTML ${USER_NAME})]] + [submit "action" "user_logout" Abmelden] + ] + EOF +} + +l10n_immutablepage(){ #TRANSLATION + cat <<-EOF +

Unveränderliche Seite

+ Dies ist eine Kernseite des Wikisystems. Name und Ort können nicht verändert werden. + Sie können jedoch den Inhalt der Seite ändern und Sie können ACLs nutzen um die Seite zu verstecken. + EOF +} +l10n_movepage(){ # TRANSLATION + cat <<-EOF +

Seite verschieben

+

$(HTML "${page}")

+ +
    +
  • Eine Seite mit dem selben Namen darf nicht schon vorhanden sein.
  • +
  • Sie müssen die Berechtigung haben, Seiten an diesem Ort anzulegen.
  • +
  • Alle Unterseiten werden unter dem neuen Pfad verfügbar gemacht.
  • +
  • Unterseiten werden unter ihrem aktuellen Pfad nicht mehr verfügbar sein.
  • +
+ + + EOF +} +l10n_renamepage(){ # TRANSLATION + cat <<-EOF +

Seite Umbenennen

+

$(HTML "${page}")

+ +
    +
  • Eine Seite mit dem neuen Namen darf nicht schon vorhanden sein.
  • +
  • Sie müssen die Berechtigung haben, Seiten an diesem Ort anzulegen.
  • +
  • Alle Unterseiten werden unter dem neuen Pfad verfügbar gemacht.
  • +
  • Unterseiten werden unter ihrem aktuellen Pfad nicht mehr verfügbar sein.
  • +
+ + + EOF +} +l10n_deletepage(){ # TRANSLATION + cat <<-EOF +

Seite Löschen

+

$(HTML "${page}")

+

Diese Seite und all ihre Anhänge werden gelöscht.

+
    +
  • Vergangene Revisionen der Seitentextes (einschließlich der aktuellen) bleiben verfügbar und können wiederhergestellt werden.
  • +
  • Anhänge werden vollständig gelöscht und können nicht wiederhergestellt werden.
  • +
  • Unterseiten sind nicht betroffen und bleiben normal verfügbar.
  • +
+ + + EOF +} diff --git a/macros/attachments b/macros/attachments new file mode 100755 index 0000000..bcd6722 --- /dev/null +++ b/macros/attachments @@ -0,0 +1,29 @@ +#!/bin/sh + +. "$_EXEC/cgilite/cgilite.sh" +. "$_EXEC/acl.sh" +. "$_EXEC/tools.sh" + +page="$1" + +if [ "${page#/}" = "$page" ]; then + page="$(PATH "${PATH_INFO}/$page")" +fi + +acl_read "$page" || exit 0 + +printf %s\\n '
    ' + +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 '
  • %s + %s%s
  • ' \ + "$(HTML "${file##*/}")" "$(HTML "${file##*/}")" \ + "$(size_human "$size")" "$(date -d @"$date" +"%F %T")" +done + +printf %s\\n '
' diff --git a/macros/changes b/macros/changes new file mode 100755 index 0000000..8714976 --- /dev/null +++ b/macros/changes @@ -0,0 +1,62 @@ +#!/bin/sh + +. "$_EXEC/cgilite/cgilite.sh" +. "$_EXEC/tools.sh" +. "$_EXEC/acl.sh" + +_(){ printf %s\\n "$*"; } +[ "${LANGUAGE}" -a -r "${_EXEC}/l10n/${LANGUAGE}.sh" ] && . "${_EXEC}/l10n/${LANGUAGE}.sh" + +LANGUAGES='' glob="/" depth=-1 +while [ $# -gt 0 ]; do case $1 in + --system) glob_system_pages=true; shift 1;; + :*) LANGUAGES="${LANGUAGES}${LANGUAGES:+ }${1#:}"; shift 1;; + --depth) depth="$2"; shift 2;; + *) glob="$1"; shift 1;; +esac; done + +page='' page_abs='' ostamp='' odate='' lstamp='' ldate='' row='' rowstate='' + +printf '\n' +for l in $LANGUAGES; do printf '' "$l"; done +printf '\n\n' + +page_glob "$glob" "$depth" |while read page; do + page_abs="$(page_abs "$page")" + acl_read "${page_abs}" || continue + + read ostamp odate <<-EOF + $([ "$REV_PAGES" = true ] \ + && git -C "$_DATA" log --pretty="format:%at %ai" -- "pages${page_abs}#page.md" \ + || stat -c "%Y %y" -- "$_DATA/pages${page_abs}#page.md" 2>&- \ + || printf "0 %s\n" "$(_ "(never edited)")" + ) + EOF + row="" + rowstate='' + + for l in $LANGUAGES; do + if [ -f "${_DATA}/pages/${page}:${l}/#page.md" ]; then + read lstamp ldate <<-EOF + $([ "$REV_PAGES" = true ] \ + && git -C "$_DATA" log --pretty="format:%at %ai" -- "pages$(page_abs "${page_abs}:${l}")/#page.md" \ + || stat -c "%Y %y" -- "$_DATA/pages$(page_abs "${page_abs}:${l}")/#page.md" + ) + EOF + if [ $lstamp -lt $ostamp ] 2>&-; then + row="${row}" + [ "$rowstate" = "${rowstate%*outdated*}" ] && rowstate="${rowstate}${rowstate:+ }outdated" + else + row="${row}" + [ "$rowstate" = "${rowstate%*current*}" ] && rowstate="${rowstate}${rowstate:+ }current" + fi + else + row="${row}" + [ "$rowstate" = "${rowstate%*missing*}" ] && rowstate="${rowstate}${rowstate:+ }missing" + fi + done + + printf '%s\n' "$rowstate" "$row" +done + +printf '
Page%s
$(HTML "$page")${odate%%[+.]*}$(_ outdated)${ldate%%[+.]*}$(_ current)${ldate%%[+.]*}$(_ missing)
' diff --git a/macros/errormessage b/macros/errormessage new file mode 100755 index 0000000..b42e426 --- /dev/null +++ b/macros/errormessage @@ -0,0 +1,10 @@ +#!/bin/sh + +. "$_EXEC/cgilite/cgilite.sh" + +_(){ printf %s\\n "$*"; } +[ "${LANGUAGE}" -a -r "${_EXEC}/l10n/${LANGUAGE}.sh" ] && . "${_EXEC}/l10n/${LANGUAGE}.sh" + +if [ "$1" -o "$ERROR_MSG" ]; then + printf '

%s

' "$(HTML "$(_ ${1:-${ERROR_MSG}})")" +fi diff --git a/macros/gallery b/macros/gallery new file mode 100755 index 0000000..3a05ee8 --- /dev/null +++ b/macros/gallery @@ -0,0 +1,31 @@ +#!/bin/sh + +. "$_EXEC/cgilite/cgilite.sh" +. "$_EXEC/acl.sh" +. "$_EXEC/tools.sh" + +[ $# = 0 ] && set -- "*" + +printf '' diff --git a/macros/include b/macros/include new file mode 100755 index 0000000..5254a26 --- /dev/null +++ b/macros/include @@ -0,0 +1,63 @@ +#!/bin/sh + +. "$_EXEC/cgilite/cgilite.sh" +. "$_EXEC/acl.sh" +. "$_EXEC/tools.sh" + +from='1'; to='$'; rev=''; items='$'; link='true' + +while [ $# -gt 0 ]; do case $1 in + --from) from="$2"; shift 2;; + from=*) from="${1#*=}"; shift 1;; + --to) to="$2"; shift 2;; + to=*) to="${1#*=}"; shift 1;; + --items) items="$2"; shift 2;; + items=*) items="${1#*=}"; shift 1;; + --rev|--reverse) rev="-r"; shift 1;; + --nolink) link=""; shift 1;; + *) page="$1"; shift 1;; +esac; done + +if ! printf %s\\n "$from" |grep -qEx '[0-9]+|/([^/\\]|\\/|\\.)*/'; then + debug 'Include macro invalid argument: "from"' + exit 1 +fi +if ! printf %s\\n "$to" |grep -qEx '\$|[0-9]+|/([^/\\]|\\/|\\.)*/'; then + debug 'Include macro Invalid argument: "to"' + exit 1 +fi +if ! printf %s\\n "$items" |grep -qEx '\$|[0-9]+'; then + debug 'Include macro Invalid argument: "items"' + exit 1 +fi + +page_glob "$page" \ +| sort $rev \ +| sed "${items}q" \ +| while read glob; do + page="$(page_abs "$glob")" + acl_read "$page" || continue + mdfile="$(mdfile "$page")" || continue + hglob="$(HTML "$glob")" + refpfx="$(printf %s\\n "$hglob" |sed 's;[\;&\;];\\&;g')" + [ "$link" ] \ + && printf '
+ %s +
' \ + "${hglob}" "${hglob}" "${hglob}" \ + || printf '
+
' \ + "${hglob}" + ( # PATH_INFO may be used by macros in the included page + export PATH_INFO="$page" + cd -- "${mdfile%/*}/" + sed -n "${from},${to}p" <"$mdfile" \ + | md \ + | grep -vx '' + ) | sed -E ' + s;(<[^>]+ )(href|src)="([^"]+://[^"]*|[mM][aA][iI][lL][tT][oO]:[^"]*)"([^>]*>);\1\2="/#safe/\3"\4;g + s;(<[^>]+ )(href|src)="([^#/"][^"]*)"([^>]*>);\1\2="'"${refpfx}"'\3"\4;g + s;(<[^>]+ )(href|src)="/#safe/([^"]*)"([^>]*>);\1\2="\3"\4;g + ' + printf '
' +done diff --git a/macros/newpage b/macros/newpage new file mode 100755 index 0000000..7a252aa --- /dev/null +++ b/macros/newpage @@ -0,0 +1,32 @@ +#!/bin/sh + +. "$_EXEC/cgilite/cgilite.sh" +. "$_EXEC/acl.sh" +. "$_EXEC/tools.sh" + +_(){ printf %s\\n "$*"; } +[ "${LANGUAGE}" -a -r "${_EXEC}/l10n/${LANGUAGE}.sh" ] && . "${_EXEC}/l10n/${LANGUAGE}.sh" + +pattern=./%%s +template='' +label='New Page' + +while [ $# -gt 0 ]; do case $1 in + template=*) template="${1#*=}"; shift 1;; + --template) template="$2"; shift 2;; + label=*) label="${1#*=}"; shift 1;; + --label) label="$2"; shift 2;; + *) pattern="$1"; shift 1;; +esac; done + +if acl_write "$(page_abs "$pattern")"; then + cat <<-EOF +
+ + + $([ ! "${pattern##*%%s*}" ] \ + && printf '' "$(_ "page name")" + ) +
+ EOF +fi diff --git a/macros/pagelist b/macros/pagelist new file mode 100755 index 0000000..ed4ddbe --- /dev/null +++ b/macros/pagelist @@ -0,0 +1,33 @@ +#!/bin/sh + +. "$_EXEC/cgilite/cgilite.sh" +. "$_EXEC/acl.sh" +. "$_EXEC/tools.sh" + +while [ $# -gt 0 ]; do case $1 in + --system) glob_system_pages=true; shift 1;; + --depth) depth="$2" shift 2;; + *) if [ ! "$dir" ]; then + dir="$1" + elif [ ! "$depth" ]; then + depth="$1" + fi + shift 1;; +esac; done + +[ "$dir" ] || dir=* +[ "$depth" -ge 0 -o "$depth" -le 0 ] 2>&- || depth=0 + +printf '
    \n' + page_glob "$dir" "$depth" \ + | while read -r glob; do + printf %s\\n "$glob" + done \ + | while read -r page; do + pagedir="$(page_abs "$page")" + [ -f "$_DATA/pages/${pagedir}/#page.md" -o \ + -f "$_EXEC/pages/${pagedir}/#page.md" ] \ + && acl_read "$pagedir" \ + && printf '
  • %s
  • ' "$(HTML "$page")" "$(HTML "$page")" + done +printf '
\n' diff --git a/macros/reflink b/macros/reflink new file mode 100755 index 0000000..1b38c15 --- /dev/null +++ b/macros/reflink @@ -0,0 +1,8 @@ +#!/bin/sh + +. "$_EXEC/cgilite/cgilite.sh" + +title="$(HTML "$1")" +ref="$(HEADER Referer)" + +printf '%s' "${ref:-./}" "${title:-Return}" diff --git a/macros/revisions b/macros/revisions new file mode 100755 index 0000000..5d7b589 --- /dev/null +++ b/macros/revisions @@ -0,0 +1,81 @@ +#!/bin/sh + +. "$_EXEC/cgilite/cgilite.sh" +. "$_EXEC/tools.sh" +. "$_EXEC/acl.sh" + +_(){ printf %s\\n "$*"; } +[ "${LANGUAGE}" -a -r "${_EXEC}/l10n/${LANGUAGE}.sh" ] && . "${_EXEC}/l10n/${LANGUAGE}.sh" + +LIST=true DIFF= +while [ $# -gt 0 ]; do case $1 in + --list) + LIST=true + shift 1 + ;; + --no-list) + LIST= + shift 1 + ;; + --diff) + DIFF=true + shift 1 + ;; + --no-diff) + DIFF= + shift 1 + ;; + *)page="$1" + shift 1 + ;; +esac; done + +page_abs="$(page_abs "$page")" +page_default="${page_abs%:*/}" + +if ! acl_read "$page_abs"; then + return 0 +fi + +printf '
\n' + +if [ "$REV_PAGES" != true ]; then + printf '
%s
' "$(_ GIT is not available to handle revisioning.)" + exit 1 +fi + +if [ "$LIST" = true ]; then + printf '
    \n' + IFS=" " + { git -C "$_DATA" log --date=format:"%a, %x %H:%M" \ + --pretty=format:"%h %cd %s" \ + -- "pages${page_abs}#page.md" + printf '\n' + } | while read -r hash date message; do + user="${message% @*}"; user="${user##*@ }" + printf '
  • %s%s%s
  • \n' \ + "$(HTML "${page%/}/[revision]/$hash")" "$(HTML "$hash")" "$(HTML "$date")" "$(HTML "$user")" + done + printf '
\n' +fi + +if [ "$DIFF" = true -a "$LANGUAGE_DEFAULT" -a "$page_default" != "$page_abs" ]; then + commit="$(git -C "$_DATA" log --pretty=format:%H -- "pages${page_abs}#page.md" |head -n1)" + printf '

%s

' "$(_ 'Latest changes to the original language page')" + git -C "$_DATA" diff -U3 "$commit" -- "pages${page_default}#page.md" |tail -n+5 \ + | while read -r diff; do case $diff in + @@\ *\ @@*) + line="${diff#@@ * @@}" + num="${diff%"${line}"}" + printf '%s\n' "$(HTML "$num")" + printf '%s\n' "$(HTML "$line")" + ;; + -*) printf '%s\n' "$(HTML "$diff")";; + +*) printf '%s\n' "$(HTML "$diff")";; + \ *) printf '%s\n' "$(HTML "$diff")";; + \\\ *) printf '%s\n' "$(HTML "$diff")";; + esac; done + printf '
' +fi + +printf '
\n' diff --git a/macros/toc b/macros/toc new file mode 100755 index 0000000..e68ac6f --- /dev/null +++ b/macros/toc @@ -0,0 +1,24 @@ +#!/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 + +min="$1" max="$2" +[ "$min" -ge 1 -a "$min" -le 6 ] || min=1 +[ "$max" -ge "$min" ] || max="$min" +[ "$max" -le 6 ] || max=6 + +md |sed -nE ' + 1i
    + s;^.*
    (([^<]|<[^aA]|<[aA][^ ])+)()?$;
  • \4
  • ;p + $i
+' diff --git a/macros/wikiform b/macros/wikiform new file mode 100755 index 0000000..4fe285e --- /dev/null +++ b/macros/wikiform @@ -0,0 +1,24 @@ +#!/bin/sh + +action="$1" + +. "$_EXEC/cgilite/cgilite.sh" +. "$_EXEC/cgilite/users.sh" + +_(){ printf %s\\n "$*"; } +[ "${LANGUAGE}" -a -r "${_EXEC}/l10n/${LANGUAGE}.sh" ] && . "${_EXEC}/l10n/${LANGUAGE}.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" + ;; + settings) + w_user_update |"$_EXEC/cgilite/html-sh.sed" + ;; +esac diff --git a/md_macros.awk b/md_macros.awk new file mode 100755 index 0000000..88e662f --- /dev/null +++ b/md_macros.awk @@ -0,0 +1,69 @@ +#!/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) { + oldRS=RS; oldORS=ORS; + RS=""; ORS=""; line=""; + "printf '%s' " sh_escape(file) " | " sh_escape(ENVIRON["MD_MACROS"]) "/" call | getline line; + RS=oldRS; ORS=oldORS; + 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[""]; + } +} diff --git a/multipart.sh b/multipart.sh new file mode 100755 index 0000000..7c0d5dd --- /dev/null +++ b/multipart.sh @@ -0,0 +1,91 @@ +#!/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 + # Do not use `sed -n` (or busybox sed will "convert" NULL to LF) + sed "/--${multipart_boundary}\(--\)\?${CR}/{x;q;}" \ + | head -c-3 + 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" +} diff --git a/pages/#attachments/favicon.ico b/pages/#attachments/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/pages/#page.md b/pages/#page.md new file mode 100644 index 0000000..4b72bae --- /dev/null +++ b/pages/#page.md @@ -0,0 +1,5 @@ +%acl Known:read,write + All:read,write + +It Works! +========= diff --git a/pages/:de/#page.md b/pages/:de/#page.md new file mode 100644 index 0000000..9e00514 --- /dev/null +++ b/pages/:de/#page.md @@ -0,0 +1,5 @@ +%acl Known:read,write + All:read,write + +Es Funktioniert! +================ diff --git a/pages/[wiki]/#page.md b/pages/[wiki]/#page.md new file mode 100644 index 0000000..dfed0bc --- /dev/null +++ b/pages/[wiki]/#page.md @@ -0,0 +1,12 @@ +%acl Known:read,write + All:read + +TECHNICAL pages +=============== +The pages in this section are used to display various wiki functions. You can update them to apply your own theme to the wiki. + +I.e. you can change Error pages, the wikis header and footer sections etc. + +*Write* access should be controlled by overriding this page. However most pages in this section should remain *readable* to All. + +<> diff --git a/pages/[wiki]/400/#page.md b/pages/[wiki]/400/#page.md new file mode 100644 index 0000000..94ba677 --- /dev/null +++ b/pages/[wiki]/400/#page.md @@ -0,0 +1,9 @@ +%nocache + +400 +=== + +**Bad Request** +<> + +<> diff --git a/pages/[wiki]/400/:de/#page.md b/pages/[wiki]/400/:de/#page.md new file mode 100644 index 0000000..23f2af5 --- /dev/null +++ b/pages/[wiki]/400/:de/#page.md @@ -0,0 +1,9 @@ +%nocache + +400 +=== + +**Fehlerhafte Anfrage** +<> + +<> diff --git a/pages/[wiki]/403/#page.md b/pages/[wiki]/403/#page.md new file mode 100644 index 0000000..d15309b --- /dev/null +++ b/pages/[wiki]/403/#page.md @@ -0,0 +1,8 @@ +%nocache + +403 +=== + +**Forbidden** + +<> diff --git a/pages/[wiki]/403/:de/#page.md b/pages/[wiki]/403/:de/#page.md new file mode 100644 index 0000000..1e1a9e4 --- /dev/null +++ b/pages/[wiki]/403/:de/#page.md @@ -0,0 +1,8 @@ +%nocache + +403 +=== + +**Zugriff verweigert** + +<> diff --git a/pages/[wiki]/404/#page.md b/pages/[wiki]/404/#page.md new file mode 100644 index 0000000..d51acf8 --- /dev/null +++ b/pages/[wiki]/404/#page.md @@ -0,0 +1,10 @@ +%nocache + +404 +=== + +**page not found** + +<> + +<> diff --git a/pages/[wiki]/404/:de/#page.md b/pages/[wiki]/404/:de/#page.md new file mode 100644 index 0000000..14ea52f --- /dev/null +++ b/pages/[wiki]/404/:de/#page.md @@ -0,0 +1,10 @@ +%nocache + +404 +=== + +**Seite nicht gefunden** + +<> + +<> diff --git a/pages/[wiki]/409/#page.md b/pages/[wiki]/409/#page.md new file mode 100644 index 0000000..0727e7c --- /dev/null +++ b/pages/[wiki]/409/#page.md @@ -0,0 +1,9 @@ +%nocache + +409 +=== + +**Conflict** +<> + +<> diff --git a/pages/[wiki]/409/:de/#page.md b/pages/[wiki]/409/:de/#page.md new file mode 100644 index 0000000..13c5091 --- /dev/null +++ b/pages/[wiki]/409/:de/#page.md @@ -0,0 +1,9 @@ +%nocache + +409 +=== + +**Konflikt** +<> + +<> diff --git a/pages/[wiki]/500/#page.md b/pages/[wiki]/500/#page.md new file mode 100644 index 0000000..0d56e0a --- /dev/null +++ b/pages/[wiki]/500/#page.md @@ -0,0 +1,9 @@ +%nocache + +500 +=== + +**Internal Server Error** +<> + +<> diff --git a/pages/[wiki]/500/:de/#page.md b/pages/[wiki]/500/:de/#page.md new file mode 100644 index 0000000..3dfcf7f --- /dev/null +++ b/pages/[wiki]/500/:de/#page.md @@ -0,0 +1,9 @@ +%nocache + +500 +=== + +**Interner Serverfehler** +<> + +<> diff --git a/pages/[wiki]/:de/#page.md b/pages/[wiki]/:de/#page.md new file mode 100644 index 0000000..7e5a6f8 --- /dev/null +++ b/pages/[wiki]/:de/#page.md @@ -0,0 +1,14 @@ +%acl Known:read,write + All:read + +TECHNISCHE Seiten +================= +Die Seiten in dieser Sektion werden genutzt um verschiedene Wiki-Funktionen anzuzeigen. Sie können sie anpassen +um dem Wiki Ihren eigenen Stil zu geben. + +Z.B. können Sie hier Fehlerseiten, die Kopfzeile und die Fußzeile ändern. + +Der *Schreib*zugriff sollte angepasst werden, indem Sie diese Seite editieren. Jedoch sollten die meisten Seiten in +dieser Sektion für alle *Lesbar* bleiben. + +<> diff --git a/pages/[wiki]/editorhelp/#page.md b/pages/[wiki]/editorhelp/#page.md new file mode 100644 index 0000000..930fda3 --- /dev/null +++ b/pages/[wiki]/editorhelp/#page.md @@ -0,0 +1,31 @@ +### Formatting: + +\*\***strong**\*\* \**emphasized*\* `~~`~~strikethrough~~`~~` \``verbatim`\` + +a backslash `\` prevents \*\*accidental formatting\*\*: \\\* \\\` + +### Links: + +Simple Weblink (use angle brackets): < > + +Simple Email Link: < > + +Weblink with Text: \[Wikipedia article\](https://en.wikipedia.org/wiki/Markdown) - [Wikipedia article](https://en.wikipedia.org/wiki/Markdown) + +Other pages on the same site: +[Start page](/): `[Start page](/)`, [Help](/[wiki]/editorhelp/): `[Help](/[wiki]/editorhelp/)` + + +### Lists: + ++----------------------------------+---------------------+ +| [space] [dash] [space] [text] | - bullet | +| - bullet | - list | +| - list | - indented point | +| - indented point | | ++------------------------------------------+---------------------+ +| [space] [number] [dot] [space] [text] | 1. ordered | +| 1. ordered | 2. list | +| 2. list | 1. indented point| +| 1. indented point | | ++------------------------------------------+---------------------+ diff --git a/pages/[wiki]/editorhelp/:de/#page.md b/pages/[wiki]/editorhelp/:de/#page.md new file mode 100644 index 0000000..52a7f4b --- /dev/null +++ b/pages/[wiki]/editorhelp/:de/#page.md @@ -0,0 +1,31 @@ +### Formatierung: + +\*\***kräftig**\*\* \**hervorgehoben*\* `~~`~~durchgestrichen~~`~~` \``wörtlich`\` + +Ein Backslash `\` verhindert \*\*versehentliche Textformatierung\*\*: \\\* \\\` + +### Links: + +Einfacher Weblink (spitze Klammern): < > + +Einfacher Email Link: < > + +Weblink mit Text: \[Wikipediaartikel\](https://de.wikipedia.org/wiki/Markdown) - [Wikipediaartikel](https://de.wikipedia.org/wiki/Markdown) + +Andere Seiten auf der selben Domain: +[Startseite](/): `[Startseite](/)`, [Hilfe](/[wiki]/editorhelp/): `[Hilfe](/[wiki]/editorhelp/)` + + +### Listen: + ++--------------------------------------------------------+--------------------------+ +| [Leerzeichen] [Bindestrich] [Leerzeichen] [Text] | - Stichpunkt | +| - Stichpunkt | - Liste | +| - Liste | - eingerückter Punkt | +| - eingerücker Punkt | | ++--------------------------------------------------------+--------------------------+ +| [Leerzeichen] [Ziffer] [Punkt] [Leerzeichen] [Text] | 1. nummerierte | +| 1. nummerierte | 2. Liste | +| 2. Liste | 1. eingerückter Punkt | +| 1. eingerückter Punkt | | ++--------------------------------------------------------+--------------------------+ diff --git a/pages/[wiki]/footer/#page.md b/pages/[wiki]/footer/#page.md new file mode 100644 index 0000000..205e8f8 --- /dev/null +++ b/pages/[wiki]/footer/#page.md @@ -0,0 +1,6 @@ +::: { .menu } + * <> + * [Register](./[register]) + * [Invite](./[invite]) +::: +Edit the Footer [here](/[wiki]/footer/[edit]) diff --git a/pages/[wiki]/footer/:de/#page.md b/pages/[wiki]/footer/:de/#page.md new file mode 100644 index 0000000..20b579d --- /dev/null +++ b/pages/[wiki]/footer/:de/#page.md @@ -0,0 +1,6 @@ +::: { .menu } + * <> + * [Registrieren](./[register]) + * [Einladen](./[invite]) +::: +Der Seitenfuß kann [hier](/[wiki]/footer/[edit]) bearbeitet werden diff --git a/pages/[wiki]/header/#page.md b/pages/[wiki]/header/#page.md new file mode 100644 index 0000000..8624c9b --- /dev/null +++ b/pages/[wiki]/header/#page.md @@ -0,0 +1,11 @@ +[Shellwiki](/) +-------------- + +::: { .menu } + * [Help](/[wiki]/) + * [Edit Style](/[wiki]/) +::: + +Edit the Header [here](/[wiki]/header/[edit]) + +---- diff --git a/pages/[wiki]/header/:de/#page.md b/pages/[wiki]/header/:de/#page.md new file mode 100644 index 0000000..306f95b --- /dev/null +++ b/pages/[wiki]/header/:de/#page.md @@ -0,0 +1,11 @@ +[Shellwiki](/) +-------------- + +::: { .menu } + * [Hilfe](/[wiki]/) + * [Stil anpassen](/[wiki]/) +::: + +Der Seitenkopf kann [hier](/[wiki]/header/[edit]) bearbeitet werden + +---- diff --git a/pages/[wiki]/invite/#page.md b/pages/[wiki]/invite/#page.md new file mode 100644 index 0000000..114b208 --- /dev/null +++ b/pages/[wiki]/invite/#page.md @@ -0,0 +1,7 @@ +%nocache + +Set up an account +----------------- +<> + +[Return](./) diff --git a/pages/[wiki]/invite/:de/#page.md b/pages/[wiki]/invite/:de/#page.md new file mode 100644 index 0000000..1386381 --- /dev/null +++ b/pages/[wiki]/invite/:de/#page.md @@ -0,0 +1,7 @@ +%nocache + +Neues Benutzerkonto +------------------- +<> + +[Zurück](./) diff --git a/pages/[wiki]/login/#page.md b/pages/[wiki]/login/#page.md new file mode 100644 index 0000000..ac715ae --- /dev/null +++ b/pages/[wiki]/login/#page.md @@ -0,0 +1,8 @@ +%nocache + +Login +----- +<> +[Account registration]([register] "Sign up for a new user account") + +[Return](./) diff --git a/pages/[wiki]/login/:de/#page.md b/pages/[wiki]/login/:de/#page.md new file mode 100644 index 0000000..9a110eb --- /dev/null +++ b/pages/[wiki]/login/:de/#page.md @@ -0,0 +1,8 @@ +%nocache + +Login +----- +<> +[Neu registrieren]([register] "Ein neues Benutzerkonto erstellen") + +[Zurück](./) diff --git a/pages/[wiki]/register/#page.md b/pages/[wiki]/register/#page.md new file mode 100644 index 0000000..b88149c --- /dev/null +++ b/pages/[wiki]/register/#page.md @@ -0,0 +1,7 @@ +%nocache + +Set up an account +----------------- +<> + +[Return](./) diff --git a/pages/[wiki]/register/:de/#page.md b/pages/[wiki]/register/:de/#page.md new file mode 100644 index 0000000..c46109b --- /dev/null +++ b/pages/[wiki]/register/:de/#page.md @@ -0,0 +1,7 @@ +%nocache + +Neues Benutzerkonto +------------------- +<> + +[Zurück](./) diff --git a/pages/[wiki]/settings/#page.md b/pages/[wiki]/settings/#page.md new file mode 100644 index 0000000..b03e0e2 --- /dev/null +++ b/pages/[wiki]/settings/#page.md @@ -0,0 +1,7 @@ +%nocache + +Change Your Passphrase +---------------------- +<> + +[Return](./) diff --git a/pages/[wiki]/settings/:de/#page.md b/pages/[wiki]/settings/:de/#page.md new file mode 100644 index 0000000..280f012 --- /dev/null +++ b/pages/[wiki]/settings/:de/#page.md @@ -0,0 +1,7 @@ +%nocache + +Passwort ändern +--------------- +<> + +[Zurück](./) diff --git a/parsers/50_markdown.sh b/parsers/50_markdown.sh new file mode 100755 index 0000000..94a0b06 --- /dev/null +++ b/parsers/50_markdown.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +if which awk >/dev/null; then + awk -f "$_EXEC/md_macros.awk" -f "$_EXEC/cgilite/markdown.awk" + # | sed -E 's;(<[^>]+ )href="((/[^"/]+|[^"/]+[^:/]|)/([^"/]+/)*)"([^>]*>);\1href="\2:'"${LANGUAGE}"'"\5;g' +elif which busybox >/dev/null; then + busybox awk -f "$_EXEC/md_macros.awk" -f "$_EXEC/cgilite/markdown.awk" + # | sed -E 's;(<[^>]+ )href="((/[^"/]+|[^"/]+[^:/]|)/([^"/]+/)*)"([^>]*>);\1href="\2:'"${LANGUAGE}"'"\5;g' +else + cat +fi diff --git a/parsers/60_translation_links.sh b/parsers/60_translation_links.sh new file mode 100755 index 0000000..0020719 --- /dev/null +++ b/parsers/60_translation_links.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ "$LANGUAGE" != "$LANGUAGE_DEFAULT" ]; then + sed -E 's;(<[^>]+ )href="((/[^"/]+|[^"/]+[^:/]|)/([^"/]+/)*)"([^>]*>);\1href="\2:'"${LANGUAGE}"'"\5;g' +else + cat +fi diff --git a/session_lock.sh b/session_lock.sh new file mode 100755 index 0000000..21c9808 --- /dev/null +++ b/session_lock.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +[ "$include_sessionlock" ] && return 0 +include_sessionlock="$0" + +. "$_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 +} diff --git a/themes/default.css b/themes/default.css new file mode 100644 index 0000000..0bd8c2a --- /dev/null +++ b/themes/default.css @@ -0,0 +1,299 @@ +html { min-height: 100%; } + +body { + position: absolute; + width: 100%; + min-height: 100%; + padding-bottom: 6em; + background-color: #EEE; + font-size: 12pt; +} + +header, footer { + background-color: #FFF; + box-shadow: 0 0 .75em; + width: 100%; + z-index: 1; +} + +footer { + padding-top: .5em; + position: absolute; + bottom: 0; +} + +header > :last-child, +main > :last-child { + margin-bottom: 0; +} + +header h1, +header h2, +header .menu, +footer .menu { + display: inline-block; +} + +header .menu, +footer .menu { list-style: none; } + +header .menu > ul > li, +header .menu > ol > li, +footer .menu > ul > li, +footer .menu > ol > li { + display: inline-block; + margin-right: .5em; + vertical-align: top; +} + +header .menu.dropdown li > ul, +header .menu.dropdown li > ol { + display: none; + position: absolute; + background-color: #FFF; + margin: 0; + padding: .25em .5em; + padding-left: 1.5em; + box-shadow: 0 0 .75em; +} +header .menu.dropdown li:hover > ul, +header .menu.dropdown li:hover > ol { + display: table; +} + +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, +main > form#renamepage, main > form#movepage, +main > form#deletepage, +[id$="/[attachment]"] main > form { + margin: 1em; + padding: .125em 1em 1em 1em; + box-shadow: .25em .25em .75em; + background-color: #FFF; +} + +[id$="/[revision]"] main .revisions, +[id$="/[attachment]"] main > .attachment.list { + margin: 1em; + padding: 1em 2em; +} +[id$="/[revision]"] main .revisions:before, +[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; +} + +main code { + padding: .125em .25em; + background-color: #CCC; +} +main pre { + padding: .5em .5em; + background-color: #CCC; + max-width: 100%; + overflow-x: auto; +} + +li.task > input[type=checkbox][disabled], +li.task > p > input[type=checkbox][disabled] { + display: none; +} +li.task > p:first-child { display: inline-block;} + +-li.task:before { font-size: 1.125em; } +li.task.pending:before { content: '\274f '; color: #222; } +li.task.partial:before { content: '\25d4 '; color: #880; } +li.task.negative:before { content: '\2718 '; color: #800; } +li.task.done:before { content: '\2714 '; color: #080; } + +/* Alternative Check Symbols, all from "geometric shapes" block */ /* +-li.task.pending:before { content: '\25a1 '; color: #222; } +-li.task.partial:before { content: '\25d4 '; color: #880; } +-li.task.negative:before { content: '\25a8 '; color: #800; } +-li.task.done:before { content: '\25a3 '; color: #080; } +*/ + + +/* === Editor === */ + +[id$="/[edit]"] main .pagemenu { + margin-bottom: 1em; +} + +.tab[name=edithelp] ~ .tab.editor textarea, +.tab[name=edithelp] ~ .tab.syntax, +.tab[name=edithelp] ~ .tab.attach, +.tab[name=edithelp] ~ .tab.transl { + background-color: #FFF; + min-height: 20em; min-height: 50vh; +} + +.tab[name=edithelp] ~ .tab.editor textarea { + width: 100%; + font-family: monospace; + font-size: inherit; +} + +.tab[name=edithelp] ~ .tab.attach { + padding-top: 1em; + padding-left: 7em; +} +.tab[name=edithelp] ~ .tab.attach .aimg img { + float: left; + max-height: 4em; + margin-left: -6em; +} + +.tab[name=edithelp] ~ .tab.transl { + font-family: monospace; + white-space: pre; +} + +.tab[name=edithelp]#editor:checked ~ .tab.editor, +.tab[name=edithelp]#syntax:checked ~ .tab.syntax, +.tab[name=edithelp]#attach:checked ~ .tab.attach, +.tab[name=edithelp]#transl:checked ~ .tab.transl { + display: block; +} + + +/* === 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; +} + +.revisions li { margin: 1em 0; } +.revisions li span.hash, +.revisions li span.date { + margin-right: 1em; +} + +.revisions .diff span { + font-family: monospace; + display: block; + white-space: pre; + line-height: 1.375em; +} +.revisions .diff span.linenum { color: #D60; } +.revisions .diff span.linedel { color: #A00; } +.revisions .diff span.lineadd { color: #0A0; } +.revisions .diff span.linenote { color: #AAA; } + + +[id$="/[attachment]"] input[type=radio].tab ~ div.tab { + display: block; + padding-top: 1em; +} +[id$="/[attachment]"] input[type=radio].tab ~ div.tab ul.attachment.list { + list-style: none; + margin-left: 0; +} + +.tab ul li input[name=select], +.tab ul li label.name, +.tab ul li a.name, +.tab ul li input.name { + display: none; +} + +[id$="/[attachment]"] input[type=radio].tab#tview:checked ~ div.tab ul li a.name, +[id$="/[attachment]"] input[type=radio].tab#tdel:checked ~ div.tab ul li input[name=select], +[id$="/[attachment]"] input[type=radio].tab#tdel:checked ~ div.tab ul li label.name, +[id$="/[attachment]"] input[type=radio].tab#tmove:checked ~ div.tab ul li input[name=select], +[id$="/[attachment]"] input[type=radio].tab#tmove:checked ~ div.tab ul li label.name { + display: inline; +} +[id$="/[attachment]"] input[type=radio].tab#tren:checked ~ .tab ul li input.name { + display: block; +} + +[id$="/[attachment]"] label[for=moveto], [id$="/[attachment]"] input#moveto, +[id$="/[attachment]"] button[name=action] { display: none; } + +[id$="/[attachment]"] .upload button[name=action] { display: inline-block; } +[id$="/[attachment]"] input[type=radio].tab#tdel:checked ~ div.tab button[name=action][value=delete], +[id$="/[attachment]"] input[type=radio].tab#tmove:checked ~ div.tab label[for=moveto], +[id$="/[attachment]"] input[type=radio].tab#tmove:checked ~ div.tab input#moveto, +[id$="/[attachment]"] input[type=radio].tab#tmove:checked ~ div.tab button[name=action][value=move], +[id$="/[attachment]"] input[type=radio].tab#tren:checked ~ div.tab button[name=action][value=rename] { + display: inline; +} + + +/* === 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; } + + +.macro.gallery { + text-align: center; + margin: 2em 0; + padding: .5em .125em; + background-color: #444; + clear: both; +} +.macro.gallery img { + max-height: 9em; + margin: 0 .25em; +} + + +.macro.changes td .date { + display: block; + font-size: .75em; +} +.macro.changes td.outdated, +.macro.changes td.current, +.macro.changes td.missing { + text-align: center; +} +.macro.changes th { background-color: #EEF; } +.macro.changes td { background-color: #DFF; } +.macro.changes td.outdated { background-color: #FFD; } +.macro.changes td.current { background-color: #DFD; } +.macro.changes td.missing { background-color: #FDD; } diff --git a/themes/default.sh b/themes/default.sh new file mode 100755 index 0000000..3c5a87f --- /dev/null +++ b/themes/default.sh @@ -0,0 +1,204 @@ +#!/bin/sh + +. "$_EXEC/tools.sh" + +theme_head(){ + local IFS="$BR" + printf ' + + ' + for css in "$_BASE/%5B.%5D/cgilite/common.css" "$_BASE/%5B.%5D/themes/default.css" $PAGE_CSS; do + printf '' \ + "$(HTML "${css##*//}")" + done +} + +theme_header(){ + printf '
%s
' "$(wiki '[wiki]/header/')" +} + +theme_footer(){ + printf '
%s
' "$(wiki '[wiki]/footer/')" +} + +theme_pagemenu(){ + local page="$1" + + if acl_write "$page"; then + cat <<-EOF + + EOF + fi +} + +theme_page(){ + local page="$1" title="$2" + title="$(HTML "${title:-"${PAGE_TITLE:-"${page}"}"}")" + + # Important! Web Server response including newline + printf "%s\r\n" "Content-Type: text/html; charset=utf-8" "" + + cat <<-EOF + + + $(theme_head) + ${title} + + $(theme_header) +
+ $(theme_pagemenu) + $(if [ "$page" = '-' ]; then + cat + else + printf '
' + wiki "$page" + printf '
' + fi) +
+ $(theme_footer) + + EOF +} + +theme_editor(){ + local page="$1" template="$2" title file att + title="$(HTML "${PAGE_TITLE:-"${page}"}")" + + [ "$template" ] && acl_read "$template" || template="$page" + + theme_page - "Editor: $title" <<-EOF + + + + $([ "$LANGUAGE_DEFAULT" -a "$LANGUAGE_DEFAULT" != "$LANGUAGE" ] && printf ' + + ' "$LANGUAGE_DEFAULT" + ) +
+ + + + +
+
$(wiki "/[wiki]/editorhelp/")
+
+ $(for file in "$_EXEC/pages/${page%/:$LANGUAGE/}/#attachments"/* "$_DATA/pages/${page%/:$LANGUAGE/}/#attachments"/*; do + [ "$file" = "$_EXEC/pages/${page%/:$LANGUAGE/}/#attachments/${file##*/}" \ + -a -f "$_DATA/pages/${page%/:$LANGUAGE/}/#attachments/${file##*/}" ] && continue + case ${file##*/} in + \*) continue;; + *.[pP][nN][gG]|*.[jJ][pP][gG]|*.[jJ][pP][eE][gG]|*.[gG][iI][fF]) + [ "$page" != "${page%/:$LANGUAGE/}" ] && p=../ || p='' + att="$(HTML "${file##*/}")" + printf '

![](%s)

  • [%s]([attachment]/%s)
  • [![%s](%s)]([attachment]/%s)
' \ + "$p" "$att" "$att" "$att" "$att" "$att" "$att" "$att" + ;; + *) + att="$(HTML "${file##*/}")" + printf '

[%s](%s)

' "$att" "$att" + ;; + esac + done) +
+ $(if [ "$LANGUAGE_DEFAULT" -a "$LANGUAGE_DEFAULT" != "$LANGUAGE" ]; then + printf '
%s
' "$(LANGUAGE='' wiki_text "${page%/:$LANGUAGE/}" |HTML)" + fi) + EOF +} + +theme_revisions(){ theme_page "$@"; } + +theme_attachments(){ + local page="$1" title + title="${page%/}"; title="${title##*/}" + + if acl_write "$page"; then + theme_page - "$(_ Attachments): $title" <<-EOF +
+

$(_ Upload)

+ + + +
+ +
+

$(_ Attachments)

+ + + + + +
+
    + $(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#* }" + hfile="$(HTML "${file##*/}")" + + printf '
  • + + + %s + %s + %s +
  • ' \ + "$hfile" "$hfile" "$hfile" "$hfile" \ + "$(slopecode "${file##*/}")" "$hfile" "$hfile" "$hfile" \ + "$(size_human "$size")" "$(date -d @"$date" +"%F %T")" + done) +
+ + + + + +
+
+ EOF + else + theme_page - "$(_ Attachments): $title" <<-EOF +
    + $(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#* }" + hfile="$(HTML "${file##*/}")" + + printf '
  • %s + %s%s
  • ' \ + "$hfile" "$hfile" "$(size_human "$size")" "$(date -d @"$date" +"%F %T")" + done) +
+ 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 +} diff --git a/tools.sh b/tools.sh new file mode 100755 index 0000000..4e93d3f --- /dev/null +++ b/tools.sh @@ -0,0 +1,135 @@ +#!/bin/sh + +[ "$include_tools" ] && return 0 +include_tools="$0" + +md(){ + local parser + + if [ "$#" = 0 ]; then + md "${_EXEC}"/parsers/* + elif [ "$#" = 1 ]; then + "$1" + else + parser="$1" + shift 1 + "$parser" |md "$@" + fi +} + +mdfile(){ + # Check if page exists, if possible fall + # back to default page from installation + local page="$(PATH "$1")" + page="${page%/}" + + # Regular processing, keep in sync with tools.sh + if [ -f "$_DATA/pages/$page/:$LANGUAGE/#page.md" ]; then + printf %s\\n "$_DATA/pages/$page/:$LANGUAGE/#page.md" + elif [ -f "$_DATA/pages/$page/#page.md" ]; then + printf %s\\n "$_DATA/pages/$page/#page.md" + elif [ -f "$_EXEC/pages/$page/:$LANGUAGE/#page.md" ]; then + printf %s\\n "$_EXEC/pages/$page/:$LANGUAGE/#page.md" + elif [ -f "$_EXEC/pages/$page/#page.md" ]; then + printf %s\\n "$_EXEC/pages/$page/#page.md" + else + return 1 + fi 2>&- + # ^^ suppress error messages produced + # by printf when stdout was closed + + return 0 +} + +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 +} + +attachment_glob(){ + local pattern="${1%/}" IFS='' + local glob page pagedir + + page="${pattern%/*}" + [ "$page" = "$pattern" ] && page=. + [ ! "$page" ] && page=/ + pattern="${pattern##*/}" + [ ! "$pattern" ] && pattern="*" + + case $page in + /*) + for glob in "$_DATA/pages/$page/#attachments"/$pattern; do printf '%s\n' "${glob#"$_DATA/pages"}"; done + for glob in "$_EXEC/pages/$page/#attachments"/$pattern; do printf '%s\n' "${glob#"$_EXEC/pages"}"; done + ;; + *) + for glob in "$_DATA/pages/$PATH_INFO/$page/#attachments"/$pattern; do printf '%s\n' "${glob#"$_DATA/pages/$PATH_INFO/"}"; done + for glob in "$_EXEC/pages/$PATH_INFO/$page/#attachments"/$pattern; do printf '%s\n' "${glob#"$_EXEC/pages/$PATH_INFO/"}"; done + ;; + esac \ + | sort -u \ + | while read -r glob; do + [ -e "$glob" ] || continue + pagedir="$(page_abs "${glob%%/#attachments/*}/")" + [ -d "$_DATA/pages/$pagedir" -o -d "$_EXEC/pages/$pagedir" ] \ + && printf '%s\n' "${glob%%/#attachments/*}/${glob#*/#attachments/}" + done +} + +page_glob(){ + local pattern="${1%/}/" depth="${2:-0}" IFS='' + local glob page pagedir + + case $pattern in + /*) + for glob in "$_DATA/pages"$pattern; do printf '%s\n' "${glob#"$_DATA/pages"}"; done + for glob in "$_EXEC/pages"$pattern; do printf '%s\n' "${glob#"$_EXEC/pages"}"; done + ;; + *) + for glob in "$_DATA/pages/$PATH_INFO"/$pattern; do printf '%s\n' "${glob#"$_DATA/pages/$PATH_INFO/"}"; done + for glob in "$_EXEC/pages/$PATH_INFO"/$pattern; do printf '%s\n' "${glob#"$_EXEC/pages/$PATH_INFO/"}"; done + ;; + esac \ + | sort -u \ + | while read -r page; do + # Not a page directory (just a metadata dir) + [ ! "${page%%#*}" -o ! "${page%%*/#*}" ] && continue + + # Omit "system" pages unless explicitly wanted + [ ! "${page%%\[*\]/*}" -o ! "${page%%*/\[*\]/*}" ] && [ "$glob_system_pages" != true ] && continue + + # Omit translation pages if translations are enabled + [ ! "${page%%:*}" -o ! "${page%%*/:*}" ] && [ "$LANGUAGE_DEFAULT" ] && continue + + pagedir="$(page_abs "$page")" + + if [ -d "$_DATA/pages/$pagedir" -o -d "$_EXEC/pages/$pagedir" ]; then + printf '%s\n' "$page" + if ! [ "$depth" -eq 0 ]; then + PATH_INFO="$pagedir" page_glob "*" "$((depth - 1))" \ + | while read -r glob; do printf %s%s\\n "$page" "$glob"; done + fi + fi + done +} + +page_abs(){ + case $1 in + /*) PATH "${1%/}/";; + *) PATH "${PATH_INFO%/*}/${1%/}/";; + esac +}