Pārlūkot izejas kodu

writeout superglue.revision before compiling

Danja Vasiliev 10 gadi atpakaļ
vecāks
revīzija
db234721d0
1 mainītis faili ar 338 papildinājumiem un 0 dzēšanām
  1. 338 0
      openwrt/common/opt/lib/cgi/post2.cgi

+ 338 - 0
openwrt/common/opt/lib/cgi/post2.cgi

@@ -0,0 +1,338 @@
+#!/usr/bin/haserl --shell=/bin/bash  --upload-limit=32768 --upload-dir=/www/tmp 
+<%# upload limit: 32Mb %>
+<%
+
+## SuperGlue project | http://superglue.it | 2014 | GPLv3
+## http://git.superglue.it/superglue/serverfiles
+## author: Danja Vasiliev <danja@k0a1a.net>
+##
+## post.sh - all POST requests are redirected to this script.
+## 
+## examples:
+## text:    curl --data-urlencode '<html><title>' http://host/file.html
+## image:   curl --form "userimage=@file.png" -H "Expect:" http://host/file.png 
+## command: curl --data-urlencode 'ls' http://host/cmd
+##
+## returns: 200 (+ output of operation) on success
+##          406 (+ error message in debug mode) on error
+##
+## auth:    curl --digest -u admin:changeme ...
+##
+## no globbing, for safety
+set -o noglob
+
+## some path variables
+readonly _WWW='/www'
+readonly _HTDOCS="${_WWW}/htdocs"
+readonly _TMP="${_WWW}/tmp"
+readonly _LOG="${_WWW}/log/post.log"
+
+## multihost, example
+#if [[ $HTTP_HOST == 'domain.name' ]]; then
+#  _HTDOCS="${_WWW}/domain-name"
+#fi
+
+## _DEBUG=0 no logging at all
+## _DEBUG=1 writes to $_LOG file
+## _DEBUG=2 adds a message to HTTP response
+_DEBUG=1
+
+#### FUNCTIONS
+
+## logging
+logThis() {
+  [[ "$_DEBUG" -gt 0 ]] || return 0
+  [[ "$_ERR" -gt 0 ]] && _TYPE='E:' || _TYPE='I:'  ## Info or Error indication
+  local _TIME=$(printf '%(%d.%m.%Y %H:%M:%S)T' -1)
+  printf '%b\n' "$_TIME  $_TYPE ${1} " >> $_LOG
+  [[ "$_DEBUG" -gt 1 ]] && printf '%b\n' "[verbose] $_TYPE ${1}"
+  return 0
+}
+
+## inject function execution trace to global _OUT
+wTf() {
+  local _WTF="$(printf '%s -> ' '| trace: '${FUNCNAME[*]:1})"
+  _OUT="$_OUT $_WTF"
+}
+
+## urldecode
+urlDecode() {
+  local encoded="${1//+/ }"
+  printf '%b' "${encoded//%/\x}"
+}
+
+## http response
+headerPrint() {
+  case ${1} in
+    200) printf '%b' 'HTTP/1.1 200 OK\nAccess-Control-Allow-Origin: *\n\n';;
+    405) printf '%b' 'HTTP/1.1 405 Method Not Allowed\n\n';;
+    406) printf '%b' 'HTTP/1.1 406 Not Acceptable\n\n';;
+  esac
+  return 0
+}
+
+## takes exit code variable $? and optional "message" string.
+## exit code 0 simply falls through. when local message 
+## is not provided tries to assign global $_OUT.
+##
+## eg: errorCheck $? "bad zombie"
+##
+## produces HTTP 406 header, $_OUT message, triggers logThis()
+## and exits the main loop with exit >= 1.
+errorCheck() {
+  _ERR=${1}  ## exit code
+  [[ $_ERR -gt 0 ]] || return 0
+  local _MSG=${2}
+  ## if $_OUT is present cut it down to one line
+  ## otherwise assign message from the invokation arguments
+  [[ $_OUT ]] && _OUT="${_OUT%%$'\n'*}" || { _OUT=${_MSG:='unknown error occured'}; wTf; }
+  [[ -e $_POST_TMP ]] && rm -f $_POST_TMP
+  headerPrint '406'
+  logThis "${_OUT}";
+  exit $_ERR
+}
+
+## urlencoded POST dispatcher
+postUrlenc() {
+  ## set vars found in POST
+  setQueryVars
+  case "${_REQUEST_URI}" in
+    \/cmd) postCmd  ;;  ## handle /cmd POST
+        *) postHtml ;;  ## handle html POST
+  esac
+}
+
+## handle /cmd POST
+postCmd() {
+  local _CMD=( ${_POST} )  ## convert POST to array
+  [[ ${#_CMD[@]} -lt 5 ]] || errorCheck '1' "'${_CMD[*]}': too many arguments"
+  local _EXE="${_CMD[0]}"  ## first member is command 
+  local _ARG="${_CMD[@]:1}"  ## the rest is arguments
+  ## note unquoted regex
+  [[ ! "$_ARG" =~ (\.\.|^/| /) ]] || errorCheck '1' "'$_ARG': illegal path"
+
+  ## 'ls' replacement function
+  lss() {
+    _D='\t' ## do we want a customizable delimiter? 
+    while getopts 'la' _OPT; do
+      case $_OPT in
+        l) local _LNG="$_D%F$_D%s$_D%y$_D%U$_D%G$_D%a" ;;
+        a) shopt -s dotglob
+      esac
+    done
+    shift $((OPTIND-1)) ## removing used args
+    [[ -z "${@}" ]] && _PT="./*"  ## list ./* if called with no args
+    [[ -d "${@}" ]] && _PT="/*" ## add /* to directories
+    ## if error occures return 0
+    stat --printf "%n$_LNG\n" -- "${@%%/}"$_PT 2>/dev/null || _ERR=0
+    return $_ERR
+  }
+  case "$_EXE" in
+   ls|lss) _EXE="lss"; _ARG="${_ARG}" ;;  ## no error is returned
+       cp) _ARG="${_ARG}" ;;
+       rm) _ARG="${_ARG}" ;;  ## add recursive option if you need
+       mv) _ARG="${_ARG}" ;;
+    mkdir) _ARG="${_ARG}" ;;
+      log) _EXE="tail"; _ARG="${_ARG} ${_LOG}" ;; 
+     wget) _ARG="-q ${_ARG/ */} -O ${_ARG/* /}" ;;  ## quiet
+        *) errorCheck '1' "'$_EXE': bad command" ;;
+  esac
+  ## toggle globbing  
+  set +o noglob  
+  _OUT=$($_EXE $_ARG 2>&1)
+  _ERR=$?
+   ## toggle globbing
+  set -o noglob
+  logThis "$_EXE $_ARG"
+  errorCheck $_ERR
+}
+
+## handle html POST
+postHtml() {
+  ## save POST to file
+  _OUT=$( (printf '%b' "${_POST}" > "${_HTDOCS}${_REQUEST_URI}") 2>&1)
+  _ERR=$?
+  errorCheck $_ERR
+}
+
+setQueryVars() {
+  _VARS=( ${!POST_*} )
+  local v
+  for v in ${_VARS[@]}; do
+    logThis "$v=${!v}"
+  done
+}
+
+## octet POST dispatcher
+postOctet() {
+  ## get 'data:' header length 
+  local IFS=','; read -d',' -r _DH < $_POST_TMP
+  case "${_ENC}" in
+    base64) postBase64Enc;;
+    binary) postBinary ;;
+         *) postGuessEnc ;;  ## handle data POST
+  esac
+}
+
+## to be converted into a proper data-type detection function
+postGuessEnc() {
+  shopt -s nocasematch
+  local _DTP="^.*\;([[:alnum:]].+)$" ## data-type header pattern
+  ## look for encoding in the data header
+  [[ "${_DH}" =~ ${_DTP} ]] && _ENC="${BASH_REMATCH[1]}"
+  logThis "'$_ENC:' encoding is the best guess";
+  shopt -u nocasematch
+  case "$_ENC" in
+                base64) postBase64Enc ;;
+             ## binary) _ERR=1 ;; 
+             ##   json) _ERR=1 ;;
+   ## quoted-printable) _ERR=1 ;;
+                     *) _ERR=1; _OUT="'${_ENC:='unknown'}' encoding, unknown POST failed";;
+  esac
+  errorCheck $_ERR
+}
+
+## handle base64 post
+postBase64Enc() {
+  logThis "'${_ENC}:' decoding stream"
+  _DL=${#_DH}  ## get data-header length
+  [[ $_DL -lt 10 ]] && { _DL=23; _SKP=0; } || { let _DL+=1; _SKP=1; }  ## '23' - what?!
+  ## the line below seems to be the best solution for the time being
+  ## dd 'ibs' and 'iflags' seem not to work on OpenWRT - investigate as it might be very useful
+  _OUT=$( dd if=${_POST_TMP} bs=${_DL} skip=${_SKP} | base64 -d > "${_HTDOCS}${_REQUEST_URI}" 2>&1) 
+  _ERR=$?
+  errorCheck $_ERR
+}
+
+postBinary() {
+  logThis "'binary': decoding stream"
+  ## it is unclear what will be necessary to do here
+  _OUT=$( dd if="${_POST_TMP}" of="${_HTDOCS}${_REQUEST_URI}" 2>&1 )
+  _ERR=$?
+  errorCheck $_ERR
+}
+
+postMpart() {
+  logThis "'multipart': decoding stream"
+  local _BND=$(findPostOpt 'boundary')
+  ## bash is binary unsafe and eats away precious lines
+  ## thus using gawk
+  function cutFile() {
+    gawk -v "want=$1" -v "bnd=$_BND" '
+      BEGIN { RS="\r\n"; ORS="\r\n" }
+
+      # reset based on boundaries
+      $0 == "--"bnd""     { st=1; next; }
+      $0 == "--"bnd"--"   { st=0; next; }
+      $0 == "--"bnd"--\r" { st=0; next; }
+
+      # search for wanted file
+      st == 1 && $0 ~  "^Content-Disposition:.* name=\""want"\"" { st=2; next; }
+      st == 1 && $0 == "" { st=9; next; }
+
+      # wait for newline, then start printing
+      st == 2 && $0 == "" { st=3; next; }
+      st == 3 { print $0 }
+      ' 2>&1
+  }
+  cutFile 'userimage' < "${_POST_TMP}" > "${_HTDOCS}${_REQUEST_URI}"
+  _ERR=$?
+  errorCheck $_ERR
+}
+
+## find arbitrary option supplied in Content-Type header
+## eg: "Content-Type:application/octet-stream; verbose=1"
+findPostOpt() {
+  for i in "${CONTENT_TYPE[@]:1}"; do
+    case "${i/=*}" in 
+      "$1") printf '%b' "${i/*=}" ;;
+    esac
+  done
+  return 0
+}
+
+## sanitize by backslashing all expandable symbols
+escapeStr() {
+  printf "%q" "${*}"
+}
+
+## brutally replace unwanted characters
+cleanFname() {
+  shopt -s extglob
+  local _STR="${*}"
+  echo -n "${_STR//[^[:alnum:]._\-\/\\]/_}"
+  shopt -u extglob
+}
+
+#### MAIN LOOP
+
+logThis 'yes'
+
+## timing
+## TODO: remove it
+## run once here and once at the end
+#read t z < /proc/uptime
+
+## check if we are in $_HTDOCS directory
+cd $_HTDOCS || errorCheck $? 'htdocs unavailable'
+[[ "${PWD}" == "${_HTDOCS}" ]] || errorCheck $? 'htdocs misconfigured'
+if [[ "${REQUEST_METHOD^^}" == "POST" ]]; then
+  [[ $CONTENT_LENGTH -gt 0 ]] || err 'content length is zero, 301 back to referer' '301'
+  case "${CONTENT_TYPE^^}" in 
+    APPLICATION/X-WWW-FORM-URLENCODED*) setQueryVars;;
+                  MULTIPART/FORM-DATA*) getQueryFile;;
+                                     *) _ERR=1; _OUT='this is not a post';;
+  esac
+
+  case $REQUEST_URI in
+                *cmd) pwdChange;;
+                   *) logThis 'bad action'; headerPrint 405; 
+                      echo 'no such thing'; exit 1;;
+  esac
+fi
+
+## URI is considered as a file dest to work with
+## add 'index.html' to default and empty request uri
+_REQUEST_URI="${REQUEST_URI/%\///index.html}"
+_REQUEST_URI="$(urlDecode $_REQUEST_URI)"
+_PATH="${_REQUEST_URI%/*}"
+
+CONTENT_TYPE=( ${CONTENT_TYPE} )
+_CONTENT_TYPE="${CONTENT_TYPE[0]/;}"
+_ENC="${HTTP_CONTENT_ENCODING}"
+
+#logThis "Len: $CONTENT_LENGTH Ctype: $CONTENT_TYPE Enc: $_CONTENT_ENCODING"
+
+## check for 'verbose' option in POST
+#findPostOpt 'verbose' || { _DEBUG=2; logThis 'verbose mode is requested'; }
+
+logThis 'yes 2'
+
+
+#_POST_TMP=$(mktemp -p $_TMP)  ## make tmp POST file
+#cat > $_POST_TMP  ## cautiously storing entire POST in a file
+
+## dispatching POST
+case "${_CONTENT_TYPE}" in
+  application\/x-www-form-urlencoded) postUrlenc ;;
+           application\/octet-stream) postOctet ;;
+                 multipart/form-data) postMpart ;;
+                                   *) _ERR=1; _OUT='this is not a post' ;;
+esac
+#[[ -e $_POST_TMP ]] && rm -f $_POST_TMP
+
+## make sure we are good
+errorCheck $_ERR 
+
+[[ -z $_OUT ]] || _OUT="${_OUT}\n"
+
+headerPrint '200' ## on success
+printf '%b' "${_OUT}" 
+logThis 'OK 200' 
+
+#read d z < /proc/uptime
+#logThis $((${d/./}-${t/./}))"/100s"
+
+exit 0
+
+%>