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
51# parse HTTP return code, cookies etc.
52function parse_http_response
53{
54	nameref response="$1"
55	typeset h statuscode statusmsg i
56    
57	# we use '\r' as additional IFS to filter the final '\r'
58	IFS=$' \t\r' read -r h statuscode statusmsg  # read HTTP/1.[01] <code>
59	[[ "$h" != ~(Eil)HTTP/.* ]]         && { print -u2 -f $"%s: HTTP/ header missing\n" "$0" ; return 1 ; }
60	[[ "$statuscode" != ~(Elr)[0-9]* ]] && { print -u2 -f $"%s: invalid status code\n"  "$0" ; return 1 ; }
61	response.statuscode="$statuscode"
62	response.statusmsg="$statusmsg"
63    
64	# skip remaining headers
65	while IFS='' read -r i ; do
66		[[ "$i" == $'\r' ]] && break
67
68		# strip '\r' at the end
69		i="${i/~(Er)$'\r'/}"
70
71		case "$i" in
72			~(Eli)Content-Type:.*)
73				response.content_type="${i/~(El).*:[[:blank:]]*/}"
74				;;
75			~(Eli)Content-Length:[[:blank:]]*[0-9]*)
76				integer response.content_length="${i/~(El).*:[[:blank:]]*/}"
77				;;
78			~(Eli)Transfer-Encoding:.*)
79				response.transfer_encoding="${i/~(El).*:[[:blank:]]*/}"
80				;;
81		esac
82	done
83
84	return 0
85}
86
87function cat_http_body
88{
89	typeset emode="$1"
90	typeset hexchunksize="0"
91	integer chunksize=0 
92    
93	if [[ "${emode}" == "chunked" ]] ; then
94		while IFS=$'\r' read hexchunksize &&
95			[[ "${hexchunksize}" == ~(Elri)[0-9abcdef]+ ]] &&
96			(( chunksize=$( printf "16#%s\n" "${hexchunksize}" ) )) && (( chunksize > 0 )) ; do
97			dd bs=1 count="${chunksize}" 2>/dev/null
98		done
99	else
100		cat
101	fi
102
103	return 0
104}
105
106function request_tinyurl
107{
108	# site setup
109	typeset url_host="tinyurl.com"
110	typeset url_path="/api-create.php"
111	typeset url="http://${url_host}${url_path}"
112	integer netfd # http stream number
113	typeset inputurl="$1"
114	compound httpresponse # http response
115	typeset request=""
116
117	# we assume "inputurl" is a correctly encoded URL which doesn't
118	# require any further mangling
119	url_path+="?url=${inputurl}"
120
121	request="GET ${url_path} HTTP/1.1\r\n"
122	request+="Host: ${url_host}\r\n"
123	request+="User-Agent: ${http_user_agent}\r\n"
124	request+="Connection: close\r\n"
125
126	redirect {netfd}<> "/dev/tcp/${url_host}/80" 
127	(( $? != 0 )) && { print -u2 -f $"%s: Could not open connection to %s.\n" "$0" "${url_host}" ;  return 1 ; }
128
129	# send http post
130	{
131		print -n -- "${request}\r\n"
132	}  >&${netfd}
133
134	# process reply
135	parse_http_response httpresponse <&${netfd}
136	response="${ cat_http_body "${httpresponse.transfer_encoding}" <&${netfd} ; }"
137
138	# close connection
139	redirect {netfd}<&-
140        
141	if (( httpresponse.statuscode >= 200 && httpresponse.statuscode <= 299 )) ; then
142		print -r -- "${response}"
143		return 0
144	else
145		print -u2 -f $"tinyurl response was (%s,%s):\n%s\n" "${httpresponse.statuscode}" "${httpresponse.statusmsg}" "${response}"
146		return 1
147	fi
148	
149	# not reached
150}
151
152function request_trimurl
153{
154	# site setup
155	typeset url_host="api.tr.im"
156	typeset url_path="/api/trim_url.xml"
157	typeset url="http://${url_host}${url_path}"
158	integer netfd # http stream number
159	typeset inputurl="$1"
160	compound httpresponse # http response
161	typeset request=""
162
163	# we assume "inputurl" is a correctly encoded URL which doesn't
164	# require any further mangling
165	url_path+="?url=${inputurl}"
166
167	request="GET ${url_path} HTTP/1.1\r\n"
168	request+="Host: ${url_host}\r\n"
169	request+="User-Agent: ${http_user_agent}\r\n"
170	request+="Connection: close\r\n"
171
172	redirect {netfd}<> "/dev/tcp/${url_host}/80" 
173	(( $? != 0 )) && { print -u2 -f $"%s: Could not open connection to %s.\n" "$0" "${url_host}" ;  return 1 ; }
174
175	# send http post
176	{
177		print -n -- "${request}\r\n"
178	}  >&${netfd}
179
180	# process reply
181	parse_http_response httpresponse <&${netfd}
182	response="${ cat_http_body "${httpresponse.transfer_encoding}" <&${netfd} ; }"
183
184	# close connection
185	redirect {netfd}<&-
186        
187	if (( httpresponse.statuscode >= 200 && httpresponse.statuscode <= 299 )) ; then
188		# the statement below should really parse the XML...
189		print -r -- "${response/~(Elr).*(\<url\>)(.*)(\<\/url\>).*/\2}"
190		return 0
191	else
192		print -u2 -f $"tr.im response was (%s,%s):\n%s\n" "${httpresponse.statuscode}" "${httpresponse.statusmsg}" "${response}"
193		return 1
194	fi
195	
196	# not reached
197}
198
199function usage
200{
201	OPTIND=0
202	getopts -a "${progname}" "${shtinyurl_usage}" OPT '-?'
203	exit 2
204}
205
206# program start
207builtin basename
208builtin cat
209builtin date
210builtin uname
211
212typeset progname="${ basename "${0}" ; }"
213
214# HTTP protocol client identifer
215typeset -r http_user_agent="shtinyurl/ksh93 (2010-03-27; ${ uname -s -r -p ; })"
216
217typeset -r shtinyurl_usage=$'+
218[-?\n@(#)\$Id: shtinyurl (Roland Mainz) 2010-03-27 \$\n]
219[-author?Roland Mainz <roland.mainz@nrubsig.org>]
220[+NAME?shtinyurl - create short alias URL from long URL]
221[+DESCRIPTION?\bshtinyurl\b is a small utility which passes a given URL
222	to internet service which creates short aliases in the
223	form of http://<servicename>/XXXXXXXX to redirect long URLs.]
224[+?The first arg \burl\b describes a long URL which is transformed into
225	a tinyurl.com short alias.]
226[P:provider?Service provider (either \'tinyurl.com\' or \'tr.im\').]:[mode]
227
228url
229
230[+SEE ALSO?\bksh93\b(1), \brssread\b(1), \bshtwitter\b(1), http://www.tinyurl.com, http://tr.im]
231'
232
233typeset service_provider="tr.im"
234
235while getopts -a "${progname}" "${shtinyurl_usage}" OPT ; do 
236#	printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|"
237	case ${OPT} in
238		P)	service_provider="${OPTARG}" ;;
239		*)	usage ;;
240	esac
241done
242shift $((OPTIND-1))
243
244# expecting at least one more argument
245(( $# >= 1 )) || usage
246
247typeset url="$1"
248shift
249
250case "${service_provider}" in
251	"tinyurl.com")
252		request_tinyurl "${url}"
253		exit $?
254		;;
255	"tr.im")
256		request_trimurl "${url}"
257		exit $?
258		;;
259	*)
260		fatal_error "Unsupported service provider."
261esac
262
263# not reached
264
265# EOF.
266