8354 sync regcomp(3C) with upstream (fix make catalog)
[unleashed/tickless.git] / usr / src / lib / libshell / common / scripts / shnote.sh
blob217fc7daaed88db0d0307b6f4be6577bbd6abb4c
1 #!/usr/bin/ksh93
4 # CDDL HEADER START
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.
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.
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]
21 # CDDL HEADER END
25 # Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
28 # Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant
29 export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin
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).
35 if [[ "${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
43 export LC_NUMERIC=C
45 function fatal_error
47 print -u2 "${progname}: $*"
48 exit 1
51 function encode_multipart_form_data
53 nameref formdata="$1"
54 nameref content="formdata.content"
55 integer numformelements=${#formdata.form[*]}
56 integer i
57 typeset tmp
59 content=""
61 # todo: add support to upload files
62 for (( i=0 ; i < numformelements ; i++ )) ; do
63 nameref element="formdata.form[${i}]"
65 content+="--${formdata.boundary}\n"
66 content+="Content-Disposition: form-data; name=\"${element.name}\"\n"
67 content+="\n"
68 # make sure we quote the '\' properly since we pass these data to one instance of
69 # "print" when putting the content on the wire.
70 content+="${element.data//\\/\\\\}\n" # fixme: may need encoding for non-ASCII data
71 done
73 # we have to de-quote the content before we can count the real numer of bytes in the payload
74 tmp="$(print -- "${content}")"
75 formdata.content_length=${#tmp}
77 # add content tail (which MUST not be added to the content length)
78 content+="--${formdata.boundary}--\n"
80 return 0
83 # parse HTTP return code, cookies etc.
84 function parse_http_response
86 nameref response="$1"
87 typeset h statuscode statusmsg i
89 # we use '\r' as additional IFS to filter the final '\r'
90 IFS=$' \t\r' read -r h statuscode statusmsg # read HTTP/1.[01] <code>
91 [[ "$h" != ~(Eil)HTTP/.* ]] && { print -u2 -f $"%s: HTTP/ header missing\n" "$0" ; return 1 ; }
92 [[ "$statuscode" != ~(Elr)[0-9]* ]] && { print -u2 -f $"%s: invalid status code\n" "$0" ; return 1 ; }
93 response.statuscode="$statuscode"
94 response.statusmsg="$statusmsg"
96 # skip remaining headers
97 while IFS='' read -r i ; do
98 [[ "$i" == $'\r' ]] && break
100 # strip '\r' at the end
101 i="${i/~(Er)$'\r'/}"
103 case "$i" in
104 ~(Eli)Content-Type:.*)
105 response.content_type="${i/~(El).*:[[:blank:]]*/}"
107 ~(Eli)Content-Length:[[:blank:]]*[0-9]*)
108 integer response.content_length="${i/~(El).*:[[:blank:]]*/}"
110 ~(Eli)Transfer-Encoding:.*)
111 response.transfer_encoding="${i/~(El).*:[[:blank:]]*/}"
113 esac
114 done
116 return 0
119 function cat_http_body
121 typeset emode="$1"
122 typeset hexchunksize="0"
123 integer chunksize=0
125 if [[ "${emode}" == "chunked" ]] ; then
126 while IFS=$'\r' read hexchunksize &&
127 [[ "${hexchunksize}" == ~(Elri)[0-9abcdef]+ ]] &&
128 (( chunksize=$( printf "16#%s\n" "${hexchunksize}" ) )) && (( chunksize > 0 )) ; do
129 dd bs=1 count="${chunksize}" 2>/dev/null
130 done
131 else
135 return 0
138 function history_write_record
140 # rec: history record:
141 # rec.title
142 # rec.description
143 # rec.provider
144 # rec.providertoken
145 # rec.url
146 nameref rec="$1"
147 integer histfd
149 mkdir -p "${HOME}/.shnote"
152 # write a single-line record which can be read
153 # as a compound variable back into the shell
154 printf "title=%q description=%q date=%q provider=%q providertoken=%q url=%q\n" \
155 "${rec.title}" \
156 "${rec.description}" \
157 "$(date)" \
158 "${rec.provider}" \
159 "${rec.providertoken}" \
160 "${rec.url}"
161 } >>"${history_file}"
163 return $?
166 function print_history
168 integer histfd # http stream number
169 typeset line
171 (( $# != 0 && $# != 1 )) && { print -u2 -f $"%s: Wrong number of arguments.\n" "$0" ; return 1 ; }
173 # default output format is:
174 # <access url>/<title> <date> <access url>
175 [[ "$1" == "-l" ]] || printf "# %s\t\t\t\t\t%s\t%s\n" "<url>" "<title>" "<date>"
177 # no history file ?
178 if [[ ! -f "${history_file}" ]] ; then
179 return 0
182 # open history file
183 redirect {histfd}<> "${history_file}"
184 (( $? != 0 )) && { print -u2 "Could not open history file." ; return 1 ; }
186 while read -u${histfd} line ; do
187 compound rec
189 printf "( %s )\n" "${line}" | read -C rec
191 if [[ "$1" == "-l" ]] ; then
192 print -- "${rec}"
193 else
194 printf "%q\t%q\t%q\n" "${rec.url}" "${rec.title}" "${rec.date}"
197 unset rec
198 done
200 # close history file
201 redirect {histfd}<&-
203 return 0
206 function put_note_pastebin_ca
208 # key to autheticate this script against pastebin.ca
209 typeset -r pastebin_ca_key="9CFXFyeNC3iga/vthok75kTBu5kSSLPD"
210 # site setup
211 typeset url_host="opensolaris.pastebin.ca"
212 typeset url_path="/quiet-paste.php?api=${pastebin_ca_key}"
213 typeset url="http://${url_host}${url_path}"
214 integer netfd # http stream number
215 compound httpresponse
217 (( $# != 1 )) && { print -u2 -f $"%s: Wrong number of arguments.\n" "$0" ; return 1 ; }
218 (( ${#1} == 0 )) && { print -u2 -f $"%s: No data.\n" "$0" ; return 1 ; }
220 # argument for "encode_multipart_form_data"
221 compound mimeform=(
222 # input
223 typeset boundary
224 typeset -a form
225 # output
226 typeset content
227 integer content_length
230 typeset request=""
231 typeset content=""
233 typeset -r boundary="--------shnote_${RANDOM}_Xfish_${RANDOM}_Yeats_${RANDOM}_Zchicken_${RANDOM}monster_--------"
235 mimeform.boundary="${boundary}"
236 mimeform.form=( # we use explicit index numbers since we rely on them below when filling the history
237 [0]=( name="name" data="${LOGNAME}" )
238 [1]=( name="expiry" data="Never" )
239 [2]=( name="type" data="1" )
240 [3]=( name="description" data="logname=${LOGNAME};hostname=$(hostname);date=$(date)" )
241 [4]=( name="content" data="$1" )
243 encode_multipart_form_data mimeform
245 content="${mimeform.content}"
247 request="POST ${url_path} HTTP/1.1\r\n"
248 request+="Host: ${url_host}\r\n"
249 request+="User-Agent: ${http_user_agent}\r\n"
250 request+="Connection: close\r\n"
251 request+="Content-Type: multipart/form-data; boundary=${boundary}\r\n"
252 request+="Content-Length: $(( mimeform.content_length ))\r\n"
254 redirect {netfd}<> "/dev/tcp/${url_host}/80"
255 (( $? != 0 )) && { print -u2 -f $"%s: Could not open connection to %s.\n" "$0" "${url_host}" ; return 1 ; }
257 # send http post
259 print -n -- "${request}\r\n"
260 print -n -- "${content}\r\n"
261 } >&${netfd}
263 # process reply
264 parse_http_response httpresponse <&${netfd}
265 response="$(cat_http_body "${httpresponse.transfer_encoding}" <&${netfd})"
267 # close connection
268 redirect {netfd}<&-
270 if [[ "${response}" == ~(E).*SUCCESS.* ]] ; then
271 typeset response_token="${response/~(E).*SUCCESS:/}"
273 printf "SUCCESS: http://opensolaris.pastebin.ca/%s\n" "${response_token}"
275 # write history entry
276 compound histrec=(
277 title="${mimeform.form[0].data}"
278 description="${mimeform.form[3].data}"
279 providertoken="${response_token}"
280 provider="opensolaris.pastebin.ca"
281 url="http://opensolaris.pastebin.ca/${response_token}"
284 history_write_record histrec
285 return 0
286 else
287 printf "ERROR: %s\n" "${response}"
288 return 1
291 # not reached
294 function get_note_pastebin_ca
296 typeset recordname="$1"
297 integer netfd # http stream number
299 (( $# != 1 )) && { print -u2 -f $"%s: No key or key URL.\n" "$0" ; return 1 ; }
301 case "${recordname}" in
302 ~(Elr)[0-9][0-9]*)
303 # pass-through
305 ~(Elr)http://opensolaris.pastebin.ca/raw/[0-9]*)
306 recordname="${recordname/~(El)http:\/\/opensolaris.pastebin.ca\/raw\//}"
308 ~(Elr)http://opensolaris.pastebin.ca/[0-9]*)
309 recordname="${recordname/~(El)http:\/\/opensolaris.pastebin.ca\//}"
312 fatal_error $"Unsupported record name ${recordname}."
313 esac
315 print -u2 -f "# Record name is '%s'\n" "${recordname}"
317 typeset url_host="opensolaris.pastebin.ca"
318 typeset url_path="/raw/${recordname}"
319 typeset url="http://${url_host}${url_path}"
320 # I hereby curse Solaris for not having an entry for "http" in /etc/services
322 # open TCP channel
323 redirect {netfd}<> "/dev/tcp/${url_host}/80"
324 (( $? != 0 )) && { print -u2 -f $"%s: Could not open connection to %s.\n" "$0" "${url_host}" ; return 1 ; }
326 # send HTTP request
327 request="GET ${url_path} HTTP/1.1\r\n"
328 request+="Host: ${url_host}\r\n"
329 request+="User-Agent: ${http_user_agent}\r\n"
330 request+="Connection: close\r\n"
331 print -u${netfd} -- "${request}\r\n"
333 # collect response and send it to stdout
334 parse_http_response httpresponse <&${netfd}
335 cat_http_body "${httpresponse.transfer_encoding}" <&${netfd}
337 # close connection
338 redirect {netfd}<&-
340 print # add newline
342 return 0
345 function usage
347 OPTIND=0
348 getopts -a "${progname}" "${USAGE}" OPT '-?'
349 exit 2
352 # program start
353 builtin basename
354 builtin cat
355 builtin date
356 builtin uname
358 typeset progname="${ basename "${0}" ; }"
360 # HTTP protocol client identifer
361 typeset -r http_user_agent="shnote/ksh93 (2010-03-27; $(uname -s -r -p))"
363 # name of history log (the number after "history" is some kind of version
364 # counter to handle incompatible changes to the history file format)
365 typeset -r history_file="${HOME}/.shnote/history0.txt"
367 typeset -r shnote_usage=$'+
368 [-?\n@(#)\$Id: shnote (Roland Mainz) 2010-03-27 \$\n]
369 [-author?Roland Mainz <roland.mainz@nrubsig.org>]
370 [+NAME?shnote - read/write text data to internet clipboards]
371 [+DESCRIPTION?\bshnote\b is a small utilty which can read and write text
372 data to internet "clipboards" such as opensolaris.pastebin.ca.]
373 [+?The first arg \bmethod\b describes one of the methods, "put" saves a string
374 to the internet clipboard, returning an identifer and the full URL
375 where the data are stored. The method "get" retrives the raw
376 information using the identifer from the previous "put" action.
377 The method "hist" prints a history of transactions created with the
378 "put" method and the keys to retrive them again using the "get" method.]
379 [+?The second arg \bstring\b contains either the string data which should be
380 stored on the clipboard using the "put" method, the "get" method uses
381 this information as identifer to retrive the raw data from the
382 clipboard.]
384 method [ string ]
386 [+SEE ALSO?\bksh93\b(1), \brssread\b(1), \bshtwitter\b(1), \bshtinyurl\b(1), http://opensolaris.pastebin.ca]
389 while getopts -a "${progname}" "${shnote_usage}" OPT ; do
390 # printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|"
391 case ${OPT} in
392 *) usage ;;
393 esac
394 done
395 shift $((OPTIND-1))
397 # expecting at least one more argument, the single method below will do
398 # the checks for more arguments if needed ("put" and "get" methods need
399 # at least one extra argument, "hist" none).
400 (($# >= 1)) || usage
402 typeset method="$1"
403 shift
405 case "${method}" in
406 put) put_note_pastebin_ca "$@" ; exit $? ;;
407 get) get_note_pastebin_ca "$@" ; exit $? ;;
408 hist) print_history "$@" ; exit $? ;;
409 *) usage ;;
410 esac
412 fatal_error $"not reached."
413 # EOF.