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

Copyright © 2008, Oligem
html css