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