1#!/usr/bin/ksh93 2 3# 4# CDDL HEADER START 5# 6# The contents of this file are subject to the terms of the 7# Common Development and Distribution License (the "License"). 8# You may not use this file except in compliance with the License. 9# 10# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 11# or http://www.opensolaris.org/os/licensing. 12# See the License for the specific language governing permissions 13# and limitations under the License. 14# 15# When distributing Covered Code, include this CDDL HEADER in each 16# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 17# If applicable, add the following below this CDDL HEADER, with the 18# fields enclosed by brackets "[]" replaced with your own identifying 19# information: Portions Copyright [yyyy] [name of copyright owner] 20# 21# CDDL HEADER END 22# 23 24# 25# Copyright 2008 Sun Microsystems, Inc. All rights reserved. 26# Use is subject to license terms. 27# 28 29# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant 30export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin 31 32# Make sure all math stuff runs in the "C" locale to avoid problems 33# with alternative # radix point representations (e.g. ',' instead of 34# '.' in de_DE.*-locales). This needs to be set _before_ any 35# floating-point constants are defined in this script). 36if [[ "${LC_ALL}" != "" ]] ; then 37 export \ 38 LC_MONETARY="${LC_ALL}" \ 39 LC_MESSAGES="${LC_ALL}" \ 40 LC_COLLATE="${LC_ALL}" \ 41 LC_CTYPE="${LC_ALL}" 42 unset LC_ALL 43fi 44export LC_NUMERIC=C 45 46function fatal_error 47{ 48 print -u2 "${progname}: $*" 49 exit 1 50} 51 52function encode_x_www_form_urlencoded 53{ 54 nameref formdata=$1 55 nameref content="formdata.content" 56 integer numformelements=${#formdata.form[*]} 57 integer i j 58 59 content="" 60 61 for (( i=0 ; i < numformelements ; i++ )) ; do 62 nameref element="formdata.form[${i}]" 63 typeset data="${element.data}" 64 integer datalen="${#data}" 65 typeset c 66 67 [[ "$content" != "" ]] && content+="&" 68 69 content+="${element.name}=" 70 71 for ((j=0 ; j < datalen ; j++)) ; do 72 c="${data:j:1}" 73 case "$c" in 74 ' ') c="+" ;; 75 '!') c="%21" ;; 76 '*') c="%2A" ;; 77 "'") c="%27" ;; 78 '(') c="%28" ;; 79 ')') c="%29" ;; 80 ';') c="%3B" ;; 81 ':') c="%3A" ;; 82 '@') c="%40" ;; 83 '&') c="%26" ;; 84 '=') c="%3D" ;; 85 '+') c="%2B" ;; 86 '$') c="%24" ;; 87 ',') c="%2C" ;; 88 '/') c="%2F" ;; 89 '?') c="%3F" ;; 90 '%') c="%25" ;; 91 '#') c="%23" ;; 92 '[') c="%5B" ;; 93 '\') c="%5C" ;; # we need this to avoid the '\'-quoting hell 94 ']') c="%5D" ;; 95 *) ;; 96 esac 97 content+="$c" 98 done 99 done 100 101 formdata.content_length=${#content} 102 103 return 0 104} 105 106# parse HTTP return code, cookies etc. 107function parse_http_response 108{ 109 nameref response="$1" 110 typeset h statuscode statusmsg i 111 112 # we use '\r' as additional IFS to filter the final '\r' 113 IFS=$' \t\r' read -r h statuscode statusmsg # read HTTP/1.[01] <code> 114 [[ "$h" != ~(Eil)HTTP/.* ]] && { print -u2 -f $"%s: HTTP/ header missing\n" "$0" ; return 1 ; } 115 [[ "$statuscode" != ~(Elr)[0-9]* ]] && { print -u2 -f $"%s: invalid status code\n" "$0" ; return 1 ; } 116 response.statuscode="$statuscode" 117 response.statusmsg="$statusmsg" 118 119 # skip remaining headers 120 while IFS='' read -r i ; do 121 [[ "$i" == $'\r' ]] && break 122 123 # strip '\r' at the end 124 i="${i/~(Er)$'\r'/}" 125 126 case "$i" in 127 ~(Eli)Content-Type:.*) 128 response.content_type="${i/~(El).*:[[:blank:]]*/}" 129 ;; 130 ~(Eli)Content-Length:[[:blank:]]*[0-9]*) 131 integer response.content_length="${i/~(El).*:[[:blank:]]*/}" 132 ;; 133 ~(Eli)Transfer-Encoding:.*) 134 response.transfer_encoding="${i/~(El).*:[[:blank:]]*/}" 135 ;; 136 esac 137 done 138 139 return 0 140} 141 142function cat_http_body 143{ 144 typeset emode="$1" 145 typeset hexchunksize="0" 146 integer chunksize=0 147 148 if [[ "${emode}" == "chunked" ]] ; then 149 while IFS=$'\r' read hexchunksize && 150 [[ "${hexchunksize}" == ~(Elri)[0-9abcdef]* ]] && 151 (( chunksize=16#${hexchunksize} )) && (( chunksize > 0 )) ; do 152 dd bs=1 count="${chunksize}" 2>/dev/null 153 done 154 else 155 cat 156 fi 157 158 return 0 159} 160 161function encode_http_basic_auth 162{ 163 typeset user="$1" 164 typeset passwd="$2" 165 typeset s 166 integer s_len 167 typeset -b base64var 168 169 # ksh93 binary variables use base64 encoding, the same as the 170 # HTTP basic authentification. We only have to read the 171 # plaintext user:passwd string into the binary variable "base64var" 172 # and then print this variable as ASCII. 173 s="${user}:${passwd}" 174 s_len="${#s}" 175 print -n "${s}" | read -N${s_len} base64var 176 177 print -- "${base64var}" # print ASCII (base64) representation of binary var 178 179 return 0 180} 181 182function put_twitter_message 183{ 184 [[ "$SHTWITTER_USER" == "" ]] && { print -u2 -f $"%s: SHTWITTER_USER not set.\n" "$0" ; return 1 ; } 185 [[ "$SHTWITTER_PASSWD" == "" ]] && { print -u2 -f $"%s: SHTWITTER_PASSWD not set.\n" "$0" ; return 1 ; } 186 187 (( $# != 1 )) && { print -u2 -f $"%s: Wrong number of arguments.\n" "$0" ; return 1 ; } 188 189 # site setup 190 typeset url_host="twitter.com" 191 typeset url_path="/statuses/update.xml" 192 typeset url="http://${url_host}${url_path}" 193 integer netfd # http stream number 194 typeset msgtext="$1" 195 typeset -C httpresponse # http response 196 197 # argument for "encode_x_www_form_urlencoded" 198 typeset urlform=( 199 # input 200 typeset -a form 201 # output 202 typeset content 203 integer content_length 204 ) 205 206 typeset request="" 207 typeset content="" 208 209 urlform.form=( 210 ( name="status" data="${msgtext}" ) 211 ) 212 213 encode_x_www_form_urlencoded urlform 214 215 content="${urlform.content}" 216 217 request="POST ${url_path} HTTP/1.1\r\n" 218 request+="Host: ${url_host}\r\n" 219 request+="Authorization: Basic ${ encode_http_basic_auth "${SHTWITTER_USER}" "${SHTWITTER_PASSWD}" ; }\r\n" 220 request+="User-Agent: ${http_user_agent}\r\n" 221 request+="Connection: close\r\n" 222 request+="Content-Type: application/x-www-form-urlencoded\r\n" 223 request+="Content-Length: $(( urlform.content_length ))\r\n" 224 225 redirect {netfd}<>"/dev/tcp/${url_host}/80" 226 (( $? != 0 )) && { print -u2 -f "%s: Could not open connection to %s\n." "$0" "${url_host}" ; return 1 ; } 227 228 # send http post 229 { 230 print -n -- "${request}\r\n" 231 print -n -- "${content}\r\n" 232 } >&${netfd} 233 234 # process reply 235 parse_http_response httpresponse <&${netfd} 236 response="${ cat_http_body "${httpresponse.transfer_encoding}" <&${netfd} ; }" 237 238 # close connection 239 redirect {netfd}<&- 240 241 printf $"twitter response was (%s,%s): %s\n" "${httpresponse.statuscode}" "${httpresponse.statusmsg}" "${response}" 242 243 if (( httpresponse.statuscode >= 200 && httpresponse.statuscode <= 299 )) ; then 244 return 0 245 else 246 return 1 247 fi 248 249 # not reached 250} 251 252function verify_twitter_credentials 253{ 254 [[ "$SHTWITTER_USER" == "" ]] && { print -u2 -f $"%s: SHTWITTER_USER not set.\n" "$0" ; return 1 ; } 255 [[ "$SHTWITTER_PASSWD" == "" ]] && { print -u2 -f $"%s: SHTWITTER_PASSWD not set.\n" "$0" ; return 1 ; } 256 257 (( $# != 0 )) && { print -u2 -f $"%s: Wrong number of arguments.\n" "$0" ; return 1 ; } 258 259 # site setup 260 typeset url_host="twitter.com" 261 typeset url_path="/account/verify_credentials.xml" 262 typeset url="http://${url_host}${url_path}" 263 integer netfd # http stream number 264 typeset -C httpresponse # http response 265 266 typeset request="" 267 268 request="POST ${url_path} HTTP/1.1\r\n" 269 request+="Host: ${url_host}\r\n" 270 request+="Authorization: Basic ${ encode_http_basic_auth "${SHTWITTER_USER}" "${SHTWITTER_PASSWD}" ; }\r\n" 271 request+="User-Agent: ${http_user_agent}\r\n" 272 request+="Connection: close\r\n" 273 request+="Content-Type: application/x-www-form-urlencoded\r\n" 274 request+="Content-Length: 0\r\n" # dummy 275 276 redirect {netfd}<>"/dev/tcp/${url_host}/80" 277 (( $? != 0 )) && { print -u2 -f $"%s: Could not open connection to %s.\n" "$0" "${url_host}" ; return 1 ; } 278 279 # send http post 280 { 281 print -n -- "${request}\r\n" 282 } >&${netfd} 283 284 # process reply 285 parse_http_response httpresponse <&${netfd} 286 response="${ cat_http_body "${httpresponse.transfer_encoding}" <&${netfd} ; }" 287 288 # close connection 289 redirect {netfd}<&- 290 291 printf $"twitter response was (%s,%s): %s\n" "${httpresponse.statuscode}" "${httpresponse.statusmsg}" "${response}" 292 293 if (( httpresponse.statuscode >= 200 && httpresponse.statuscode <= 299 )) ; then 294 return 0 295 else 296 return 1 297 fi 298 299 # not reached 300} 301 302function usage 303{ 304 OPTIND=0 305 getopts -a "${progname}" "${shtwitter_usage}" OPT '-?' 306 exit 2 307} 308 309# program start 310builtin basename 311builtin cat 312builtin date 313builtin uname 314 315typeset progname="${ basename "${0}" ; }" 316 317# HTTP protocol client identifer 318typeset -r http_user_agent="shtwitter/ksh93 (2008-10-14; ${ uname -s -r -p ; })" 319 320typeset -r shtwitter_usage=$'+ 321[-?\n@(#)\$Id: shtwitter (Roland Mainz) 2008-10-14 \$\n] 322[-author?Roland Mainz <roland.mainz@nrubsig.org>] 323[+NAME?shtwitter - read/write text data to internet clipboards] 324[+DESCRIPTION?\bshtwitter\b is a small utility which can read and write text 325 to the twitter.com microblogging site.] 326[+?The first arg \bmethod\b describes one of the methods, "update" posts a 327 text message to the users twitter blog, returning the raw response 328 message from the twitter server.] 329[+?The second arg \bstring\b contains the string data which should be 330 stored on twitter.com.] 331 332method [ string ] 333 334[+SEE ALSO?\bksh93\b(1), \brssread\b(1), \bshtinyurl\b(1), http://www.twitter.com] 335' 336 337while getopts -a "${progname}" "${shtwitter_usage}" OPT ; do 338# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|" 339 case ${OPT} in 340 *) usage ;; 341 esac 342done 343shift $((OPTIND-1)) 344 345# expecting at least one more argument 346(($# >= 1)) || usage 347 348typeset method="$1" 349shift 350 351case "${method}" in 352 update|blog) put_twitter_message "$@" ; exit $? ;; 353 verify_credentials) verify_twitter_credentials "$@" ; exit $? ;; 354 *) usage ;; 355esac 356 357fatal_error $"not reached." 358# EOF. 359