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 2009 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 compound httpresponse # http response 196 197 # argument for "encode_x_www_form_urlencoded" 198 compound urlform=( 199 # input 200 compound -a form=( 201 ( name="status" data="${msgtext}" ) 202 ) 203 # output 204 typeset content 205 integer content_length 206 ) 207 208 typeset request="" 209 typeset content="" 210 211 encode_x_www_form_urlencoded urlform 212 213 content="${urlform.content}" 214 215 request="POST ${url_path} HTTP/1.1\r\n" 216 request+="Host: ${url_host}\r\n" 217 request+="Authorization: Basic ${ encode_http_basic_auth "${SHTWITTER_USER}" "${SHTWITTER_PASSWD}" ; }\r\n" 218 request+="User-Agent: ${http_user_agent}\r\n" 219 request+="Connection: close\r\n" 220 request+="Content-Type: application/x-www-form-urlencoded\r\n" 221 request+="Content-Length: $(( urlform.content_length ))\r\n" 222 223 redirect {netfd}<> "/dev/tcp/${url_host}/80" 224 (( $? != 0 )) && { print -u2 -f "%s: Could not open connection to %s\n." "$0" "${url_host}" ; return 1 ; } 225 226 # send http post 227 { 228 print -n -- "${request}\r\n" 229 print -n -- "${content}\r\n" 230 } >&${netfd} 231 232 # process reply 233 parse_http_response httpresponse <&${netfd} 234 response="${ cat_http_body "${httpresponse.transfer_encoding}" <&${netfd} ; }" 235 236 # close connection 237 redirect {netfd}<&- 238 239 printf $"twitter response was (%s,%s): %s\n" "${httpresponse.statuscode}" "${httpresponse.statusmsg}" "${response}" 240 241 if (( httpresponse.statuscode >= 200 && httpresponse.statuscode <= 299 )) ; then 242 return 0 243 else 244 return 1 245 fi 246 247 # not reached 248} 249 250function verify_twitter_credentials 251{ 252 [[ "$SHTWITTER_USER" == "" ]] && { print -u2 -f $"%s: SHTWITTER_USER not set.\n" "$0" ; return 1 ; } 253 [[ "$SHTWITTER_PASSWD" == "" ]] && { print -u2 -f $"%s: SHTWITTER_PASSWD not set.\n" "$0" ; return 1 ; } 254 255 (( $# != 0 )) && { print -u2 -f $"%s: Wrong number of arguments.\n" "$0" ; return 1 ; } 256 257 # site setup 258 typeset url_host="twitter.com" 259 typeset url_path="/account/verify_credentials.xml" 260 typeset url="http://${url_host}${url_path}" 261 integer netfd # http stream number 262 compound httpresponse # http response 263 264 typeset request="" 265 266 request="POST ${url_path} HTTP/1.1\r\n" 267 request+="Host: ${url_host}\r\n" 268 request+="Authorization: Basic ${ encode_http_basic_auth "${SHTWITTER_USER}" "${SHTWITTER_PASSWD}" ; }\r\n" 269 request+="User-Agent: ${http_user_agent}\r\n" 270 request+="Connection: close\r\n" 271 request+="Content-Type: application/x-www-form-urlencoded\r\n" 272 request+="Content-Length: 0\r\n" # dummy 273 274 redirect {netfd}<> "/dev/tcp/${url_host}/80" 275 (( $? != 0 )) && { print -u2 -f $"%s: Could not open connection to %s.\n" "$0" "${url_host}" ; return 1 ; } 276 277 # send http post 278 { 279 print -n -- "${request}\r\n" 280 } >&${netfd} 281 282 # process reply 283 parse_http_response httpresponse <&${netfd} 284 response="${ cat_http_body "${httpresponse.transfer_encoding}" <&${netfd} ; }" 285 286 # close connection 287 redirect {netfd}<&- 288 289 printf $"twitter response was (%s,%s): %s\n" "${httpresponse.statuscode}" "${httpresponse.statusmsg}" "${response}" 290 291 if (( httpresponse.statuscode >= 200 && httpresponse.statuscode <= 299 )) ; then 292 return 0 293 else 294 return 1 295 fi 296 297 # not reached 298} 299 300function usage 301{ 302 OPTIND=0 303 getopts -a "${progname}" "${shtwitter_usage}" OPT '-?' 304 exit 2 305} 306 307# program start 308builtin basename 309builtin cat 310builtin date 311builtin uname 312 313typeset progname="${ basename "${0}" ; }" 314 315# HTTP protocol client identifer 316typeset -r http_user_agent="shtwitter/ksh93 (2009-06-15; ${ uname -s -r -p ; })" 317 318typeset -r shtwitter_usage=$'+ 319[-?\n@(#)\$Id: shtwitter (Roland Mainz) 2009-06-15 \$\n] 320[-author?Roland Mainz <roland.mainz@nrubsig.org>] 321[+NAME?shtwitter - read/write text data to internet clipboards] 322[+DESCRIPTION?\bshtwitter\b is a small utility which can read and write text 323 to the twitter.com microblogging site.] 324[+?The first arg \bmethod\b describes one of the methods, "update" posts a 325 text message to the users twitter blog, returning the raw response 326 message from the twitter server.] 327[+?The second arg \bstring\b contains the string data which should be 328 stored on twitter.com.] 329 330method [ string ] 331 332[+SEE ALSO?\bksh93\b(1), \brssread\b(1), \bshtinyurl\b(1), http://www.twitter.com] 333' 334 335while getopts -a "${progname}" "${shtwitter_usage}" OPT ; do 336# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|" 337 case ${OPT} in 338 *) usage ;; 339 esac 340done 341shift $((OPTIND-1)) 342 343# expecting at least one more argument 344(($# >= 1)) || usage 345 346typeset method="$1" 347shift 348 349case "${method}" in 350 update|blog) put_twitter_message "$@" ; exit $? ;; 351 verify_credentials) verify_twitter_credentials "$@" ; exit $? ;; 352 *) usage ;; 353esac 354 355fatal_error $"not reached." 356# EOF. 357