gurl
#!/bin/sh
#
# Copyright (c) 2007, 2008 Oligem.com. All rights reserved.
#
# This code is derived from software contributed to Oligem by
# Marc Vertes <mvertes@free.fr>.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
#
# Todo:
# - better doc
# - hresp: complete processing of chuncks.
# - ftp support
# - support HTTP authenfication (basic, digest), and proxy authentification
# - keep-alive connections
#
#
# print text that can be transformed into a man page using txt2man.
#
man()
{
echo 'NAME
gurl - HTTP transfer tool
SYNOPSIS
gurl [-0|-1][-d pdata][-f filename][-p host:port][-x] URL
DESCRIPTION
gurl is a simple shell script to transfer data from or to a HTTP server.
OPTIONS
-0 Select HTPP/1.0 protocol (default).
-1 Select HTPP/1.1 protocol.
-d pdata Send the specified pdata in a POST request to the HTTP
server. pdata is expected to be url-encoded.
-f filename Upload file filename using a HTTP POST form.
-p host:port Connect through a http proxy server.
-x Enable debug trace: display HTTP resp header on stderr.
ENVIRONMENT
http_proxy In the form of host:port, to set the proxy.
EXAMPLES
print the content of an url on standard output:
$ gurl http://www.google.com/
IMPLEMENTATION NOTES
BSD systems: portal pseudo filesystem must be enabled to allow
tcp connection. See mount_portal(8).
Tested with ash (*BSD /bin/sh), bash, pd-ksh, ksh93, zsh.
AUTHOR
Marc Vertes
SEE ALSO
curl(1), wget(1), nc(1)'
}
unset CDPATH
export LC_ALL=C
die() { echo "gurl fatal: $@" >&2; exit 1; }
error() { echo "gurl error: $@" >&2; }
# Usage: dd_read Nbytes
# Read the specified number of bytes from stdin and print them to stdout
dd_read()
{
bs=4096 len=$1 # bs is the default block size
[ "$ddr" ] || { ddr=/tmp/gurl_read.$$; trap "rm $ddr" EXIT; }
while [ $len -gt 0 ]
do
[ $len -ge $bs ] && b=$bs c=$(($len / $bs)) || b=$len c=1
dd bs=$b count=$c 2>$ddr
{ read -r nil; read -r nil; read -r n nil; } <$ddr
[ ! "$n" -o "$n" = 0 ] && break || len=$(($len - $n))
done
}
#
# parse a HTTP response, and print content if OK
#
hresp()
{
out=1 # output is stdout
len=chuncked # HTTP defaults to chuncked data
while read line
do
[ "$debug" = 1 ] && echo $line >&2
line=${line%
} # strip CR
case $line in
(HTTP*2[0-9][0-9]*OK*)
;;
(HTTP*[1345][0-9][0-9]*)
error $line; out=2 ;; # output is stderr
(Connection:\ [Cc]lose)
len=close ;; # read until eof
(Content-Length:*)
len=${line#*: } ;; # read $len bytes
(*:*)
;;
(*)
case $len in
(chuncked)
[ "$out" = 2 ] && break
# the next line is the data length in hexa
read line
len=$(printf %d 0x${line%
})
dd_read $len
# FIXME: should iterate until last chunck
;;
(close)
cat >&$out ;;
([0-9]*)
dd_read $len >&$out ;;
esac
break
;;
esac
done
[ "$out" = 1 ] # true if no error occured
}
#
# Establish a tcp connection to the http server
# fd 3 is allocated to tcp input, fd 4 to tcp output.
#
connect()
{
[ "$host" ] && nhost=$host || die "missing host"
[ "$port" ] && nport=$port || die "missing port"
[ "$http_proxy" ] && nhost=${http_proxy%:*} nport=${http_proxy#*:}
# Check for /dev/tcp on portals.
# If not found, check for shell provided /dev/tcp (ksh93, bash3)
case $(uname -s) in
(*BSD) tcp_pref=$(mount | awk '$5 == "portal" {print $3}') ;;
esac
if [ ! "$tcp_pref" ]
then
trap '' KEYBD 2>/dev/null && tcp_pref=/dev || #ksh93
[ "${BASH_VERSION%%.*}" = 3 ] && tcp_pref=/dev
fi
# Try /dev/tcp. If not available, try a netcat coprocess.
if [ "$tcp_pref" ]
then
exec 3<>$tcp_pref/tcp/$nhost/$nport ||
die "cannot connect to $nhost:$nport"
exec 4<&3
return
fi
# create a pair of pipes, connect nc(1) coprocess to them
[ "$(command -v nc)" ] || die "no command nc"
mkfifo /tmp/gurli.$$ /tmp/gurlo.$$
trap "rm -f /tmp/gurli.$$ /tmp/gurlo.$$" EXIT
exec nc $nhost $nport </tmp/gurli.$$ >/tmp/gurlo.$$ &
exec 3>/tmp/gurli.$$
exec 4</tmp/gurlo.$$
}
#
# Main starts here
#
proto=0
while getopts :01xd:f:p: opt
do
case $opt in
(0) proto=0 ;;
(1) proto=1 ;;
(d) data="$OPTARG" ;;
(f) filename="$OPTARG" ;;
(p) http_proxy="$OPTARG" ;;
(x) debug=1 ;;
(*) man; exit 1 ;;
esac
done
shift $(($OPTIND - 1))
url=${1#http://}
rbase=${url#*/}
[ "$rbase" = "$url" ] && rbase=
host=${url%/$rbase}
port=${host#*:}
[ "$port" = "$host" ] && port=80
host=${host%:*}
vers=0.1
[ "$http_proxy" ] && rbase=$1
case "$rbase" in ([!/]*) rbase=/$rbase ;; esac
#
# The presence of CR chars (^M) at end of header lines is required by
# the HTTP protocol, please do not remove them.
#
[ "$data" ] && # Data not in URL are sent through a POST request
req="POST $rbase HTTP/1.$proto
User-Agent: gurl-$vers
Host: $host:$port
Accept: */*
Content-Length: ${#data}
Content-Type: application/x-www-form-urlencoded
$data" || # Default is a simple GET request
req="GET $rbase HTTP/1.$proto
User-Agent: gurl-$vers
Host: $host:$port
Accept: */*
"
#
# If a file is specified, build a special file upload POST request
#
[ "$filename" ] && {
[ -f "$filename" ] || die "invalid file $filename";
fsize=$((354 + $(wc -c <$filename)))
maxsize=2000000
[ $fsize -gt $maxsize ] &&
die "file $filename is larger than $maxsize"
b=---------------------------60841378475689853717345751983
req="POST $rbase HTTP/1.$proto
User-Agent: gurl-$vers
Host: $host:$port
Accept: */*
Content-Length: $fsize
Content-Type: multipart/form-data; boundary=$b
--$b
Content-Disposition: form-data; name=\"MAX_FILE_SIZE\"
$maxsize
--$b
Content-Disposition: form-data; name=\"userfile\"; filename=\"x\"
Content-Type: application/octet-stream
"
connect || die "could not connect to $host $port"
{
echo -n "$req"
cat $filename
echo -n "
--$b--
"
} >&3
hresp <&4
exit
}
connect || die "could not connect to $host $port"
echo -n "$req" >&3
hresp <&4
|