#!/bin/sh -eu # # httprecv -- A simple shell script that receives a file over HTTP # # Copyright (c) 2019 Samuel Lidén Borell # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # export LC_ALL=C.UTF-8 log() { level="$1" message="$2" date="$(date --rfc-3339=seconds)" printf '%s\n' "$date [$level] $message" >&2 } if [ $# = 0 ]; then port=1111 log WARNING "This script does NOT offer any security at all!" # Determine IP address, so we can show a IP # FIXME this is IPv4 only for now, but this script is intended for LAN use only # example.com = 93.184.216.34 ip=$(ip route get 93.184.216.34 | grep -oE 'src [^ \t]+' | cut -c '5-' || true) urltext="${ip:+, URL is http://$ip:$port/}" log INFO "Listening at port $port$urltext" log INFO "Uploaded files will be saved as \"$PWD/upload.bin\"" while nc.traditional -c "'$0' --internal-recv" -n -l -p "$port"; do true done exit elif [ $# != 1 ] || [ "$1" != --internal-recv ]; then cat < $title EOF } html_footer() { cat < EOF } resp_message() { respcode="$1" statusline="$2" resp_header "$respcode" "$statusline" html_head "$statusline" echo "

$statusline

" html_footer } skip_headers() { while read -r line; do line=${line%$cr} [ -n "$line" ] || break done } read_multipart_boundary() { boundary="$1" read -r line line="${line%$cr}" if [ "$line" != "--$boundary" ]; then resp_message 400 "Bad request" echo "Error: Unexpected multipart boundary from client." >&2 exit 1 fi } read -r reqline without_get=${reqline#GET } without_get=${without_get#get } without_head=${reqline#HEAD } without_head=${without_head#head } without_post=${reqline#POST } without_post=${without_post#post } if [ "${without_get}" != "$reqline" ]; then # GET request method=GET url_ver=$without_get elif [ "${without_head}" != "$reqline" ]; then # HEAD request method=HEAD url_ver=$without_head elif [ "${without_post}" != "$reqline" ]; then # POST request method=POST url_ver=$without_post else skip_headers resp_message 405 'Method not allowed' exit fi if [ "${url_ver#/ }" = "$url_ver" ]; then skip_headers resp_message 404 'Incorrect URL' exit fi # Read headers content_type="" content_type_boundary="" content_length="" while read -r hdr; do hdr="${hdr%$cr}" [ -n "$hdr" ] || break hdrlower=$(printf %s "$hdr" | tr '[:upper:]' '[:lower:]') hdrname=$(printf %s "$hdrlower" | cut -d ':' -f 1 | tr -s ' \t') hdrname=${hdrname% } hdrvalue=$(printf %s "$hdr" | cut -d ':' -f 2- | tr -s ' \t') hdrvalue=${hdrvalue# } case "$hdrname" in content-length) tmp=$(printf %s "$hdrvalue" | tr -cd 0123456789) if [ "$tmp" = "$hdrvalue" ]; then content_length=$tmp fi unset tmp;; content-type) content_type=$(printf %s "$hdrvalue" | cut -d ';' -f 1 | tr -s ' \t' | tr '[:upper:]' '[:lower:]') remaining="$hdrvalue" while true; do remaining=$(printf %s "$remaining" | cut -sd ';' -f 2- | tr -s ' \t') remaining=${remaining# } [ -n "$remaining" ] || break if [ "${remaining#boundary=}" != "$remaining" ]; then content_type_boundary=$(printf %s "${remaining#boundary=}" | cut -d ';' -f 1 | tr -s ' \t') fi done unset remaining;; esac done if [ "$method" = HEAD ]; then cat </dev/null else cat fi | { read_multipart_boundary "$content_type_boundary" part_length="" while read -r line; do line="${line%$cr}" [ -n "$line" ] || break hdrlower=$(printf %s "$hdr" | tr '[:upper:]' '[:lower:]') hdrname=$(printf %s "$hdrlower" | cut -d ':' -f 1 | tr -s ' \t') hdrname=${hdrname% } hdrvalue=$(printf %s "$hdr" | cut -d ':' -f 2- | tr -s ' \t') hdrvalue=${hdrvalue# } case "$hdrname" in content-length) tmp=$(printf %s "$hdrvalue" | tr -cd 0123456789) if [ "$tmp" = "$hdrvalue" ]; then part_length=$tmp fi unset tmp;; content-disposition) true # TODO ;; esac done # if [ -z "$part_length" ]; then # # TODO # true # else # dd of=upload.bin bs=1 count="$part_length" # fi # read_multipart_boundary "$content_type_boundary--" boundary_length=$(printf '%s' "RN--$boundary--RN" | wc -c) # include space for CR-LF before and after head -c -"$boundary_length" > upload.bin # FIXME this does not check the final multipart boundary } # TODO check for empty file and show a different message uploadstatus='File was uploaded successfully!' log INFO "$uploadstatus" unset line else log INFO "Sending start page" uploadstatus='' fi title=${uploadstatus:-File upload} statusdiv=${uploadstatus:+
$uploadstatus
} # Show form resp_header 200 Ok html_head "$title" cat <

Upload file

$statusdiv EOF html_footer