#!/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
}

usage() {
  if ! type -p perldoc &>/dev/null; then
    echo "$SELF: can't find perldoc on PATH, printing raw POD." 1>&2
    exec sed -n '/^=pod/,/^=cut/p' $0
  fi

  exec perldoc "$0"
}

manpage() {
  exec pod2man --stderr -s1 -c"UrchlaysStuff" -r$VERSION "$0"
}

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 ! xsel --version &>/dev/null; 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() {
  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
    info "sleeping $DELAY sec between uploads"
    sleep "$DELAY"
  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

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

shift $(($OPTIND - 1))

[ "$GZIP" = 1 -a "$ZIP" = 1 ] && die "can't give both -g and -z"
[ "$GZIP" = 1 -o "$ZIP" = 1 ] && COMPRESS=1

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
    download_and_decrypt "$arg"
  done
  exit 0
else
  [ "$DLOUT" != "" ] && warn "-o option ignored when not using -d"
fi

info "expire time set to $EXPIRE hour(s)"

[ "$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
    warn "$arg not a regular file, ignoring"
    continue
  fi

  upload_file

  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 "$UPLOAD_COUNT file(s) 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. If B<xsel>(1)
is installed, 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 .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 -o option. The upload options above (-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.

=back

Note: With the -c and -d option, you will be prompted interactively
for the encryption key (password), on the process's controlling tty.

=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
