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]
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
/xpg
6/bin
:/usr
/xpg
4/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
37 LC_MONETARY
="${LC_ALL}" \
38 LC_MESSAGES
="${LC_ALL}" \
39 LC_COLLATE
="${LC_ALL}" \
47 print
-u2 "${progname}: $*"
51 function encode_multipart_form_data
54 nameref content
="formdata.content"
55 integer numformelements
=${#formdata.form[*]}
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"
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
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"
83 # parse HTTP return code, cookies etc.
84 function parse_http_response
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
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:]]*/}"
119 function cat_http_body
122 typeset hexchunksize
="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
138 function history_write_record
140 # rec: history record:
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" \
156 "${rec.description}" \
159 "${rec.providertoken}" \
161 } >>"${history_file}"
166 function print_history
168 integer histfd
# http stream number
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>"
178 if [[ ! -f "${history_file}" ]] ; then
183 redirect
{histfd
}<> "${history_file}"
184 (( $?
!= 0 )) && { print
-u2 "Could not open history file." ; return 1 ; }
186 while read -u${histfd} line
; do
189 printf "( %s )\n" "${line}" |
read -C rec
191 if [[ "$1" == "-l" ]] ; then
194 printf "%q\t%q\t%q\n" "${rec.url}" "${rec.title}" "${rec.date}"
206 function put_note_pastebin_ca
208 # key to autheticate this script against pastebin.ca
209 typeset
-r pastebin_ca_key
="9CFXFyeNC3iga/vthok75kTBu5kSSLPD"
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"
227 integer content_length
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 ; }
259 print
-n -- "${request}\r\n"
260 print
-n -- "${content}\r\n"
264 parse_http_response httpresponse
<&${netfd}
265 response
="$(cat_http_body "${httpresponse.transfer_encoding}" <&${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
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
287 printf "ERROR: %s\n" "${response}"
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
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}."
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
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 ; }
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}
348 getopts -a "${progname}" "${USAGE}" OPT
'-?'
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
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}|"
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).
406 put
) put_note_pastebin_ca
"$@" ; exit $?
;;
407 get
) get_note_pastebin_ca
"$@" ; exit $?
;;
408 hist
) print_history
"$@" ; exit $?
;;
412 fatal_error $
"not reached."