Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # HTTP 1.0 Specification: http://www.w3.org/Protocols/HTTP/1.0/spec.html
- # HTTP 1.1 Specification: http://www.w3.org/Protocols/rfc2616/rfc2616.html
- hash tr funzip cat head grep;
- http.utils.stripcr() {
- tr -d $'\r';
- }
- http.parse.uri() {
- # $1 = scheme | host | port | path | query | param | file | request
- # $2 = URI
- # $3 variable label to write result if not present the result is echoed to stdout.
- # param has its own convention: $3 = param name; $4 = variable label
- local -i \
- invalid_arg=1 empty_uri=2 missing_component=3 missing_param=4 malformed_request=5;
- # enforce minimum argument length
- [[ $# -lt 2 ]] && return $invalid_arg;
- # assert uri value
- [[ -z "$2" ]] && return $empty_uri;
- local buffer;
- case $1 in
- scheme)
- # assert we have a scheme
- [[ "$2" =~ ^.+'://' ]] || return $missing_component;
- buffer=${2%://*}; # remove anything unrelated to scheme
- ;;
- host)
- buffer=${2#*//}; # remove scheme
- buffer=${buffer%%/*}; # remove path components
- buffer=${buffer%%:*}; # remove a possible port number
- # assert we have a host
- [[ -z "$buffer" ]] && return $missing_component;
- ;;
- port)
- buffer=${2#*://}; # remove scheme
- buffer=${buffer%%/*}; # remove path components
- # assert we have a port
- [[ "$buffer" =~ ^.+: ]] || return $missing_component;
- buffer=${buffer##*:}; # remove host components
- ;;
- path)
- buffer=${2#*//}; # remove scheme
- buffer=${buffer%%\?*} # remove query
- #make default path if there is no path component ie: host.com -> host.com/
- [[ "$buffer" =~ / ]] || { buffer=$buffer/; }
- buffer=/${buffer#*/} # remove host components, restore root slash;
- ;;
- request)
- buffer=${2#*//}; # remove scheme
- #make default path if there is no path component ie: host.com -> host.com/
- [[ "$buffer" =~ / ]] || { buffer=$buffer/; }
- buffer=/${buffer#*/} # remove host components, restore root slash;
- ;;
- query)
- # assert we have a query
- [[ "$buffer" =~ ^.+'?' ]] || return $missing_component;
- buffer=${2##*\?} # remove all prior components
- ;;
- param)
- # in the case of param we require 3 minimum arguments
- [[ $# -lt 3 ]] && return $invalid_arg;
- # if variable assignment is desired it is now the fourth argument
- # which means we must do assignment here and return early later on
- buffer=${2##*\?}; # remove all non query components
- # assert we have a query
- [[ -z "$buffer" ]] && return $missing_component;
- # assert the param is present
- [[ "$buffer" =~ "$3=" ]] || return $missing_param;
- buffer=${buffer##$3=}; # remove the identifier and value operator
- buffer=${buffer%%&*}; # remove remaining params if any
- [[ $# -gt 3 ]] && { # perform assignment
- eval "$4='$buffer'";
- return;
- }
- echo "$buffer";
- return;
- ;;
- file)
- buffer=${2%%\?*}; # remove query
- buffer=${buffer##*/}; # remove path stuff
- # assert we have a file or directory
- [[ -z "$buffer" ]] && return $missing_component;
- ;;
- *)
- return $malformed_request;
- ;;
- esac
- [[ $# -eq 3 ]] && {
- eval "$3='$buffer'";
- } || {
- echo "$buffer";
- }
- }
- function NewHttpRequest() {
- # $1 script label
- # $2 http uri
- [[ $1 =~ ^this || $1 =~ ^_ ]] && {
- echo "error: NewHttpRequest $1: 'this*' and '_*' are reserved api labels" >&2;
- return 1;
- }
- local this=$1 this_uri="$2"
- local this_scheme="`http.parse.uri scheme "${this_uri}"`";
- [[ $this_scheme == http || $this_scheme == "" ]] || {
- echo "error: NewHttpRequest $this: invalid uri: scheme is not 'http'" >&2;
- return 1;
- }
- declare -gA \
- $this[response.header]="" $this[response.body]="" \
- $this[socket]="" $this[socket.descriptor]="" \
- $this[uri]="${this_uri}" $this[version]='1.1' $this[name]="$this";
- # this[0] is reserved for outbound request header.
- local this_source="$(
- # in the gedit IDE under 'sh' syntax this code block appears as a quoted string.
- # the comment below is a workaround for that.
- # syntax.token != " - pc.wiz.tt@gmail.com discovered this here: November 29th 2012
- # Do note however that the above workaround must be repeated at the end of this
- # subshell to satisfy the pairing algorithm of the syntax parser.
- # We are actually at the root of a new subshell. Hence Adjusted block indentation.
- # Any output that escapes this subshell will be inherited by this object's source code.
- # Define some utility functions for generating script code.
- quote() {
- local this=$1; shift;
- printf %s "${this}$@${this}";
- }
- quotedString() {
- quote \" "$@";
- }
- quotedLiteral() {
- quote \' "$@";
- }
- thisProperty() {
- # we advantage the fact that 'this' is defined in the constructor.
- local _property="$1"
- printf %s '${'"$this"'['"${_property}"']}';
- }
- # the above functions will die with this subshell..
- # Define some shortcuts for generating script code.
- declare functionArguments='"$@"';
- # the above shortcuts will die with this subshell..
- # Object properties
- printf %s "
- function $this() {
- HttpRequest.dualAccessor $this 0 $functionArguments;
- }
- function $this.version() {
- HttpRequest.dualAccessor $this version $functionArguments;
- }
- function $this.uri() {
- HttpRequest.dualAccessor $this uri $functionArguments;
- }
- function $this.method() {
- [[ -z `quotedString $(thisProperty method)` ]] && {
- HttpRequest.head $this || return;
- }
- echo `quotedString $(thisProperty method)`;
- }
- function $this.uri.file() {
- http.parse.uri file `quotedString $(thisProperty uri)`;
- }
- function $this.uri.port() {
- http.parse.uri port `quotedString $(thisProperty uri)`;
- }
- function $this.uri.host() {
- http.parse.uri host `quotedString $(thisProperty uri)`;
- }
- function $this.uri.path() {
- http.parse.uri path `quotedString $(thisProperty uri)`;
- }
- function $this.uri.request() {
- http.parse.uri request `quotedString $(thisProperty uri)`;
- }
- function $this.uri.query() {
- http.parse.uri query `quotedString $(thisProperty uri)`;
- }
- function $this.uri.scheme() {
- http.parse.uri scheme `quotedString $(thisProperty uri)`;
- }
- function $this.socket() {
- echo `quotedString $(thisProperty socket)`;
- }
- function $this.socket.descriptor() {
- echo `quotedString $(thisProperty socket.descriptor)`;
- }
- function $this.response.header() {
- [[ -z `quotedString $(thisProperty response.header)` ]] && {
- HttpRequest.submit $this || return;
- }
- echo -n `quotedString $(thisProperty response.header)`;
- }
- function $this.response.body() {
- [[ -z `quotedString $(thisProperty response.body)` ]] && {
- HttpRequest.response.readBody $this || return;
- }
- echo -n `quotedString $(thisProperty response.body)`;
- }
- function $this.response.body.filter() {
- HttpRequest.dualAccessor $this response.body.filter $functionArguments;
- }
- function $this.response.status() {
- [[ -z `quotedString $(thisProperty response.status)` ]] && {
- HttpRequest.submit $this || {
- return;
- }
- }
- echo `quotedString $(thisProperty response.status)`;
- }
- "; # end of Object properties
- # Object Methods, linked to class functions
- printf %s "
- function $this.connect() {
- HttpRequest.connect $this;
- }
- function $this.connected() {
- HttpRequest.connected $this;
- }
- function $this.disconnect() {
- HttpRequest.disconnect $this;
- }
- function $this.dispose() {
- HttpRequest.dispose $this;
- }
- function $this.get() {
- HttpRequest.get $this $functionArguments;
- }
- function $this.post() {
- HttpRequest.post $this $functionArguments;
- }
- function $this.head() {
- HttpRequest.head $this $functionArguments;
- }
- function $this.submit() {
- HttpRequest.submit $this;
- }
- function $this.reset() {
- $this.dispose;
- NewHttpRequest $this '$2';
- }
- function $this.response.readBody() {
- HttpRequest.response.readBody $this;
- }
- function $this.response.downloadBody() {
- HttpRequest.response.downloadBody $this;
- }
- "; # end of Object Methods, linked to class functions
- printf %s "HttpRequest.head $this;";
- )"; # syntax.token != " - as per pc.wiz.tt@gmail.com
- source <(echo "$this_source");
- }
- HttpRequest.dispose() {
- local this=$1;
- HttpRequest.disconnect $this;
- unset -f $this.{connect,disconnect,connected,dispose,reset,socket,response,uri} \
- $this.{get,head,post,submit,version,method} \
- $this.response.{readBody,downloadBody,body,header,status,body.filter} \
- $this.uri.{file,port,host,scheme,path,query,request} \
- $this.socket.descriptor $this \
- ;
- unset $this;
- }
- HttpRequest.dualAccessor() {
- local this="$1" this_property="$1[$2]" this_operator="$3" this_value="$4";
- (( $# > 2 )) && {
- [[ "${this_operator}" == '=' ]] && {
- declare -gA ${this_property}${this_operator}"${this_value}";
- }
- [[ "${this_operator}" == '+=' ]] && {
- declare -gA \
- ${this_property}${this_operator}"${!this_property}${this_value}";
- }
- return;
- }
- echo "${!this_property}";
- }
- HttpRequest.connect() {
- local this=$1;
- HttpRequest.connected $this && return;
- local -i this_descriptor=20;
- local this_port=`$this.uri.port`;
- local this_socket="/dev/tcp/`$this.uri.host`/${this_port:-80}";
- while [[ -e /dev/fd/$((++this_descriptor)) ]]; do :; done;
- declare -gA $this[socket]="${this_socket}" \
- $this[socket.descriptor]="${this_descriptor}";
- eval "exec ${this_descriptor}<>${this_socket}";
- }
- HttpRequest.connected() {
- local this=$1;
- local this_descriptor=$this[socket.descriptor];
- [[ -n ${!this_descriptor} && -e /dev/fd/${!this_descriptor} ]]
- }
- HttpRequest.assertConnected() {
- local this=$1;
- HttpRequest.connected $this || {
- echo "error: HttpRequest.assertConnected->$this: assertion failure" >&2;
- return 1;
- }
- }
- HttpRequest.disconnect() {
- local this=$1;
- HttpRequest.connected $this && {
- local this_descriptor=$this[socket.descriptor];
- eval "exec ${!this_descriptor}>&-; declare -gA $this[socket.descriptor]='';";
- }
- }
- HttpRequest.method() {
- # 1: this, 2: method, ...;
- local this=$1 this_method="$2" _field='';
- shift 2; # discard 'this' argument and method,
- # remaining arguments are header fields.
- (( $# )) || {
- local this_port=`$this.uri.port`;
- local this_host_port=`$this.uri.host`;
- [[ -n $this_port ]] && this_host_port+=:$this_port;
- set -- \
- "Host: $this_host_port" \
- "User-Agent: HttpToolkit/1.1 (Bashful; `uname -o`)" \
- "Accept: */*" \
- "Accept-Encoding: gzip, deflate" \
- "DNT: 1" \
- "Connection: close"
- }
- local this_version=$this[version] _lf=$'\n';
- local this_header="${this_method} `$this.uri.request` ";
- this_header+="HTTP/${!this_version}${_lf}";
- for _field; do this_header+="${_field}${_lf}"; done;
- # we'll add this later.. this_header+="${_lf}";
- declare -gA $this[0]="${this_header}" $this[method]="${this_method}";
- }
- HttpRequest.get() {
- local this=$1; shift
- HttpRequest.method $this GET "$@";
- }
- HttpRequest.head() {
- local this=$1; shift;
- HttpRequest.method $this HEAD "$@";
- }
- HttpRequest.post() {
- local this=$1; shift;
- HttpRequest.method $this POST "$@";
- }
- HttpRequest.submit() {
- local this=$1;
- local this_descriptor=$this[socket.descriptor] \
- this_response=$this[response.header];
- local this_header="$this[0]" this_method=$this[method] \
- this_status=$this[response.status] _x='' _y='';
- [[ -z ${!this_response} ]] || {
- return;
- }
- [[ -z ${!this_method} ]] && {
- HttpRequest.head $this || return;
- }
- HttpRequest.connected $this && {
- return 1;
- }
- HttpRequest.connect $this || {
- return;
- }
- echo -n "${!this_header//$'\n'/$'\r\n'}"$'\r\n' >&${!this_descriptor};
- [[ ${!this_method} == POST ]] && cat >&${!this_descriptor};
- while read -u ${!this_descriptor} _x; do _x="${_x//$'\r'/}";
- [[ -z "$_x" ]] && break
- _y+="$_x"$'\n';
- done
- [[ "${_y}" =~ ^HTTP.+' '100' 'Continue$'\n'$ ]] && {
- _y='';
- while read -u ${!this_descriptor} _x; do _x="${_x//$'\r'/}";
- [[ -z "$_x" ]] && break
- _y+="$_x"$'\n';
- done
- }
- declare -gA ${this_response}="${_y}"$'\n';
- declare -gA ${this_status}="`echo "${_y}" | head -n 1`";
- [[ ${!this_method} == HEAD ]] && HttpRequest.disconnect $this;
- true;
- }
- HttpRequest.response.validateBodyRequest() {
- local this=$1;
- local this_method=$this[method] this_guest="$2";
- [[ -z ${!this_method} || ${!this_method} == HEAD ]] && {
- # we can't fulfill this request because
- # the method is HEAD, and head requests don't respond with a body.
- echo "error: ${this_guest}->$this: method has no body expected" >&2;
- return 1;
- }
- true;
- }
- HttpRequest.response.downloadBodyChunked() {
- local this=$1 this_val='' this_filter="cat";
- local this_descriptor=${this}[socket.descriptor];
- local this_header=$this[response.header];
- local -i this_val_num=0;
- $this.response.header | grep "Content-Encoding" | grep -q gzip && {
- this_filter=funzip;
- }
- { while read -u ${!this_descriptor} this_val;
- do
- this_val="${this_val//$'\r'/}";
- [[ $this_val == "" ]] && break;
- [[ $this_val =~ .+[^\;]: ]] && {
- declare -gA ${this_header}="$(
- $this.response.header | head -n -1)$this_val"$'\n\n';
- continue;
- }
- this_val_num="`printf %i 0x${this_val%%;*} 2>&-`";
- (( this_val_num )) || continue;
- let this_val_num+=2;
- head -c$this_val_num <&${!this_descriptor};
- done
- } | $this_filter;
- }
- HttpRequest.response.downloadBody() {
- local this=$1;
- HttpRequest.response.validateBodyRequest \
- $this $FUNCNAME || return;
- local this_descriptor=$this[socket.descriptor] this_method=$this[method] \
- this_response=$this[response.header] this_body=$this[response.body];
- [[ -z ${!this_response} ]] && {
- HttpRequest.submit $this || return;
- }
- [[ -z ${!this_body} ]] || {
- echo "error: $FUNCNAME->$this: response body already retrieved" >&2;
- return 1;
- }
- HttpRequest.assertConnected $this || return;
- local this_filter="`$this.response.body.filter`";
- (#|
- $this.response.header | grep "Transfer-Encoding" | grep -q chunked && {
- HttpRequest.response.downloadBodyChunked $this;
- } || cat <&${!this_descriptor};
- ) | ${this_filter:-cat};
- [[ "`$this`" =~ "Connection: close" ]] && HttpRequest.disconnect $this;
- }
- HttpRequest.response.readBody() {
- local this=$1;
- HttpRequest.response.validateBodyRequest \
- $this $FUNCNAME || return;
- declare -gA ${this}[response.body]="$(HttpRequest.response.downloadBody $this)";
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement