#!/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 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 } 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 ;; *) 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. : < - upload files to litterbox.catbox.moe =head1 SYNOPSIS B [-e I] [-g] [-z] [-c] [-v] I > B -d [-o I] I > =head1 DESCRIPTION B uploads files to (or downloads files from) the temporary file storage site B. 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 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(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. Non-encrypted files can simply be downloaded with B(1), B(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 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 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 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 is an amazingly useful service. B be respectful of the site's owner, B 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 =head1 COPYRIGHT B is licensed under the WTFPL: do WTF you want to with this. See http://www.wtfpl.net/txt/copying/ for details. =cut EOF