17c2fbfb3SApril Chin#!/usr/bin/ksh93 27c2fbfb3SApril Chin 37c2fbfb3SApril Chin# 47c2fbfb3SApril Chin# CDDL HEADER START 57c2fbfb3SApril Chin# 67c2fbfb3SApril Chin# The contents of this file are subject to the terms of the 77c2fbfb3SApril Chin# Common Development and Distribution License (the "License"). 87c2fbfb3SApril Chin# You may not use this file except in compliance with the License. 97c2fbfb3SApril Chin# 107c2fbfb3SApril Chin# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 117c2fbfb3SApril Chin# or http://www.opensolaris.org/os/licensing. 127c2fbfb3SApril Chin# See the License for the specific language governing permissions 137c2fbfb3SApril Chin# and limitations under the License. 147c2fbfb3SApril Chin# 157c2fbfb3SApril Chin# When distributing Covered Code, include this CDDL HEADER in each 167c2fbfb3SApril Chin# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 177c2fbfb3SApril Chin# If applicable, add the following below this CDDL HEADER, with the 187c2fbfb3SApril Chin# fields enclosed by brackets "[]" replaced with your own identifying 197c2fbfb3SApril Chin# information: Portions Copyright [yyyy] [name of copyright owner] 207c2fbfb3SApril Chin# 217c2fbfb3SApril Chin# CDDL HEADER END 227c2fbfb3SApril Chin# 237c2fbfb3SApril Chin 247c2fbfb3SApril Chin# 25*3e14f97fSRoger A. Faulkner# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. 267c2fbfb3SApril Chin# 277c2fbfb3SApril Chin 287c2fbfb3SApril Chin# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant 297c2fbfb3SApril Chinexport PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin 307c2fbfb3SApril Chin 317c2fbfb3SApril Chin# Make sure all math stuff runs in the "C" locale to avoid problems 327c2fbfb3SApril Chin# with alternative # radix point representations (e.g. ',' instead of 337c2fbfb3SApril Chin# '.' in de_DE.*-locales). This needs to be set _before_ any 347c2fbfb3SApril Chin# floating-point constants are defined in this script). 357c2fbfb3SApril Chinif [[ "${LC_ALL}" != "" ]] ; then 367c2fbfb3SApril Chin export \ 377c2fbfb3SApril Chin LC_MONETARY="${LC_ALL}" \ 387c2fbfb3SApril Chin LC_MESSAGES="${LC_ALL}" \ 397c2fbfb3SApril Chin LC_COLLATE="${LC_ALL}" \ 407c2fbfb3SApril Chin LC_CTYPE="${LC_ALL}" 417c2fbfb3SApril Chin unset LC_ALL 427c2fbfb3SApril Chinfi 437c2fbfb3SApril Chinexport LC_NUMERIC=C 447c2fbfb3SApril Chin 457c2fbfb3SApril Chinfunction fatal_error 467c2fbfb3SApril Chin{ 477c2fbfb3SApril Chin print -u2 "${progname}: $*" 487c2fbfb3SApril Chin exit 1 497c2fbfb3SApril Chin} 507c2fbfb3SApril Chin 517c2fbfb3SApril Chinfunction encode_x_www_form_urlencoded 527c2fbfb3SApril Chin{ 537c2fbfb3SApril Chin nameref formdata=$1 547c2fbfb3SApril Chin nameref content="formdata.content" 557c2fbfb3SApril Chin integer numformelements=${#formdata.form[*]} 567c2fbfb3SApril Chin integer i j 577c2fbfb3SApril Chin 587c2fbfb3SApril Chin content="" 597c2fbfb3SApril Chin 607c2fbfb3SApril Chin for (( i=0 ; i < numformelements ; i++ )) ; do 617c2fbfb3SApril Chin nameref element="formdata.form[${i}]" 627c2fbfb3SApril Chin typeset data="${element.data}" 637c2fbfb3SApril Chin integer datalen="${#data}" 647c2fbfb3SApril Chin typeset c 657c2fbfb3SApril Chin 667c2fbfb3SApril Chin [[ "$content" != "" ]] && content+="&" 677c2fbfb3SApril Chin 687c2fbfb3SApril Chin content+="${element.name}=" 697c2fbfb3SApril Chin 707c2fbfb3SApril Chin for ((j=0 ; j < datalen ; j++)) ; do 717c2fbfb3SApril Chin c="${data:j:1}" 727c2fbfb3SApril Chin case "$c" in 737c2fbfb3SApril Chin ' ') c="+" ;; 747c2fbfb3SApril Chin '!') c="%21" ;; 757c2fbfb3SApril Chin '*') c="%2A" ;; 767c2fbfb3SApril Chin "'") c="%27" ;; 777c2fbfb3SApril Chin '(') c="%28" ;; 787c2fbfb3SApril Chin ')') c="%29" ;; 797c2fbfb3SApril Chin ';') c="%3B" ;; 807c2fbfb3SApril Chin ':') c="%3A" ;; 817c2fbfb3SApril Chin '@') c="%40" ;; 827c2fbfb3SApril Chin '&') c="%26" ;; 837c2fbfb3SApril Chin '=') c="%3D" ;; 847c2fbfb3SApril Chin '+') c="%2B" ;; 857c2fbfb3SApril Chin '$') c="%24" ;; 867c2fbfb3SApril Chin ',') c="%2C" ;; 877c2fbfb3SApril Chin '/') c="%2F" ;; 887c2fbfb3SApril Chin '?') c="%3F" ;; 897c2fbfb3SApril Chin '%') c="%25" ;; 907c2fbfb3SApril Chin '#') c="%23" ;; 917c2fbfb3SApril Chin '[') c="%5B" ;; 927c2fbfb3SApril Chin '\') c="%5C" ;; # we need this to avoid the '\'-quoting hell 937c2fbfb3SApril Chin ']') c="%5D" ;; 947c2fbfb3SApril Chin *) ;; 957c2fbfb3SApril Chin esac 967c2fbfb3SApril Chin content+="$c" 977c2fbfb3SApril Chin done 987c2fbfb3SApril Chin done 997c2fbfb3SApril Chin 1007c2fbfb3SApril Chin formdata.content_length=${#content} 1017c2fbfb3SApril Chin 1027c2fbfb3SApril Chin return 0 1037c2fbfb3SApril Chin} 1047c2fbfb3SApril Chin 1057c2fbfb3SApril Chin# parse HTTP return code, cookies etc. 1067c2fbfb3SApril Chinfunction parse_http_response 1077c2fbfb3SApril Chin{ 1087c2fbfb3SApril Chin nameref response="$1" 1097c2fbfb3SApril Chin typeset h statuscode statusmsg i 1107c2fbfb3SApril Chin 1117c2fbfb3SApril Chin # we use '\r' as additional IFS to filter the final '\r' 1127c2fbfb3SApril Chin IFS=$' \t\r' read -r h statuscode statusmsg # read HTTP/1.[01] <code> 1137c2fbfb3SApril Chin [[ "$h" != ~(Eil)HTTP/.* ]] && { print -u2 -f $"%s: HTTP/ header missing\n" "$0" ; return 1 ; } 1147c2fbfb3SApril Chin [[ "$statuscode" != ~(Elr)[0-9]* ]] && { print -u2 -f $"%s: invalid status code\n" "$0" ; return 1 ; } 1157c2fbfb3SApril Chin response.statuscode="$statuscode" 1167c2fbfb3SApril Chin response.statusmsg="$statusmsg" 1177c2fbfb3SApril Chin 1187c2fbfb3SApril Chin # skip remaining headers 1197c2fbfb3SApril Chin while IFS='' read -r i ; do 1207c2fbfb3SApril Chin [[ "$i" == $'\r' ]] && break 1217c2fbfb3SApril Chin 1227c2fbfb3SApril Chin # strip '\r' at the end 1237c2fbfb3SApril Chin i="${i/~(Er)$'\r'/}" 1247c2fbfb3SApril Chin 1257c2fbfb3SApril Chin case "$i" in 1267c2fbfb3SApril Chin ~(Eli)Content-Type:.*) 1277c2fbfb3SApril Chin response.content_type="${i/~(El).*:[[:blank:]]*/}" 1287c2fbfb3SApril Chin ;; 1297c2fbfb3SApril Chin ~(Eli)Content-Length:[[:blank:]]*[0-9]*) 1307c2fbfb3SApril Chin integer response.content_length="${i/~(El).*:[[:blank:]]*/}" 1317c2fbfb3SApril Chin ;; 1327c2fbfb3SApril Chin ~(Eli)Transfer-Encoding:.*) 1337c2fbfb3SApril Chin response.transfer_encoding="${i/~(El).*:[[:blank:]]*/}" 1347c2fbfb3SApril Chin ;; 1357c2fbfb3SApril Chin esac 1367c2fbfb3SApril Chin done 1377c2fbfb3SApril Chin 1387c2fbfb3SApril Chin return 0 1397c2fbfb3SApril Chin} 1407c2fbfb3SApril Chin 1417c2fbfb3SApril Chinfunction cat_http_body 1427c2fbfb3SApril Chin{ 1437c2fbfb3SApril Chin typeset emode="$1" 1447c2fbfb3SApril Chin typeset hexchunksize="0" 1457c2fbfb3SApril Chin integer chunksize=0 1467c2fbfb3SApril Chin 1477c2fbfb3SApril Chin if [[ "${emode}" == "chunked" ]] ; then 1487c2fbfb3SApril Chin while IFS=$'\r' read hexchunksize && 149*3e14f97fSRoger A. Faulkner [[ "${hexchunksize}" == ~(Elri)[0-9abcdef]+ ]] && 150*3e14f97fSRoger A. Faulkner (( chunksize=$( printf "16#%s\n" "${hexchunksize}" ) )) && (( chunksize > 0 )) ; do 1517c2fbfb3SApril Chin dd bs=1 count="${chunksize}" 2>/dev/null 1527c2fbfb3SApril Chin done 1537c2fbfb3SApril Chin else 1547c2fbfb3SApril Chin cat 1557c2fbfb3SApril Chin fi 1567c2fbfb3SApril Chin 1577c2fbfb3SApril Chin return 0 1587c2fbfb3SApril Chin} 1597c2fbfb3SApril Chin 1607c2fbfb3SApril Chinfunction encode_http_basic_auth 1617c2fbfb3SApril Chin{ 1627c2fbfb3SApril Chin typeset user="$1" 1637c2fbfb3SApril Chin typeset passwd="$2" 1647c2fbfb3SApril Chin typeset s 1657c2fbfb3SApril Chin integer s_len 1667c2fbfb3SApril Chin typeset -b base64var 1677c2fbfb3SApril Chin 1687c2fbfb3SApril Chin # ksh93 binary variables use base64 encoding, the same as the 1697c2fbfb3SApril Chin # HTTP basic authentification. We only have to read the 1707c2fbfb3SApril Chin # plaintext user:passwd string into the binary variable "base64var" 1717c2fbfb3SApril Chin # and then print this variable as ASCII. 1727c2fbfb3SApril Chin s="${user}:${passwd}" 1737c2fbfb3SApril Chin s_len="${#s}" 1747c2fbfb3SApril Chin print -n "${s}" | read -N${s_len} base64var 1757c2fbfb3SApril Chin 1767c2fbfb3SApril Chin print -- "${base64var}" # print ASCII (base64) representation of binary var 1777c2fbfb3SApril Chin 1787c2fbfb3SApril Chin return 0 1797c2fbfb3SApril Chin} 1807c2fbfb3SApril Chin 1817c2fbfb3SApril Chinfunction put_twitter_message 1827c2fbfb3SApril Chin{ 1837c2fbfb3SApril Chin [[ "$SHTWITTER_USER" == "" ]] && { print -u2 -f $"%s: SHTWITTER_USER not set.\n" "$0" ; return 1 ; } 1847c2fbfb3SApril Chin [[ "$SHTWITTER_PASSWD" == "" ]] && { print -u2 -f $"%s: SHTWITTER_PASSWD not set.\n" "$0" ; return 1 ; } 1857c2fbfb3SApril Chin 1867c2fbfb3SApril Chin (( $# != 1 )) && { print -u2 -f $"%s: Wrong number of arguments.\n" "$0" ; return 1 ; } 1877c2fbfb3SApril Chin 1887c2fbfb3SApril Chin # site setup 1897c2fbfb3SApril Chin typeset url_host="twitter.com" 1907c2fbfb3SApril Chin typeset url_path="/statuses/update.xml" 1917c2fbfb3SApril Chin typeset url="http://${url_host}${url_path}" 1927c2fbfb3SApril Chin integer netfd # http stream number 1937c2fbfb3SApril Chin typeset msgtext="$1" 19434f9b3eeSRoland Mainz compound httpresponse # http response 1957c2fbfb3SApril Chin 1967c2fbfb3SApril Chin # argument for "encode_x_www_form_urlencoded" 19734f9b3eeSRoland Mainz compound urlform=( 1987c2fbfb3SApril Chin # input 19934f9b3eeSRoland Mainz compound -a form=( 20034f9b3eeSRoland Mainz ( name="status" data="${msgtext}" ) 20134f9b3eeSRoland Mainz ) 2027c2fbfb3SApril Chin # output 2037c2fbfb3SApril Chin typeset content 2047c2fbfb3SApril Chin integer content_length 2057c2fbfb3SApril Chin ) 2067c2fbfb3SApril Chin 2077c2fbfb3SApril Chin typeset request="" 2087c2fbfb3SApril Chin typeset content="" 2097c2fbfb3SApril Chin 2107c2fbfb3SApril Chin encode_x_www_form_urlencoded urlform 2117c2fbfb3SApril Chin 2127c2fbfb3SApril Chin content="${urlform.content}" 2137c2fbfb3SApril Chin 2147c2fbfb3SApril Chin request="POST ${url_path} HTTP/1.1\r\n" 2157c2fbfb3SApril Chin request+="Host: ${url_host}\r\n" 2167c2fbfb3SApril Chin request+="Authorization: Basic ${ encode_http_basic_auth "${SHTWITTER_USER}" "${SHTWITTER_PASSWD}" ; }\r\n" 2177c2fbfb3SApril Chin request+="User-Agent: ${http_user_agent}\r\n" 2187c2fbfb3SApril Chin request+="Connection: close\r\n" 2197c2fbfb3SApril Chin request+="Content-Type: application/x-www-form-urlencoded\r\n" 2207c2fbfb3SApril Chin request+="Content-Length: $(( urlform.content_length ))\r\n" 2217c2fbfb3SApril Chin 2227c2fbfb3SApril Chin redirect {netfd}<> "/dev/tcp/${url_host}/80" 2237c2fbfb3SApril Chin (( $? != 0 )) && { print -u2 -f "%s: Could not open connection to %s\n." "$0" "${url_host}" ; return 1 ; } 2247c2fbfb3SApril Chin 2257c2fbfb3SApril Chin # send http post 2267c2fbfb3SApril Chin { 2277c2fbfb3SApril Chin print -n -- "${request}\r\n" 2287c2fbfb3SApril Chin print -n -- "${content}\r\n" 2297c2fbfb3SApril Chin } >&${netfd} 2307c2fbfb3SApril Chin 2317c2fbfb3SApril Chin # process reply 2327c2fbfb3SApril Chin parse_http_response httpresponse <&${netfd} 2337c2fbfb3SApril Chin response="${ cat_http_body "${httpresponse.transfer_encoding}" <&${netfd} ; }" 2347c2fbfb3SApril Chin 2357c2fbfb3SApril Chin # close connection 2367c2fbfb3SApril Chin redirect {netfd}<&- 2377c2fbfb3SApril Chin 2387c2fbfb3SApril Chin printf $"twitter response was (%s,%s): %s\n" "${httpresponse.statuscode}" "${httpresponse.statusmsg}" "${response}" 2397c2fbfb3SApril Chin 2407c2fbfb3SApril Chin if (( httpresponse.statuscode >= 200 && httpresponse.statuscode <= 299 )) ; then 2417c2fbfb3SApril Chin return 0 2427c2fbfb3SApril Chin else 2437c2fbfb3SApril Chin return 1 2447c2fbfb3SApril Chin fi 2457c2fbfb3SApril Chin 2467c2fbfb3SApril Chin # not reached 2477c2fbfb3SApril Chin} 2487c2fbfb3SApril Chin 2497c2fbfb3SApril Chinfunction verify_twitter_credentials 2507c2fbfb3SApril Chin{ 2517c2fbfb3SApril Chin [[ "$SHTWITTER_USER" == "" ]] && { print -u2 -f $"%s: SHTWITTER_USER not set.\n" "$0" ; return 1 ; } 2527c2fbfb3SApril Chin [[ "$SHTWITTER_PASSWD" == "" ]] && { print -u2 -f $"%s: SHTWITTER_PASSWD not set.\n" "$0" ; return 1 ; } 2537c2fbfb3SApril Chin 2547c2fbfb3SApril Chin (( $# != 0 )) && { print -u2 -f $"%s: Wrong number of arguments.\n" "$0" ; return 1 ; } 2557c2fbfb3SApril Chin 2567c2fbfb3SApril Chin # site setup 2577c2fbfb3SApril Chin typeset url_host="twitter.com" 2587c2fbfb3SApril Chin typeset url_path="/account/verify_credentials.xml" 2597c2fbfb3SApril Chin typeset url="http://${url_host}${url_path}" 2607c2fbfb3SApril Chin integer netfd # http stream number 26134f9b3eeSRoland Mainz compound httpresponse # http response 2627c2fbfb3SApril Chin 2637c2fbfb3SApril Chin typeset request="" 2647c2fbfb3SApril Chin 2657c2fbfb3SApril Chin request="POST ${url_path} HTTP/1.1\r\n" 2667c2fbfb3SApril Chin request+="Host: ${url_host}\r\n" 2677c2fbfb3SApril Chin request+="Authorization: Basic ${ encode_http_basic_auth "${SHTWITTER_USER}" "${SHTWITTER_PASSWD}" ; }\r\n" 2687c2fbfb3SApril Chin request+="User-Agent: ${http_user_agent}\r\n" 2697c2fbfb3SApril Chin request+="Connection: close\r\n" 2707c2fbfb3SApril Chin request+="Content-Type: application/x-www-form-urlencoded\r\n" 2717c2fbfb3SApril Chin request+="Content-Length: 0\r\n" # dummy 2727c2fbfb3SApril Chin 2737c2fbfb3SApril Chin redirect {netfd}<> "/dev/tcp/${url_host}/80" 2747c2fbfb3SApril Chin (( $? != 0 )) && { print -u2 -f $"%s: Could not open connection to %s.\n" "$0" "${url_host}" ; return 1 ; } 2757c2fbfb3SApril Chin 2767c2fbfb3SApril Chin # send http post 2777c2fbfb3SApril Chin { 2787c2fbfb3SApril Chin print -n -- "${request}\r\n" 2797c2fbfb3SApril Chin } >&${netfd} 2807c2fbfb3SApril Chin 2817c2fbfb3SApril Chin # process reply 2827c2fbfb3SApril Chin parse_http_response httpresponse <&${netfd} 2837c2fbfb3SApril Chin response="${ cat_http_body "${httpresponse.transfer_encoding}" <&${netfd} ; }" 2847c2fbfb3SApril Chin 2857c2fbfb3SApril Chin # close connection 2867c2fbfb3SApril Chin redirect {netfd}<&- 2877c2fbfb3SApril Chin 2887c2fbfb3SApril Chin printf $"twitter response was (%s,%s): %s\n" "${httpresponse.statuscode}" "${httpresponse.statusmsg}" "${response}" 2897c2fbfb3SApril Chin 2907c2fbfb3SApril Chin if (( httpresponse.statuscode >= 200 && httpresponse.statuscode <= 299 )) ; then 2917c2fbfb3SApril Chin return 0 2927c2fbfb3SApril Chin else 2937c2fbfb3SApril Chin return 1 2947c2fbfb3SApril Chin fi 2957c2fbfb3SApril Chin 2967c2fbfb3SApril Chin # not reached 2977c2fbfb3SApril Chin} 2987c2fbfb3SApril Chin 2997c2fbfb3SApril Chinfunction usage 3007c2fbfb3SApril Chin{ 3017c2fbfb3SApril Chin OPTIND=0 3027c2fbfb3SApril Chin getopts -a "${progname}" "${shtwitter_usage}" OPT '-?' 3037c2fbfb3SApril Chin exit 2 3047c2fbfb3SApril Chin} 3057c2fbfb3SApril Chin 3067c2fbfb3SApril Chin# program start 3077c2fbfb3SApril Chinbuiltin basename 3087c2fbfb3SApril Chinbuiltin cat 3097c2fbfb3SApril Chinbuiltin date 3107c2fbfb3SApril Chinbuiltin uname 3117c2fbfb3SApril Chin 3127c2fbfb3SApril Chintypeset progname="${ basename "${0}" ; }" 3137c2fbfb3SApril Chin 3147c2fbfb3SApril Chin# HTTP protocol client identifer 315*3e14f97fSRoger A. Faulknertypeset -r http_user_agent="shtwitter/ksh93 (2010-03-27; ${ uname -s -r -p ; })" 3167c2fbfb3SApril Chin 3177c2fbfb3SApril Chintypeset -r shtwitter_usage=$'+ 318*3e14f97fSRoger A. Faulkner[-?\n@(#)\$Id: shtwitter (Roland Mainz) 2010-03-27 \$\n] 3197c2fbfb3SApril Chin[-author?Roland Mainz <roland.mainz@nrubsig.org>] 3207c2fbfb3SApril Chin[+NAME?shtwitter - read/write text data to internet clipboards] 3217c2fbfb3SApril Chin[+DESCRIPTION?\bshtwitter\b is a small utility which can read and write text 3227c2fbfb3SApril Chin to the twitter.com microblogging site.] 3237c2fbfb3SApril Chin[+?The first arg \bmethod\b describes one of the methods, "update" posts a 3247c2fbfb3SApril Chin text message to the users twitter blog, returning the raw response 3257c2fbfb3SApril Chin message from the twitter server.] 3267c2fbfb3SApril Chin[+?The second arg \bstring\b contains the string data which should be 3277c2fbfb3SApril Chin stored on twitter.com.] 3287c2fbfb3SApril Chin 3297c2fbfb3SApril Chinmethod [ string ] 3307c2fbfb3SApril Chin 3317c2fbfb3SApril Chin[+SEE ALSO?\bksh93\b(1), \brssread\b(1), \bshtinyurl\b(1), http://www.twitter.com] 3327c2fbfb3SApril Chin' 3337c2fbfb3SApril Chin 3347c2fbfb3SApril Chinwhile getopts -a "${progname}" "${shtwitter_usage}" OPT ; do 3357c2fbfb3SApril Chin# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|" 3367c2fbfb3SApril Chin case ${OPT} in 3377c2fbfb3SApril Chin *) usage ;; 3387c2fbfb3SApril Chin esac 3397c2fbfb3SApril Chindone 3407c2fbfb3SApril Chinshift $((OPTIND-1)) 3417c2fbfb3SApril Chin 3427c2fbfb3SApril Chin# expecting at least one more argument 3437c2fbfb3SApril Chin(($# >= 1)) || usage 3447c2fbfb3SApril Chin 3457c2fbfb3SApril Chintypeset method="$1" 3467c2fbfb3SApril Chinshift 3477c2fbfb3SApril Chin 3487c2fbfb3SApril Chincase "${method}" in 3497c2fbfb3SApril Chin update|blog) put_twitter_message "$@" ; exit $? ;; 3507c2fbfb3SApril Chin verify_credentials) verify_twitter_credentials "$@" ; exit $? ;; 3517c2fbfb3SApril Chin *) usage ;; 3527c2fbfb3SApril Chinesac 3537c2fbfb3SApril Chin 3547c2fbfb3SApril Chinfatal_error $"not reached." 3557c2fbfb3SApril Chin# EOF. 356