Advertisement
bash-masters

httpToolkit

Nov 28th, 2012
648
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 14.89 KB | None | 0 0
  1. # HTTP 1.0 Specification: http://www.w3.org/Protocols/HTTP/1.0/spec.html
  2. # HTTP 1.1 Specification: http://www.w3.org/Protocols/rfc2616/rfc2616.html
  3.  
  4. hash tr funzip cat head grep;
  5.  
  6. http.utils.stripcr() {
  7.     tr -d $'\r';
  8. }
  9.  
  10. http.parse.uri() {
  11.  
  12.     # $1 = scheme | host | port | path | query | param | file | request
  13.     # $2 = URI
  14.     # $3 variable label to write result if not present the result is echoed to stdout.
  15.  
  16.     # param has its own convention: $3 = param name; $4 = variable label
  17.  
  18.     local -i \
  19.     invalid_arg=1 empty_uri=2 missing_component=3 missing_param=4 malformed_request=5;
  20.  
  21.     # enforce minimum argument length
  22.     [[ $# -lt 2 ]] && return $invalid_arg;
  23.  
  24.     # assert uri value
  25.     [[ -z "$2" ]] && return $empty_uri;
  26.  
  27.     local buffer;
  28.  
  29.     case $1 in
  30.         scheme)
  31.  
  32.             # assert we have a scheme
  33.             [[ "$2" =~ ^.+'://' ]] || return $missing_component;
  34.  
  35.             buffer=${2%://*};    # remove anything unrelated to scheme
  36.  
  37.             ;;
  38.  
  39.         host)
  40.  
  41.             buffer=${2#*//};      # remove scheme
  42.             buffer=${buffer%%/*}; # remove path components
  43.             buffer=${buffer%%:*}; # remove a possible port number
  44.  
  45.             # assert we have a host
  46.             [[ -z "$buffer" ]] && return $missing_component;
  47.  
  48.             ;;
  49.  
  50.         port)
  51.  
  52.             buffer=${2#*://};     # remove scheme
  53.             buffer=${buffer%%/*}; # remove path components
  54.  
  55.             # assert we have a port
  56.             [[ "$buffer" =~ ^.+: ]] || return $missing_component;
  57.  
  58.             buffer=${buffer##*:}; # remove host components
  59.  
  60.             ;;
  61.  
  62.         path)
  63.  
  64.             buffer=${2#*//};      # remove scheme
  65.             buffer=${buffer%%\?*} # remove query
  66.  
  67.             #make default path if there is no path component ie: host.com -> host.com/
  68.             [[ "$buffer" =~ / ]] || { buffer=$buffer/; }
  69.  
  70.             buffer=/${buffer#*/}  # remove host components, restore root slash;
  71.  
  72.             ;;
  73.  
  74.         request)
  75.  
  76.             buffer=${2#*//};      # remove scheme
  77.  
  78.             #make default path if there is no path component ie: host.com -> host.com/
  79.             [[ "$buffer" =~ / ]] || { buffer=$buffer/; }
  80.  
  81.             buffer=/${buffer#*/}  # remove host components, restore root slash;
  82.  
  83.             ;;
  84.  
  85.         query)
  86.  
  87.             # assert we have a query
  88.             [[ "$buffer" =~ ^.+'?' ]] || return $missing_component;
  89.  
  90.             buffer=${2##*\?}      # remove all prior components
  91.             ;;
  92.  
  93.         param)
  94.  
  95.             # in the case of param we require 3 minimum arguments
  96.             [[ $# -lt 3 ]] && return $invalid_arg;
  97.  
  98.             # if variable assignment is desired it is now the fourth argument
  99.             # which means we must do assignment here and return early later on
  100.  
  101.             buffer=${2##*\?};    # remove all non query components
  102.            
  103.             # assert we have a query
  104.             [[ -z "$buffer" ]] && return $missing_component;
  105.  
  106.             # assert the param is present
  107.             [[ "$buffer" =~ "$3=" ]] || return $missing_param;
  108.  
  109.             buffer=${buffer##$3=}; # remove the identifier and value operator
  110.             buffer=${buffer%%&*};  # remove remaining params if any
  111.  
  112.             [[ $# -gt 3 ]] && {    # perform assignment
  113.                 eval "$4='$buffer'";
  114.                 return;
  115.             }
  116.  
  117.             echo "$buffer";
  118.             return;
  119.  
  120.             ;;
  121.  
  122.         file)
  123.  
  124.             buffer=${2%%\?*};      # remove query
  125.             buffer=${buffer##*/};  # remove path stuff
  126.  
  127.             # assert we have a file or directory
  128.             [[ -z "$buffer" ]] && return $missing_component;
  129.  
  130.             ;;
  131.  
  132.         *)
  133.             return $malformed_request;
  134.             ;;
  135.     esac
  136.    
  137.     [[ $# -eq 3 ]] && {
  138.         eval "$3='$buffer'";
  139.     } || {
  140.         echo "$buffer";
  141.     }
  142.  
  143. }
  144.  
  145. function NewHttpRequest() {
  146.  
  147.     # $1 script label
  148.     # $2 http uri
  149.  
  150.     [[ $1 =~ ^this || $1 =~ ^_ ]] && {
  151.         echo "error: NewHttpRequest $1: 'this*' and '_*' are reserved api labels" >&2;
  152.         return 1;
  153.     }
  154.  
  155.     local this=$1 this_uri="$2"
  156.     local this_scheme="`http.parse.uri scheme "${this_uri}"`";
  157.  
  158.     [[ $this_scheme == http || $this_scheme == "" ]] || {
  159.         echo "error: NewHttpRequest $this: invalid uri: scheme is not 'http'" >&2;
  160.         return 1;
  161.     }
  162.  
  163.     declare -gA \
  164.     $this[response.header]="" $this[response.body]="" \
  165.     $this[socket]="" $this[socket.descriptor]="" \
  166.     $this[uri]="${this_uri}" $this[version]='1.1' $this[name]="$this";
  167.  
  168.     # this[0] is reserved for outbound request header.
  169.  
  170. local this_source="$(
  171.  
  172. # in the gedit IDE under 'sh' syntax this code block appears as a quoted string.
  173. # the comment below is a workaround for that.
  174.  
  175. # syntax.token != " - pc.wiz.tt@gmail.com discovered this here: November 29th 2012
  176.  
  177. # Do note however that the above workaround must be repeated at the end of this
  178. # subshell to satisfy the pairing algorithm of the syntax parser.
  179.  
  180. # We are actually at the root of a new subshell. Hence Adjusted block indentation.
  181.  
  182. # Any output that escapes this subshell will be inherited by this object's source code.
  183.  
  184. # Define some utility functions for generating script code.
  185.  
  186. quote() {
  187.     local this=$1; shift;
  188.     printf %s "${this}$@${this}";
  189. }
  190.  
  191. quotedString() {
  192.     quote \" "$@";
  193. }
  194.  
  195. quotedLiteral() {
  196.     quote \' "$@";
  197. }
  198.  
  199. thisProperty() {
  200.     # we advantage the fact that 'this' is defined in the constructor.
  201.     local _property="$1"
  202.     printf %s '${'"$this"'['"${_property}"']}';
  203. }
  204.  
  205. # the above functions will die with this subshell..
  206.  
  207. # Define some shortcuts for generating script code.
  208.  
  209. declare functionArguments='"$@"';
  210.  
  211. # the above shortcuts will die with this subshell..
  212.  
  213. # Object properties
  214.  
  215. printf %s "
  216. function $this() {
  217.     HttpRequest.dualAccessor $this 0 $functionArguments;
  218. }
  219. function $this.version() {
  220.     HttpRequest.dualAccessor $this version $functionArguments;
  221. }
  222. function $this.uri() {
  223.     HttpRequest.dualAccessor $this uri $functionArguments;
  224. }
  225. function $this.method() {
  226.     [[ -z `quotedString $(thisProperty method)` ]] && {
  227.         HttpRequest.head $this || return;
  228.     }
  229.     echo `quotedString $(thisProperty method)`;
  230. }
  231. function $this.uri.file() {
  232.     http.parse.uri file `quotedString $(thisProperty uri)`;
  233. }
  234. function $this.uri.port() {
  235.     http.parse.uri port `quotedString $(thisProperty uri)`;
  236. }
  237. function $this.uri.host() {
  238.     http.parse.uri host `quotedString $(thisProperty uri)`;
  239. }
  240. function $this.uri.path() {
  241.     http.parse.uri path `quotedString $(thisProperty uri)`;
  242. }
  243. function $this.uri.request() {
  244.     http.parse.uri request `quotedString $(thisProperty uri)`
  245. }
  246. function $this.uri.query() {
  247.     http.parse.uri query `quotedString $(thisProperty uri)`;   
  248. }
  249. function $this.uri.scheme() {
  250.     http.parse.uri scheme `quotedString $(thisProperty uri)`;  
  251. }
  252. function $this.socket() {
  253.     echo `quotedString $(thisProperty socket)`;
  254. }
  255. function $this.socket.descriptor() {
  256.     echo `quotedString $(thisProperty socket.descriptor)`;
  257. }
  258. function $this.response.header() {
  259.     [[ -z `quotedString $(thisProperty response.header)` ]] && {
  260.         HttpRequest.submit $this || return;
  261.     }
  262.     echo -n `quotedString $(thisProperty response.header)`;
  263. }
  264. function $this.response.body() {
  265.     [[ -z `quotedString $(thisProperty response.body)` ]] && {
  266.         HttpRequest.response.readBody $this || return;
  267.     }
  268.     echo -n `quotedString $(thisProperty response.body)`;  
  269. }
  270. function $this.response.body.filter() {
  271.     HttpRequest.dualAccessor $this response.body.filter $functionArguments;
  272. }
  273. function $this.response.status() {
  274.     [[ -z `quotedString $(thisProperty response.status)` ]] && {
  275.         HttpRequest.submit $this || {
  276.             return;
  277.         }
  278.     }
  279.     echo `quotedString $(thisProperty response.status)`;
  280. }
  281. "; # end of Object properties
  282.  
  283. # Object Methods, linked to class functions
  284.  
  285. printf %s "
  286. function $this.connect() {
  287.     HttpRequest.connect $this;
  288. }
  289. function $this.connected() {
  290.     HttpRequest.connected $this;
  291. }
  292. function $this.disconnect() {
  293.     HttpRequest.disconnect $this;
  294. }
  295. function $this.dispose() {
  296.     HttpRequest.dispose $this;
  297. }
  298. function $this.get() {
  299.     HttpRequest.get $this $functionArguments;
  300. }
  301. function $this.post() {
  302.     HttpRequest.post $this $functionArguments;
  303. }
  304. function $this.head() {
  305.     HttpRequest.head $this $functionArguments;
  306. }
  307. function $this.submit() {
  308.     HttpRequest.submit $this;
  309. }
  310. function $this.reset() {
  311.     $this.dispose;
  312.     NewHttpRequest $this '$2';
  313. }
  314. function $this.response.readBody() {
  315.     HttpRequest.response.readBody $this;
  316. }
  317. function $this.response.downloadBody() {
  318.     HttpRequest.response.downloadBody $this;
  319. }
  320. "; # end of Object Methods, linked to class functions
  321.  
  322. printf %s "HttpRequest.head $this;";
  323.  
  324. )"; # syntax.token != " - as per pc.wiz.tt@gmail.com
  325.  
  326. source <(echo "$this_source");
  327.  
  328. }
  329.  
  330. HttpRequest.dispose() {
  331.     local this=$1;
  332.     HttpRequest.disconnect $this;
  333.     unset -f $this.{connect,disconnect,connected,dispose,reset,socket,response,uri} \
  334.         $this.{get,head,post,submit,version,method} \
  335.         $this.response.{readBody,downloadBody,body,header,status,body.filter} \
  336.         $this.uri.{file,port,host,scheme,path,query,request} \
  337.         $this.socket.descriptor $this \
  338.     ;
  339.     unset $this;
  340. }
  341.  
  342. HttpRequest.dualAccessor() {
  343.  
  344.     local this="$1" this_property="$1[$2]" this_operator="$3" this_value="$4";
  345.  
  346.     (( $# > 2 )) && {
  347.         [[ "${this_operator}" == '=' ]] && {
  348.             declare -gA ${this_property}${this_operator}"${this_value}";
  349.         }
  350.         [[ "${this_operator}" == '+=' ]] && {
  351.             declare -gA \
  352.             ${this_property}${this_operator}"${!this_property}${this_value}";
  353.         }
  354.         return;
  355.     }
  356.  
  357.     echo "${!this_property}";
  358.  
  359. }
  360.  
  361. HttpRequest.connect() {
  362.  
  363.     local this=$1;
  364.  
  365.     HttpRequest.connected $this && return;
  366.  
  367.     local -i this_descriptor=20;
  368.     local this_port=`$this.uri.port`;
  369.     local this_socket="/dev/tcp/`$this.uri.host`/${this_port:-80}";
  370.  
  371.     while [[ -e /dev/fd/$((++this_descriptor)) ]]; do :; done;
  372.  
  373.     declare -gA $this[socket]="${this_socket}" \
  374.     $this[socket.descriptor]="${this_descriptor}";
  375.  
  376.     eval "exec ${this_descriptor}<>${this_socket}";
  377.  
  378. }
  379.  
  380. HttpRequest.connected() {
  381.     local this=$1;
  382.     local this_descriptor=$this[socket.descriptor];
  383.     [[ -n ${!this_descriptor} && -e /dev/fd/${!this_descriptor} ]]
  384. }
  385.  
  386. HttpRequest.assertConnected() {
  387.     local this=$1;
  388.     HttpRequest.connected $this || {
  389.         echo "error: HttpRequest.assertConnected->$this: assertion failure" >&2;
  390.         return 1;
  391.     }
  392. }
  393.  
  394. HttpRequest.disconnect() {
  395.     local this=$1;
  396.     HttpRequest.connected $this && {
  397.         local this_descriptor=$this[socket.descriptor];
  398.         eval "exec ${!this_descriptor}>&-; declare -gA $this[socket.descriptor]='';";
  399.     }
  400. }
  401.  
  402. HttpRequest.method() {
  403.  
  404.     # 1: this, 2: method, ...;
  405.  
  406.     local this=$1 this_method="$2" _field='';
  407.  
  408.     shift 2; # discard 'this' argument and method,
  409.     # remaining arguments are header fields.
  410.  
  411.     (( $# )) || {
  412.         local this_port=`$this.uri.port`;
  413.         local this_host_port=`$this.uri.host`
  414.         [[ -n $this_port ]] && this_host_port+=:$this_port;
  415.         set -- \
  416.         "Host: $this_host_port" \
  417.         "User-Agent: HttpToolkit/1.1 (Bashful; `uname -o`)" \
  418.         "Accept: */*" \
  419.         "Accept-Encoding: gzip, deflate" \
  420.         "DNT: 1" \
  421.         "Connection: close"
  422.     }
  423.  
  424.     local this_version=$this[version] _lf=$'\n';
  425.  
  426.     local this_header="${this_method} `$this.uri.request` ";
  427.     this_header+="HTTP/${!this_version}${_lf}";
  428.  
  429.     for _field; do this_header+="${_field}${_lf}"; done;
  430.  
  431.     # we'll add this later.. this_header+="${_lf}";
  432.  
  433.     declare -gA $this[0]="${this_header}" $this[method]="${this_method}";
  434.  
  435. }
  436.  
  437. HttpRequest.get() {
  438.     local this=$1; shift
  439.     HttpRequest.method $this GET "$@";
  440. }
  441.  
  442. HttpRequest.head() {
  443.     local this=$1; shift;
  444.     HttpRequest.method $this HEAD "$@";
  445. }
  446.  
  447. HttpRequest.post() {
  448.     local this=$1; shift;
  449.     HttpRequest.method $this POST "$@";
  450. }
  451.  
  452. HttpRequest.submit() {
  453.  
  454.     local this=$1;
  455.  
  456.     local this_descriptor=$this[socket.descriptor] \
  457.     this_response=$this[response.header];
  458.  
  459.     local this_header="$this[0]" this_method=$this[method] \
  460.     this_status=$this[response.status] _x='' _y='';
  461.  
  462.     [[ -z ${!this_response} ]] || {
  463.         return;
  464.     }
  465.  
  466.     [[ -z ${!this_method} ]] && {
  467.         HttpRequest.head $this || return;
  468.     }
  469.  
  470.     HttpRequest.connected $this && {
  471.         return 1;
  472.     }
  473.  
  474.     HttpRequest.connect $this || {
  475.         return;
  476.     }
  477.  
  478.     echo -n "${!this_header//$'\n'/$'\r\n'}"$'\r\n' >&${!this_descriptor};
  479.  
  480.     [[ ${!this_method} == POST ]] && cat >&${!this_descriptor};
  481.  
  482.     while read -u ${!this_descriptor} _x; do _x="${_x//$'\r'/}";
  483.         [[ -z "$_x" ]] && break
  484.         _y+="$_x"$'\n';
  485.     done
  486.  
  487.     [[ "${_y}" =~ ^HTTP.+' '100' 'Continue$'\n'$ ]] && {
  488.         _y='';
  489.         while read -u ${!this_descriptor} _x; do _x="${_x//$'\r'/}";
  490.             [[ -z "$_x" ]] && break
  491.             _y+="$_x"$'\n';
  492.         done
  493.     }
  494.  
  495.     declare -gA ${this_response}="${_y}"$'\n';
  496.  
  497.     declare -gA ${this_status}="`echo "${_y}" | head -n 1`";
  498.  
  499.     [[ ${!this_method} == HEAD ]] && HttpRequest.disconnect $this;
  500.  
  501.     true;
  502.  
  503. }
  504.  
  505. HttpRequest.response.validateBodyRequest() {
  506.     local this=$1;
  507.     local this_method=$this[method] this_guest="$2";
  508.     [[ -z ${!this_method} || ${!this_method} == HEAD ]] && {
  509.         # we can't fulfill this request because
  510.         # the method is HEAD, and head requests don't respond with a body.
  511.         echo "error: ${this_guest}->$this: method has no body expected" >&2;
  512.         return 1;
  513.     }
  514.     true;
  515. }
  516.  
  517. HttpRequest.response.downloadBodyChunked() {
  518.  
  519.     local this=$1 this_val='' this_filter="cat";
  520.     local this_descriptor=${this}[socket.descriptor];
  521.     local this_header=$this[response.header];
  522.     local -i this_val_num=0;
  523.  
  524.     $this.response.header | grep "Content-Encoding" | grep -q gzip && {
  525.         this_filter=funzip;
  526.     }
  527.  
  528.     { while read -u ${!this_descriptor} this_val;
  529.     do
  530.         this_val="${this_val//$'\r'/}";
  531.         [[ $this_val == "" ]] && break;
  532.         [[ $this_val  =~ .+[^\;]: ]] && {
  533.             declare -gA ${this_header}="$(
  534.             $this.response.header | head -n -1)$this_val"$'\n\n';
  535.             continue;
  536.         }
  537.         this_val_num="`printf %i 0x${this_val%%;*} 2>&-`";
  538.         (( this_val_num )) || continue;
  539.         let this_val_num+=2;
  540.         head -c$this_val_num <&${!this_descriptor};
  541.     done
  542.     } | $this_filter;
  543.  
  544. }
  545.  
  546. HttpRequest.response.downloadBody() {
  547.  
  548.     local this=$1;
  549.  
  550.     HttpRequest.response.validateBodyRequest \
  551.     $this $FUNCNAME || return;
  552.  
  553.     local this_descriptor=$this[socket.descriptor] this_method=$this[method] \
  554.     this_response=$this[response.header] this_body=$this[response.body];
  555.  
  556.     [[ -z ${!this_response} ]] && {
  557.         HttpRequest.submit $this || return;
  558.     }
  559.  
  560.     [[ -z ${!this_body} ]] || {
  561.         echo "error: $FUNCNAME->$this: response body already retrieved" >&2;
  562.         return 1;
  563.     }
  564.  
  565.     HttpRequest.assertConnected $this || return;
  566.  
  567.     local this_filter="`$this.response.body.filter`";
  568.  
  569.     (#|
  570.         $this.response.header | grep "Transfer-Encoding" | grep -q chunked && {
  571.             HttpRequest.response.downloadBodyChunked $this;
  572.         } || cat <&${!this_descriptor};
  573.  
  574.     ) | ${this_filter:-cat};
  575.  
  576.     [[ "`$this`" =~ "Connection: close" ]] && HttpRequest.disconnect $this;
  577.  
  578. }
  579.  
  580. HttpRequest.response.readBody() {
  581.  
  582.     local this=$1;
  583.  
  584.     HttpRequest.response.validateBodyRequest \
  585.     $this $FUNCNAME || return;
  586.  
  587.     declare -gA ${this}[response.body]="$(HttpRequest.response.downloadBody $this)";
  588.  
  589. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement