1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
|
#!/bin/bash
# based on the API spec: https://litterbox.catbox.moe/tools.php
VERSION="0.0.1"
SELF="$( basename $0 )"
APIURL="https://litterbox.catbox.moe/resources/internals/api.php"
URLS=""
EXPIRE="1"
DELAY="${DELAY:-2}"
UPLOAD_COUNT=0
TMP="$( mktemp -t -d $SELF.XXXXXXXXXX )"
trap cleanup EXIT
die() {
echo "$SELF: fatal: $@" 1>&2
exit 1
}
warn() {
echo "$SELF: warning: $@" 1>&2
}
info() {
[ "$VERBOSE" = "1" ] && echo "$SELF: info: $@" 1>&2
}
check_path() {
type -p "$1" &>/dev/null
}
cleanup() {
[ "$TMP" != "" ] && rm -rf "$TMP"
}
usage() {
if ! check_path perldoc; then
warn "can't find perldoc on PATH, printing raw POD."
exec sed -n '/^=pod/,/^=cut/p' $0
fi
exec perldoc "$0"
}
manpage() {
exec pod2man --stderr -s1 -c"UrchlaysStuff" -r$VERSION "$0"
}
pluralize() {
echo -n "$1 $2"
[ "$1" != 1 ] && echo -n "s"
}
delay() {
info "sleeping $DELAY sec between $1"
sleep "$DELAY"
}
set_expiration() {
case "$1" in
1|12|24|72) ;; # OK
*) die "invalid expiration time, allowed values are: 1 12 24 72" ;;
esac
EXPIRE="$1"
}
add_url() {
if [ "$URLS" = "" ]; then
URLS="$1"
else
URLS+=" $1"
fi
: $(( UPLOAD_COUNT++ ))
}
copy_urls() {
if ! check_path xsel; then
warn "xsel not found in PATH, not copying URL(s) to clipboard"
return
fi
if [ "$DISPLAY" = "" ]; then
warn "DISPLAY not set in env, not copying URL(s) to clipboard"
return
fi
echo -n "$URLS" | xsel -i
}
# compress_file() gets called on both files and directories.
compress_file() {
local ext
local basefile
local is_dir
[ -d "$FILE" ] && is_dir=1
if [ "$ZIP" = "1" ]; then
ext=.zip
else
ext=.gz
fi
basefile="$( basename "$FILE" )"
# TODO: create a random tmp dir, don't use /tmp!
TMPFILE="${TMP:-/tmp}/$basefile$ext"
rm -f "$TMPFILE"
if [ "$ZIP" = "1" ]; then
zip -qr "$TMPFILE" "$FILE"
info "zipped $FILE as $TMPFILE"
else
if [ "$is_dir" = 1 ]; then
tar cfz "$TMPFILE" "$FILE"
info "tarred up $FILE as $TMPFILE"
else
gzip -9 < "$FILE" > "$TMPFILE"
info "gzipped $FILE as $TMPFILE"
fi
fi
FILE="$TMPFILE"
}
# TODO: curl options:
# --max-time --retry --retry-max-time
upload_file() {
local url
local use_compression
info "checking $FILE"
case "$FILE" in
*.exe|*.scr|*.cpl|*.doc*|*.jar)
warn "forbidden file extension, compression forced"
use_compression=1 ;;
*.zip|*.gz)
use_compression=0 ;;
*)
use_compression="$COMPRESS" ;;
esac
if [ "$use_compression" = 1 ]; then
# can't easily zip stdin, write it to a file
if [ "$FILE" = "-" ]; then
FILE="${TMP:-/tmp}/$SELF.stdin"
cat > "$FILE"
if [ "$?" != 0 ]; then
rm -f "$FILE"
return
fi
fi
compress_file # resets FILE, sets TMPFILE
fi
# at this point, we have an actual file, even if input was stdin,
# so we can encrypt it.
if [ "$ENC" = "1" ]; then
CRYPTFILE="$FILE.aes"
openssl enc -pbkdf2 -aes-256-cbc < "$FILE" > "$CRYPTFILE"
if [ "$?" != "0" ]; then
warn "encryption failed, skipping file"
return
fi
FILE="$CRYPTFILE"
fi
if [ "$URLS" != "" ]; then
delay "uploads"
fi
info "uploading $FILE"
url="$(
cat "$FILE" | \
curl --silent \
-F "reqtype=fileupload" \
-F "time=${EXPIRE}h" \
-F "fileToUpload=@-" \
"$APIURL"
)"
# TODO: check curl's exit status!
case "$url" in
https://litter.catbox.moe/*) add_url "$url" ;;
*) warn "upload failed" ;;
esac
}
download_and_decrypt() {
local url="$1"
info "downloading and decrypting $url"
curl --silent "$url" | openssl enc -d -pbkdf2 -aes-256-cbc
[ "$?" != 0 ] && warn "download/decrypt failed"
}
# main()
if [ "$1" = "--man" ]; then
manpage ; exit 0
fi
if [ "$1" = "--help" ]; then
usage ; exit 0
fi
if ! check_path curl; then
die "can't find curl on PATH (PATH is \"$PATH\")"
fi
# option string starts with a :, meaning we're responsible for
# printing our own error messages.
while getopts ":e:gzcvdo:" OPT; do
case "$OPT" in
e) set_expiration "$OPTARG"; opt_e=1 ;;
o) DLOUT="$OPTARG" ;;
g) GZIP=1 ;;
z) ZIP=1 ;;
c) ENC=1 ;;
v) VERBOSE=1 ;;
d) DOWNLOAD=1 ;;
*) die "invalid option -$OPTARG, try --help" ;;
esac
done
# getopts doesn't remove the args/options for us, so:
shift $(($OPTIND - 1))
# sanity check options and external binaries' existence.
[ "$GZIP" = 1 -a "$ZIP" = 1 ] && die "can't give both -g and -z"
[ "$GZIP" = 1 -o "$ZIP" = 1 ] && COMPRESS=1
if [ "$ZIP" = 1 ] && ! check_path zip; then
warn "can't find zip on PATH, disabling -z option"
ZIP=0
fi
if [ "$GZIP" = 1 ] && ! check_path gzip; then
warn "can't find gzip on PATH, disabling -g option"
GZIP=0
fi
if [ "$DOWNLOAD" = 1 -o "$ENC" = 1 ]; then
check_path openssl || die "can't find openssl on path, -c/-d won't work"
fi
# download mode (-d)
if [ "$DOWNLOAD" = 1 ]; then
[ "$GZIP" = 1 ] && warn "-g option ignored when using -d"
[ "$ZIP" = 1 ] && warn "-z option ignored when using -d"
[ "$ENC" = 1 ] && warn "-c option ignored when using -d"
[ "$opt_e" = 1 ] && warn "-e option ignored when using -d"
[ "$DLOUT" != "" ] && exec 1>"$DLOUT" || die "can't redirect stdout"
for arg; do
[ "$need_delay" = "1" ] && delay "downloads"
download_and_decrypt "$arg"
need_delay=1
done
exit 0
else
[ "$DLOUT" != "" ] && warn "-o option ignored when not using -d"
fi
info "expire time set to $( pluralize $EXPIRE hour )"
# upload mode
[ "$1" = "" ] && set -- -
for arg; do
TMPFILE=""
CRYPTFILE=""
if [ "$arg" = "-" -o -f "$arg" ]; then
FILE="$arg"
elif [ -d "$arg" ]; then
FILE="$arg"
compress_file # sets FILE
else
# broken symlink, device node, FIFO, ???
warn "$arg not a regular file, ignoring"
continue
fi
upload_file
# clean up if needed
if [ "$TMPFILE" != "" ]; then
info "removing temp file $TMPFILE"
rm -f "$TMPFILE"
fi
if [ "$TMPFILE" != "" ]; then
info "removing temp encrypted file $CRYPTFILE"
rm -f "$CRYPTFILE"
fi
done
if [ "$UPLOAD_COUNT" = "0" ]; then
die "no files uploaded"
fi
echo "$URLS"
copy_urls
info "$( pluralize $UPLOAD_COUNT file ) uploaded"
exit 0
# Rest of the file is perldoc.
: <<EOF
=pod
=head1 NAME
B<litterbox> - upload files to litterbox.catbox.moe
=head1 SYNOPSIS
B<litterbox> [-e I<hours>] [-g] [-z] [-c] [-v] I<file> <I<file ...>>
B<litterbox> -d [-o I<output>] I<url> <I<url ...>>
=head1 DESCRIPTION
B<litterbox> uploads files to (or downloads files from) the temporary
file storage site B<https://litterbox.catbox.moe>. The site is
similar to a "pastebin" site, except that it allows any type of
file (not just text), and uploads can be up to 1GB in size.
=head2 Uploading
Each I<file> can be a filename, a directory name, or "-" for standard
input. If no files are given, litterbox reads from standard input.
Directories are uploaded as B<.tar.gz> archives, by default. Files
can be optionally encrypted with AES-256.
Once a file is uploaded, its URL is printed on stdout (one per
line). If B<xsel>(1) is installed and X is running, URLs are also
copied to the X paste buffer (separated by spaces).
When uploading multiple files, a delay of 2 seconds is added between
uploads, to avoid hammering the site.
=head2 Downloading
Download mode is only for downloading AES-256 encrypted files that
were previously uploaded with B<litterbox>. Non-encrypted files can
simply be downloaded with B<wget>(1), B<curl>(1), a web browser, etc.
You will, of course, have to provide the correct decryption key
(password).
=head1 OPTIONS
=head2 Upload Options
=over 4
=item B<-e> I<hours>
Set expiration time in hours. Allowed values are B<1>, B<12>,
B<24>, or B<72>. Default is B<1>.
=item B<-g>
Upload gzipped files. This is automatically enabled
when uploading files whose names end in .exe, .scr,
.cpl, .doc*, .jar (unless B<-z> is given). Also, directories
are automatically tarred/gzipped (unless B<-z> is given).
=item B<-z>
Upload zipped files or dirs, rather than .gz or .tar.gz.
=item B<-c>
Upload encrypted file. Currently this means the file
is encrypted with 'openssl enc -pbkdf2 -aes-256-cbc', and
you're prompted for an encryption key. Whoever downloads
the encrypted file will have to decrypt it with the B<-d>
option, or else do it manually.
=back
=head2 Download Options
=over 4
=item B<-d>
Download and decrypt previously uploaded file(s) from URL(s).
Equivalent to downloading the file normally, then piping it
through:
openssl enc -d -pbkdf2 -aes-256-cbc
...which means the file will be printed to your terminal, unless you
use shell redirection or the B<-o> option. The upload options above (B<-e>
B<-g> B<-z> B<-t> B<-c>) are ignored in download mode.
=item B<-o> I<file>
Set output file for B<-d> option. Note that giving multiple URLs
will result in all the data being written to the same file. If you need
separate files, run B<litterbox> multiple times.
The B<-o> option is ignored in upload mode (when B<-d> is not used).
=back
=head2 General Options
=over 4
=item B<-v>
Verbose operation.
=item B<--help>
Print this help to standard output and exit. Since the help is long,
it will be passed through your pager.
=item B<--man>
Print this help in man page (troff) format to standard output and exit.
=back
=head1 NOTES
=over 2
=item B<->
B<litter.catbox.moe> is an amazingly useful service. B<Please> be
respectful of the site's owner, B<follow> the site's rules, and don't
abuse the service with excessive requests. You should also consider
supporting the site financially, by buying merch from its store, or
simply making a donation.
=item B<->
With the B<-c> and B<-d> options, you will be prompted interactively
for the encryption key (password), on the process's controlling tty.
This could pose a problem if there's no controlling tty. With
multiple files, you will be prompted separately for each.
=item B<->
Copying URLs to the copy/paste buffer only works under X11. It might
be possible to support gpm(8) for the console. If Wayland has an
xsel-like utility, it could be supported too (the author doesn't use
Wayland so it's not high on the priority list).
=back
=head1 AUTHOR
B. Watson <urchlay@slackware.uk>
=head1 COPYRIGHT
B<litterbox> is licensed under the WTFPL: do WTF you want to with this.
See http://www.wtfpl.net/txt/copying/ for details.
=cut
EOF
|