| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 | 
							- #!/bin/bash
 
- ## SuperGlue project | http://superglue.it | 2014 | GPLv3
 
- ## http://git.superglue.it/superglue/serverside/edit/master/common/rootFS/www/lib/post.sh
 
- ## 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
 
- ## no globbing, for safety
 
- set -o noglob
 
- ## some path variables
 
- _WWW='/www'
 
- _HTDOCS="${_WWW}/htdocs"
 
- _TMP="${_WWW}/tmp"
 
- _LOG="${_WWW}/log/post.log"
 
- ## multihost
 
- #if [[ $HTTP_HOST == 'demo.superglue.it' ]]; then
 
- #  _HTDOCS="${_WWW}/htdocs-demo"
 
- #fi
 
- ## _DEBUG=0 no logging at all
 
- ## _DEBUG=1 writes to $_LOG file
 
- ## _DEBUG=2 adds [verbose].. to HTTP response. Can be triggered via 'Content-Type' header option
 
- ## eg: "Content-Type:application/octet-stream; verbose=1"
 
- _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
 
- }
 
- ## get data from argv=data pair in POST request
 
- ## (any 4 character arg in the beginning of string is matched)
 
- postGetData() {
 
-   _POST="${_POST##????'='}"
 
- }
 
- ## urlencoded POST dispatcher
 
- postUrlenc() {
 
-   ## decode stream
 
-   _POST=$(urlDecode "$(< $_POST_TMP)")  ## decode global $_POST
 
-   postGetData
 
-   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
 
- }
 
- ## 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
 
- ## 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'
 
- [[ $CONTENT_LENGTH -gt 0 ]] || errorCheck $? 'content length zero'
 
- ## 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'; }
 
- _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
 
 
  |