Compare commits
1 commit
master
...
plugin-sup
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a6a56a11c7 |
76 changed files with 1115 additions and 15065 deletions
|
@ -1,10 +0,0 @@
|
|||
# specifies the linker for compiling to these targets
|
||||
# this needs to be done to allow cross compiling
|
||||
|
||||
# may need more entries for more architectures, if you run into
|
||||
# issues on something else then aarch64 musl open an issue and
|
||||
# point to this comment. This will no longer be necessary when
|
||||
# rust-lld is stabilizes.
|
||||
|
||||
[target.aarch64-unknown-linux-musl]
|
||||
rustflags = ["-Clinker=rust-lld"]
|
237
.env
237
.env
|
@ -1,237 +0,0 @@
|
|||
# Require username for HTTP Basic Authentication when
|
||||
# visiting the service. If basic auth username is set but
|
||||
# basic auth password is not, just leave the password field
|
||||
# empty when logging in. You can also just go to
|
||||
# https://username:password@yourserver.net or
|
||||
# https://username@yourserver.net if password is not set
|
||||
# instead of typing into the password
|
||||
# Default value: unset
|
||||
# export MICROBIN_BASIC_AUTH_USERNAME=
|
||||
|
||||
# Require password for HTTP Basic Authentication when
|
||||
# visiting the service. Will not have any affect unless
|
||||
# basic auth username is also set. If basic auth username is
|
||||
# set but basic auth password is not, just leave the
|
||||
# password field empty when logging in. You can also just go
|
||||
# to https://username:password@yourserver.net or
|
||||
# https://username@yourserver.net if password is not set
|
||||
# instead of typing into the password prompt.
|
||||
# Default value: unset
|
||||
# export MICROBIN_BASIC_AUTH_PASSWORD=
|
||||
|
||||
# Enables administrator interface at yourserver.com/admin/
|
||||
# if set, disables it if unset. If admin username is set but
|
||||
# admin password is not, just leave the password field empty
|
||||
# when logging in.
|
||||
# Default value: admin
|
||||
export MICROBIN_ADMIN_USERNAME=admin
|
||||
|
||||
# Enables administrator interface at yourserver.com/admin/
|
||||
# if set, disables it if unset. Will not have any affect
|
||||
# unless admin username is also set. If admin username is
|
||||
# set but admin password is not, just leave the password
|
||||
# field empty when logging in.
|
||||
# Default value: m1cr0b1n
|
||||
export MICROBIN_ADMIN_PASSWORD=m1cr0b1n
|
||||
|
||||
# Enables editable pastas. You will still be able to make
|
||||
# finalised pastas but there will be an extra checkbox to
|
||||
# make your new pasta editable from the pasta list or the
|
||||
# pasta view page.
|
||||
# Default value: true
|
||||
export MICROBIN_EDITABLE=true
|
||||
|
||||
# Replaces the default footer text with your own. If you
|
||||
# want to hide the footer, use the hide footer option instead.
|
||||
# Note that you can also embed HTML here, so you may want to escape
|
||||
# '<', '>' and so on.
|
||||
# export MICROBIN_FOOTER_TEXT=
|
||||
|
||||
# Hides the navigation bar on every page.
|
||||
# Default value: false
|
||||
export MICROBIN_HIDE_HEADER=false
|
||||
|
||||
# Hides the footer on every page.
|
||||
# Default value: false
|
||||
export MICROBIN_HIDE_FOOTER=false
|
||||
|
||||
# Hides the MicroBin logo from the navigation bar on every
|
||||
# page.
|
||||
# Default value: false
|
||||
export MICROBIN_HIDE_LOGO=false
|
||||
|
||||
# Disables the /pastalist endpoint, essentially making all
|
||||
# pastas private.
|
||||
# Default value: false
|
||||
export MICROBIN_NO_LISTING=false
|
||||
|
||||
# Enables syntax highlighting support. When creating a new
|
||||
# pasta, a new dropdown selector will be added where you can
|
||||
# select your pasta's syntax, or just leave it empty for no
|
||||
# highlighting.
|
||||
export MICROBIN_HIGHLIGHTSYNTAX=true
|
||||
|
||||
# Sets the port for the server will be listening on.
|
||||
# Default value: 8080
|
||||
export MICROBIN_PORT=8080
|
||||
|
||||
# Sets the bind address for the server will be listening on.
|
||||
# Both ipv4 and ipv6 are supported. Default value: "0.0.0.0".
|
||||
# Example value: "myserver.net", "127.0.0.1".
|
||||
export MICROBIN_BIND="0.0.0.0"
|
||||
|
||||
# Enables private pastas. Adds a new checkbox to make your
|
||||
# pasta private, which then won't show up on the pastalist
|
||||
# page. With the URL to your pasta, it will still be
|
||||
# accessible.
|
||||
# Default value: true
|
||||
export MICROBIN_PRIVATE=true
|
||||
|
||||
# DEPRECATED: Will be removed soon. If you want to change styling (incl. removal), use custom CSS variable instead.
|
||||
# Disables main CSS styling, just uses a few in-line
|
||||
# stylings for the layout. With this option you will lose
|
||||
# dark-mode support.
|
||||
export MICROBIN_PURE_HTML=false
|
||||
|
||||
# Sets the name of the directory where MicroBin creates
|
||||
# its database and stores attachments.
|
||||
# Default value: microbin_data
|
||||
export MICROBIN_DATA_DIR="microbin_data"
|
||||
|
||||
# Enables storing pasta data (not attachments and files) in
|
||||
# a JSON file instead of the SQLite database.
|
||||
# Default value: false
|
||||
export MICROBIN_JSON_DB=false
|
||||
|
||||
# Add the given public path prefix to all urls. This allows
|
||||
# you to host MicroBin behind a reverse proxy on a subpath.
|
||||
# Note that MicroBin itself still expects all routes to be
|
||||
# as without this option, and thus is unsuited if you are
|
||||
# running MicroBin directly. Default value: unset. Example
|
||||
# values: https://myserver.com/ or https://192.168.0.10:8080/
|
||||
# export MICROBIN_PUBLIC_PATH=
|
||||
|
||||
# Sets a shortened path to use when the user copies URL from
|
||||
# the application. This will also use shorter endpoints,
|
||||
# such as /p/ instead if /pasta/. Default value:
|
||||
# unset.Example value: https://b.in/ export
|
||||
# MICROBIN_SHORT_PATH=
|
||||
|
||||
# The password required for uploading, if read-only mode is enabled
|
||||
# Default value: unset
|
||||
# export MICROBIN_UPLOADER_PASSWORD=
|
||||
|
||||
# If set to true, authentication required for uploading
|
||||
# Default value: false
|
||||
export MICROBIN_READONLY=false
|
||||
|
||||
# Enables showing read count on pasta pages.
|
||||
# Default value: false
|
||||
export MICROBIN_SHOW_READ_STATS=true
|
||||
|
||||
# Adds your title of choice to the
|
||||
# navigation bar.
|
||||
# Default value: unset
|
||||
# export MICROBIN_TITLE=
|
||||
|
||||
# Number of workers MicroBin is allowed to have. Increase
|
||||
# this to the number of CPU cores you have if you want to go
|
||||
# beast mode, but for personal use one worker is enough.
|
||||
# Default value: 1.
|
||||
export MICROBIN_THREADS=1
|
||||
|
||||
# Sets the garbage collector time limit. Pastas not accessed
|
||||
# for N days are removed even if they are set to never
|
||||
# expire.
|
||||
# Default value: 90.
|
||||
# To turn off GC: 0.
|
||||
export MICROBIN_GC_DAYS=90
|
||||
|
||||
# Enables or disables the "Burn after" function
|
||||
# Default value: false
|
||||
export MICROBIN_ENABLE_BURN_AFTER=true
|
||||
|
||||
# Sets the default burn after setting on the main screen.
|
||||
# Default value: 0. Available expiration options: 1, 10,
|
||||
# 100, 1000, 10000, 0 (= no limit)
|
||||
export MICROBIN_DEFAULT_BURN_AFTER=0
|
||||
|
||||
# Changes the maximum width of the UI from 720 pixels to
|
||||
# 1080 pixels.
|
||||
# Default value: false
|
||||
export MICROBIN_WIDE=false
|
||||
|
||||
# Enables generating QR codes for pastas. Requires
|
||||
# the public path to also be set.
|
||||
# Default value: false
|
||||
export MICROBIN_QR=true
|
||||
|
||||
# Toggles "Never" expiry settings for pastas. Default
|
||||
# value: false
|
||||
export MICROBIN_ETERNAL_PASTA=false
|
||||
|
||||
# Enables "Read-only" uploads. These are unlisted and
|
||||
# unencrypted, but can be viewed without password if you
|
||||
# have the URL. Editing and removing requires password.
|
||||
# Default value: true
|
||||
export MICROBIN_ENABLE_READONLY=true
|
||||
|
||||
# Sets the default expiry time setting on the main screen.
|
||||
# Default value: 24hour Available expiration options: 1min,
|
||||
# 10min, 1hour, 24hour, 1week, never
|
||||
export MICROBIN_DEFAULT_EXPIRY=24hour
|
||||
|
||||
# Disables and hides the file upload option in the UI.
|
||||
# Default value: false
|
||||
export MICROBIN_NO_FILE_UPLOAD=false
|
||||
|
||||
# Replaced the built-in water.css stylesheet with the URL
|
||||
# you provide. Default value: unset. Example value:
|
||||
# https://myserver.net/public/mystyle.css
|
||||
# export MICROBIN_CUSTOM_CSS=
|
||||
|
||||
# Use short hash strings in the URLs instead of animal names
|
||||
# to make URLs shorter. Does not change the underlying data
|
||||
# stored, just how pastas are recalled.
|
||||
# Default value: false
|
||||
export MICROBIN_HASH_IDS=false
|
||||
|
||||
# Enables server-side encryption. This will add private
|
||||
# privacy level, where the user sends plain unencrypted data
|
||||
# (still secure, because you use HTTPS, right?), but the
|
||||
# server sees everything that the user submits, therefore
|
||||
# the user does not have complete and absolute protection.
|
||||
# Default value: false
|
||||
export MICROBIN_ENCRYPTION_CLIENT_SIDE=true
|
||||
|
||||
# Enables client-side encryption. This will add the secret
|
||||
# privacy level where the user's browser encrypts all data
|
||||
# with JavaScript before sending it over to MicroBin, which
|
||||
# encrypt the data once again on server side.
|
||||
# Default value: false
|
||||
export MICROBIN_ENCRYPTION_SERVER_SIDE=true
|
||||
|
||||
# Limit the maximum file size users can upload without
|
||||
# encryption. Default value: 256.
|
||||
export MICROBIN_MAX_FILE_SIZE_ENCRYPTED_MB=256
|
||||
|
||||
# Limit the maximum file size users can upload with
|
||||
# encryption (more strain on your server than without
|
||||
# encryption, so the limit should be lower. Secrets tend to
|
||||
# be tiny files usually anyways.) Default value: 2048.
|
||||
export MICROBIN_MAX_FILE_SIZE_UNENCRYPTED_MB=2048
|
||||
|
||||
# Disables the feature that checks for available updates
|
||||
# when opening the admin screen.
|
||||
# Default value: false
|
||||
export MICROBIN_DISABLE_UPDATE_CHECKING=false
|
||||
|
||||
# Disables telemetry if set to true.
|
||||
# Telemetry includes your configuration and helps development.
|
||||
# It does not include any sensitive data.
|
||||
# Default value: false
|
||||
export MICROBIN_DISABLE_TELEMETRY=false
|
||||
|
||||
# Enables listing your server in the public MicroBin server list.
|
||||
# Default value: false
|
||||
export MICROBIN_LIST_SERVER=false
|
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
|
@ -1,4 +0,0 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: szabodanika
|
||||
|
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,38 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
BIN
.github/index.png
vendored
BIN
.github/index.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 1.7 MiB |
BIN
.github/logo.png
vendored
BIN
.github/logo.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 43 KiB |
196
.github/workflows/release.yml
vendored
196
.github/workflows/release.yml
vendored
|
@ -1,196 +0,0 @@
|
|||
name: GitHub and Docker Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+*
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Publish to Github Relases
|
||||
outputs:
|
||||
rc: ${{ steps.check-tag.outputs.rc }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
cargo-flags: ""
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
use-cross: true
|
||||
cargo-flags: ""
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
use-cross: true
|
||||
cargo-flags: "--no-default-features"
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
cargo-flags: ""
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
cargo-flags: ""
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
cargo-flags: ""
|
||||
- target: i686-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
cargo-flags: ""
|
||||
- target: i686-pc-windows-msvc
|
||||
os: windows-latest
|
||||
use-cross: true
|
||||
cargo-flags: ""
|
||||
- target: armv7-unknown-linux-musleabihf
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
cargo-flags: ""
|
||||
- target: arm-unknown-linux-musleabihf
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
cargo-flags: ""
|
||||
- target: mips-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
cargo-flags: "--no-default-features"
|
||||
- target: mipsel-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
cargo-flags: "--no-default-features"
|
||||
- target: mips64-unknown-linux-gnuabi64
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
cargo-flags: "--no-default-features"
|
||||
- target: mips64el-unknown-linux-gnuabi64
|
||||
os: ubuntu-latest
|
||||
use-cross: true
|
||||
cargo-flags: "--no-default-features"
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Check Tag
|
||||
id: check-tag
|
||||
shell: bash
|
||||
run: |
|
||||
tag=${GITHUB_REF##*/}
|
||||
echo "::set-output name=version::$tag"
|
||||
if [[ "$tag" =~ [0-9]+.[0-9]+.[0-9]+$ ]]; then
|
||||
echo "::set-output name=rc::false"
|
||||
else
|
||||
echo "::set-output name=rc::true"
|
||||
fi
|
||||
|
||||
|
||||
- name: Install Rust Toolchain Components
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
toolchain: stable
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
|
||||
- name: Show Version Information (Rust, cargo, GCC)
|
||||
shell: bash
|
||||
run: |
|
||||
gcc --version || true
|
||||
rustup -V
|
||||
rustup toolchain list
|
||||
rustup default
|
||||
cargo -V
|
||||
rustc -V
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.use-cross }}
|
||||
command: build
|
||||
args: --locked --release --target=${{ matrix.target }} ${{ matrix.cargo-flags }}
|
||||
|
||||
- name: Build Archive
|
||||
shell: bash
|
||||
id: package
|
||||
env:
|
||||
target: ${{ matrix.target }}
|
||||
version: ${{ steps.check-tag.outputs.version }}
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
|
||||
bin=${GITHUB_REPOSITORY##*/}
|
||||
src=`pwd`
|
||||
dist=$src/dist
|
||||
name=$bin-$version-$target
|
||||
executable=target/$target/release/$bin
|
||||
|
||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||
executable=$executable.exe
|
||||
fi
|
||||
|
||||
mkdir $dist
|
||||
cp $executable $dist
|
||||
cd $dist
|
||||
|
||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||
archive=$dist/$name.zip
|
||||
7z a $archive *
|
||||
echo "::set-output name=archive::`pwd -W`/$name.zip"
|
||||
else
|
||||
archive=$dist/$name.tar.gz
|
||||
tar czf $archive *
|
||||
echo "::set-output name=archive::$archive"
|
||||
fi
|
||||
|
||||
- name: Publish Archive
|
||||
uses: softprops/action-gh-release@v0.1.15
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
draft: false
|
||||
files: ${{ steps.package.outputs.archive }}
|
||||
prerelease: ${{ steps.check-tag.outputs.rc == 'true' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
docker:
|
||||
name: Publish to Docker Hub
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
steps:
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ secrets.DOCKERHUB_REPO }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
build-args: |
|
||||
REPO=${{ github.repository }}
|
||||
VER=${{ github.ref_name }}
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
push: ${{ github.ref_type == 'tag' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
15
.gitignore
vendored
15
.gitignore
vendored
|
@ -1,15 +0,0 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
pasta_data/*
|
||||
microbin_data/*
|
||||
*.env
|
||||
**/**/microbin-data
|
3708
Cargo.lock
generated
3708
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
85
Cargo.toml
85
Cargo.toml
|
@ -1,71 +1,24 @@
|
|||
[package]
|
||||
name = "microbin"
|
||||
version = "2.0.4"
|
||||
edition = "2021"
|
||||
rust-version = "1.74.0"
|
||||
authors = ["Daniel Szabo <daniel@microbin.eu>"]
|
||||
license = "BSD-3-Clause"
|
||||
description = "Simple, performant, configurable, entirely self-contained Pastebin and URL shortener."
|
||||
readme = "README.md"
|
||||
homepage = "https://microbin.eu"
|
||||
repository = "https://github.com/szabodanika/microbin"
|
||||
keywords = ["pastebin", "filesharing", "microbin", "actix", "selfhosted"]
|
||||
categories = ["pastebins"]
|
||||
name="microbin"
|
||||
version="0.2.0"
|
||||
edition="2021"
|
||||
|
||||
[dependencies]
|
||||
actix-files = "0.6.6"
|
||||
actix-multipart = "0.7.2"
|
||||
actix-web = { version = "4", default-features = false, features = [
|
||||
"compat","compress-brotli", "compress-gzip", "cookies", "http2", "macros", "unicode"] }
|
||||
actix-web-httpauth = "0.8.2"
|
||||
askama = "0.10"
|
||||
askama-filters = { version = "0.1.3", features = ["chrono"] }
|
||||
bytesize = { version = "1.1", features = ["serde"] }
|
||||
chrono = "0.4.19"
|
||||
clap = { version = "3.1.12", features = ["derive", "env"] }
|
||||
env_logger = "0.9.0"
|
||||
actix-web="4"
|
||||
actix-files="0.6.0"
|
||||
serde={ version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.80"
|
||||
askama="0.10"
|
||||
askama-filters={ version = "0.1.3", features = ["chrono"] }
|
||||
chrono="0.4.19"
|
||||
rand="0.8.5"
|
||||
linkify="0.8.1"
|
||||
clap={ version = "3.1.12", features = ["derive"] }
|
||||
actix-multipart = "0.4.0"
|
||||
futures = "0.3"
|
||||
harsh = "0.2"
|
||||
html-escape = "0.2.13"
|
||||
sanitize-filename = "0.3.0"
|
||||
log = "0.4"
|
||||
env_logger = "0.9.0"
|
||||
actix-web-httpauth = "0.6.0"
|
||||
lazy_static = "1.4.0"
|
||||
linkify = "0.10.0"
|
||||
log = "0.4.21"
|
||||
magic-crypt = "3.1.13"
|
||||
mime_guess = "2.0.4"
|
||||
once_cell = "1.19.0"
|
||||
qrcode-generator = "4.1.9"
|
||||
rand = "0.8.5"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["charset",
|
||||
"http2", "macos-system-configuration", "json", "blocking"] }
|
||||
rusqlite = { version = "0.32", features = ["bundled"], optional = true }
|
||||
rust-embed = "8.3.0"
|
||||
|
||||
# The rustls-rustcrypto version must support the rustls version and the
|
||||
# rustls version must match the one expected by reqwest;
|
||||
rustls = { version = "0.23", default-features = false, features = ["custom-provider"], optional = true }
|
||||
rustls-rustcrypto = { version = "0.0.2-alpha", optional = true }
|
||||
|
||||
sanitize-filename = "0.5.0"
|
||||
serde_json = "1.0.114"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
syntect = { version = "5.2.0", default-features = false }
|
||||
webpki-roots = { version = "0.26", optional = true }
|
||||
|
||||
[dependencies.openssl]
|
||||
version = "0.10.64"
|
||||
features = ["vendored"]
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = ["__default-tls", "__zstd", "__syntect-fast", "dep:rusqlite"]
|
||||
no-c-deps = ["__rustcrypto-tls", "__syntect-rust"]
|
||||
|
||||
__default-tls = ["reqwest/default-tls", "dep:openssl"]
|
||||
__rustcrypto-tls = ["reqwest/rustls-tls-manual-roots-no-provider", "dep:rustls", "dep:rustls-rustcrypto", "webpki-roots"]
|
||||
__syntect-fast = ["syntect/default-onig"]
|
||||
__syntect-rust = ["syntect/default-fancy"]
|
||||
__zstd = ["actix-web/compress-zstd"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = true
|
||||
rutie = "0.8.4"
|
60
Dockerfile
60
Dockerfile
|
@ -1,41 +1,31 @@
|
|||
FROM rust:latest as build
|
||||
# latest rust will be used to build the binary
|
||||
FROM rust:latest as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
apt-get update &&\
|
||||
apt-get -y install ca-certificates tzdata
|
||||
# the temporary directory where we build
|
||||
WORKDIR /usr/src/microbin
|
||||
|
||||
# copy sources to /usr/src/microbin on the temporary container
|
||||
COPY . .
|
||||
|
||||
# run release build
|
||||
RUN cargo build --release
|
||||
|
||||
# create final container using slim version of debian
|
||||
FROM debian:buster-slim
|
||||
|
||||
# microbin will be in /usr/local/bin/microbin/
|
||||
WORKDIR /usr/local/bin
|
||||
|
||||
# copy built exacutable
|
||||
COPY --from=builder /usr/src/microbin/target/release/microbin /usr/local/bin/microbin
|
||||
|
||||
# copy /static folder containing the stylesheets
|
||||
COPY --from=builder /usr/src/microbin/static /usr/local/bin/static
|
||||
|
||||
# Install Ruby (no need if you're disabling all plugins)
|
||||
RUN \
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI=true \
|
||||
cargo build --release
|
||||
apt-get update && \
|
||||
apt-get install -y ruby
|
||||
|
||||
# https://hub.docker.com/r/bitnami/minideb
|
||||
FROM bitnami/minideb:latest
|
||||
|
||||
# microbin will be in /app
|
||||
WORKDIR /app
|
||||
|
||||
RUN mkdir -p /usr/share/zoneinfo
|
||||
|
||||
# copy time zone info
|
||||
COPY --from=build \
|
||||
/usr/share/zoneinfo \
|
||||
/usr/share/
|
||||
|
||||
COPY --from=build \
|
||||
/etc/ssl/certs/ca-certificates.crt \
|
||||
/etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
# copy built executable
|
||||
COPY --from=build \
|
||||
/app/target/release/microbin \
|
||||
/usr/bin/microbin
|
||||
|
||||
# Expose webport used for the webserver to the docker runtime
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["microbin"]
|
||||
# run the binary
|
||||
CMD ["microbin"]
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2022-2023, Dániel Szabó
|
||||
Copyright (c) 2022, Dániel Szabó
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
66
README.MD
Normal file
66
README.MD
Normal file
|
@ -0,0 +1,66 @@
|
|||
# MicroBin
|
||||
|
||||

|
||||
|
||||
MicroBin is a super tiny and simple self hosted pastebin app written in Rust. The executable is around 6MB and it uses 2MB memory (plus your pastas, because they are all stored in the memory at the moment).
|
||||
|
||||
### Features
|
||||
- Is very small
|
||||
- File uploads
|
||||
- Raw pasta content (/raw/[animals])
|
||||
- URL shortening and redirection
|
||||
- Automatic dark mode (follows system preferences)
|
||||
- Very simple database (json + files) for portability and easy backups
|
||||
- Animal names instead of random numbers for pasta identifiers (64 animals)
|
||||
- Automatically expiring pastas
|
||||
- Never expiring pastas
|
||||
- Listing and manually removing pastas (/pastalist)
|
||||
- Very little CSS and absolutely no JS (see [water.css](https://github.com/kognise/water.css))
|
||||
|
||||
### Installation
|
||||
Simply clone the repository, build it with `cargo build --release` and run the `microbin` executable in the created `target/release/` directory. It will start on port 8080. You can change the port with `-p` or `--port` CL arguments. For other arguments see [the Wiki](https://github.com/szabodanika/microbin/wiki).
|
||||
|
||||
To install it as a service on your Linux machine, create a file called `/etc/systemd/system/microbin.service`, paste this into it with the `[username]` and `[path to installation directory]` replaced with the actual values.
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=MicroBin
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
User=[username]
|
||||
RootDirectory=/
|
||||
WorkingDirectory=[path to installation directory]
|
||||
ExecStart=[path to installation directory]/target/release/microbin
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Then start the service with `systemctl start microbin` and enable it on boot with `systemctl enable microbin`.
|
||||
|
||||
### Create Pasta with cURL
|
||||
|
||||
Simple text Pasta: `curl -d "expiration=10min&content=This is a test pasta" -X POST https://microbin.myserver.com/create`
|
||||
|
||||
File contents: `curl -d "expiration=10min&content=$( < mypastafile.txt )" -X POST https://microbin.myserver.com/create`
|
||||
|
||||
Available expiration options:
|
||||
`1min`, `10min`, `1hour`, `24hour`, `1week`, `never`
|
||||
|
||||
Use cURL to read the pasta: `curl https://microbin.myserver.com/rawpasta/fish-pony-crow`,
|
||||
|
||||
or to download the pasta: `curl https://microbin.myserver.com/rawpasta/fish-pony-crow > output.txt` (use /file instead of /rawpasta to download attached file).
|
||||
|
||||
|
||||
### Needed improvements
|
||||
- ~~Persisting pastas on disk (currently they are lost on restart)~~ (added on 2 May 2022)
|
||||
- ~~Configuration with command line arguments (ports, enable-disable pasta list, footer, etc)~~ (added on 7 May 2022)
|
||||
- ~~File uploads~~ (added on 2 May 2022)
|
||||
- ~~URL shortening~~ (added on 23 April 2022)
|
||||
- Removing pasta after N reads
|
||||
- CLI tool (beyond wget)
|
||||
- Better instructions and documentation - on GitHub and built in
|
||||
|
77
README.md
77
README.md
|
@ -1,77 +0,0 @@
|
|||
|
||||

|
||||
|
||||
# MicroBin
|
||||
|
||||

|
||||
[](https://crates.io/crates/microbin)
|
||||
[](https://hub.docker.com/r/danielszabo99/microbin)
|
||||
[](https://img.shields.io/docker/pulls/danielszabo99/microbin?label=Docker%20pulls)
|
||||
|
||||
MicroBin is a super tiny, feature-rich, configurable, self-contained and self-hosted paste bin web application. It is very easy to set up and use, and will only require a few megabytes of memory and disk storage. It takes only a couple minutes to set it up, why not give it a try now?
|
||||
|
||||
### Check out the Public Test Server at [pub.microbin.eu](https://pub.microbin.eu)!
|
||||
|
||||
### Or host MicroBin yourself
|
||||
|
||||
Run our quick docker setup script ([DockerHub](https://hub.docker.com/r/danielszabo99/microbin)):
|
||||
```bash
|
||||
bash <(curl -s https://microbin.eu/docker.sh)
|
||||
```
|
||||
|
||||
Or install it manually from [Cargo](https://crates.io/crates/microbin):
|
||||
|
||||
```bash
|
||||
cargo install microbin;
|
||||
curl -L -O https://raw.githubusercontent.com/szabodanika/microbin/master/.env;
|
||||
source .env;
|
||||
microbin
|
||||
```
|
||||
|
||||
On our website [microbin.eu](https://microbin.eu), you will find the following:
|
||||
|
||||
- [Screenshots](https://microbin.eu/screenshots/)
|
||||
- [Guide and Documentation](https://microbin.eu/docs/intro)
|
||||
- [Donations and Sponsorships](https://microbin.eu/sponsorship)
|
||||
- [Roadmap](https://microbin.eu/roadmap)
|
||||
|
||||
## Features
|
||||
|
||||
- Entirely self-contained executable, MicroBin is a single file!
|
||||
- Server-side and client-side encryption
|
||||
- File uploads (e.g. `server.com/file/pig-dog-cat`)
|
||||
- Raw text serving (e.g. `server.com/raw/pig-dog-cat`)
|
||||
- QR code support
|
||||
- URL shortening and redirection
|
||||
- Animal names instead of random numbers for upload identifiers (64 animals)
|
||||
- SQLite and JSON database support
|
||||
- Private and public, editable and uneditable, automatically and never expiring uploads
|
||||
- Automatic dark mode and custom styling support with very little CSS and only vanilla JS (see [`water.css`](https://github.com/kognise/water.css))
|
||||
- And much more!
|
||||
|
||||
## What is an upload?
|
||||
|
||||
In MicroBin, an upload can be:
|
||||
|
||||
- A text that you want to paste from one machine to another, e.g. some code,
|
||||
- A file that you want to share, e.g. a video that is too large for Discord, a zip with a code project in it or an image,
|
||||
- A URL redirection.
|
||||
|
||||
## When is MicroBin useful?
|
||||
|
||||
You can use MicroBin:
|
||||
|
||||
- To send long texts to other people,
|
||||
- To send large files to other people,
|
||||
- To share secrets or sensitive documents securely,
|
||||
- As a URL shortener/redirect service,
|
||||
- To serve content on the web, eg . configuration files for testing, images, or any other file content using the Raw functionality,
|
||||
- To move files between your desktop and a server you access from the console,
|
||||
- As a "postbox" service where people can upload their files or texts, but they cannot see or remove what others sent you,
|
||||
- Or even to take quick notes.
|
||||
|
||||
...and many other things, why not get creative?
|
||||
|
||||
MicroBin and MicroBin.eu are available under the [BSD 3-Clause License](LICENSE).
|
||||
|
||||
© Dániel Szabó 2022-2024
|
12
SECURITY.md
12
SECURITY.md
|
@ -1,12 +0,0 @@
|
|||
# Security Policy
|
||||
|
||||
## Version Support
|
||||
|
||||
Currently we only have capacity to support the latest version of MicroBin. We recommend that you always update to the newest one and check our pages regularly for announcements.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Security vulnerabilities can be reported directly to the developer/maintainer at d@szab.eu.
|
||||
|
||||
Sensitive information may be GPG encrypted with my public key available at
|
||||
https://szab.eu/assets/files/daniel-szabo-pub.asc.
|
47
compose.yaml
47
compose.yaml
|
@ -1,47 +0,0 @@
|
|||
services:
|
||||
microbin:
|
||||
image: danielszabo99/microbin:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "${MICROBIN_PORT}:8080"
|
||||
volumes:
|
||||
- ./microbin-data:/app/microbin_data
|
||||
environment:
|
||||
MICROBIN_BASIC_AUTH_USERNAME: ${MICROBIN_BASIC_AUTH_USERNAME}
|
||||
MICROBIN_BASIC_AUTH_PASSWORD: ${MICROBIN_BASIC_AUTH_PASSWORD}
|
||||
MICROBIN_ADMIN_USERNAME: ${MICROBIN_ADMIN_USERNAME}
|
||||
MICROBIN_ADMIN_PASSWORD: ${MICROBIN_ADMIN_PASSWORD}
|
||||
MICROBIN_EDITABLE: ${MICROBIN_EDITABLE}
|
||||
MICROBIN_FOOTER_TEXT: ${MICROBIN_FOOTER_TEXT}
|
||||
MICROBIN_HIDE_FOOTER: ${MICROBIN_HIDE_FOOTER}
|
||||
MICROBIN_HIDE_HEADER: ${MICROBIN_HIDE_HEADER}
|
||||
MICROBIN_HIDE_LOGO: ${MICROBIN_HIDE_LOGO}
|
||||
MICROBIN_NO_LISTING: ${MICROBIN_NO_LISTING}
|
||||
MICROBIN_HIGHLIGHTSYNTAX: ${MICROBIN_HIGHLIGHTSYNTAX}
|
||||
MICROBIN_BIND: ${MICROBIN_BIND}
|
||||
MICROBIN_PRIVATE: ${MICROBIN_PRIVATE}
|
||||
MICROBIN_PURE_HTML: ${MICROBIN_PURE_HTML}
|
||||
MICROBIN_DATA_DIR: ${MICROBIN_DATA_DIR}
|
||||
MICROBIN_JSON_DB: ${MICROBIN_JSON_DB}
|
||||
MICROBIN_PUBLIC_PATH: ${MICROBIN_PUBLIC_PATH}
|
||||
MICROBIN_SHORT_PATH: ${MICROBIN_SHORT_PATH}
|
||||
MICROBIN_READONLY: ${MICROBIN_READONLY}
|
||||
MICROBIN_UPLOADER_PASSWORD: ${MICROBIN_UPLOADER_PASSWORD}
|
||||
MICROBIN_SHOW_READ_STATS: ${MICROBIN_SHOW_READ_STATS}
|
||||
MICROBIN_TITLE: ${MICROBIN_TITLE}
|
||||
MICROBIN_THREADS: ${MICROBIN_THREADS}
|
||||
MICROBIN_GC_DAYS: ${MICROBIN_GC_DAYS}
|
||||
MICROBIN_ENABLE_BURN_AFTER: ${MICROBIN_ENABLE_BURN_AFTER}
|
||||
MICROBIN_DEFAULT_BURN_AFTER: ${MICROBIN_DEFAULT_BURN_AFTER}
|
||||
MICROBIN_WIDE: ${MICROBIN_WIDE}
|
||||
MICROBIN_QR: ${MICROBIN_QR}
|
||||
MICROBIN_ETERNAL_PASTA: ${MICROBIN_ETERNAL_PASTA}
|
||||
MICROBIN_ENABLE_READONLY: ${MICROBIN_ENABLE_READONLY}
|
||||
MICROBIN_DEFAULT_EXPIRY: ${MICROBIN_DEFAULT_EXPIRY}
|
||||
MICROBIN_NO_FILE_UPLOAD: ${MICROBIN_NO_FILE_UPLOAD}
|
||||
MICROBIN_CUSTOM_CSS: ${MICROBIN_CUSTOM_CSS}
|
||||
MICROBIN_HASH_IDS: ${MICROBIN_HASH_IDS}
|
||||
MICROBIN_ENCRYPTION_CLIENT_SIDE: ${MICROBIN_ENCRYPTION_CLIENT_SIDE}
|
||||
MICROBIN_ENCRYPTION_SERVER_SIDE: ${MICROBIN_ENCRYPTION_SERVER_SIDE}
|
||||
MICROBIN_MAX_FILE_SIZE_ENCRYPTED_MB: ${MICROBIN_MAX_FILE_SIZE_ENCRYPTED_MB}
|
||||
MICROBIN_MAX_FILE_SIZE_UNENCRYPTED_MB: ${MICROBIN_MAX_FILE_SIZE_UNENCRYPTED_MB}
|
|
@ -1,35 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Check if wget is installed; if not, try to use curl
|
||||
if ! command -v wget &> /dev/null
|
||||
then
|
||||
download_command="curl -O"
|
||||
else
|
||||
download_command="wget"
|
||||
fi
|
||||
|
||||
# Get installation directory from user
|
||||
echo -e "\033[1mEnter installation directory (default is /usr/share/microbin):\033[0m"
|
||||
read install_dir
|
||||
install_dir=${install_dir:-/usr/share/microbin}
|
||||
|
||||
# Create directory and download files
|
||||
mkdir -p $install_dir
|
||||
cd $install_dir
|
||||
$download_command https://raw.githubusercontent.com/szabodanika/microbin/master/.env
|
||||
$download_command https://raw.githubusercontent.com/szabodanika/microbin/master/compose.yaml
|
||||
|
||||
# Get public path URL and port from user
|
||||
echo -e "\033[1mEnter public path URL (e.g. https://microbin.myserver.net or http://localhost:8080):\033[0m"
|
||||
read public_path
|
||||
|
||||
echo -e "\033[1mEnter port number (default is 8080):\033[0m"
|
||||
read port
|
||||
port=${port:-8080}
|
||||
|
||||
# Update environment variables in .env file
|
||||
sed -i "s|MICROBIN_PUBLIC_PATH=.*|MICROBIN_PUBLIC_PATH=${public_path}|" .env
|
||||
sed -i "s|MICROBIN_PORT=.*|MICROBIN_PORT=${port}|" .env
|
||||
|
||||
# Start Microbin using Docker Compose
|
||||
docker compose --env-file .env up --detach
|
BIN
git/index.png
Normal file
BIN
git/index.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 480 KiB |
78
plugins/BasicSyntaxHighlighter.rb
Normal file
78
plugins/BasicSyntaxHighlighter.rb
Normal file
|
@ -0,0 +1,78 @@
|
|||
module MBP
|
||||
module BasicSyntaxHighlighter
|
||||
|
||||
# Plugin Properties
|
||||
|
||||
def self.get_id()
|
||||
"BasicSyntaxHighlighter"
|
||||
end
|
||||
|
||||
def self.get_name()
|
||||
"Basic Syntax Highlighter Plugin"
|
||||
end
|
||||
|
||||
def self.get_version()
|
||||
"1.0.0"
|
||||
end
|
||||
|
||||
def self.get_author()
|
||||
"Daniel Szabo"
|
||||
end
|
||||
|
||||
def self.get_webpage()
|
||||
"https://github.com/szabodanika/microbin"
|
||||
end
|
||||
|
||||
def self.get_description()
|
||||
"This plugin will simply color keywords and special characters in four different colors based on some very basic RegEx - it is meant to univesally make code pastas more readable but is not a robust syntax highlighter solution."
|
||||
end
|
||||
|
||||
# Plugin Event Hooks
|
||||
|
||||
def self.init()
|
||||
# Ignore event
|
||||
"OK"
|
||||
end
|
||||
|
||||
def self.on_pasta_created(content)
|
||||
# We do not modify stored content
|
||||
return content
|
||||
end
|
||||
|
||||
def self.on_pasta_read(content)
|
||||
|
||||
tokens = {
|
||||
|
||||
"orchid" => [/([0-9])/, /([t|T][r|R][u|U][e|E]|[f|F][a|A][l|L][s|S][e|E])/],
|
||||
|
||||
"palevioletred" => ['(', ')', '{', '}', '[', ']'],
|
||||
|
||||
"royalblue" => [/(\s(for|while|do|select|async|await|mut|break|continue|in|as|switch|let|fn|async|if|else|elseif|new|switch|match|case|default|public|protected|private|return|class|interface|static|final|const|var|int|integer|boolean|float|double|module|def|end|void))(?![a-z])/],
|
||||
|
||||
"mediumorchid" => [/(:|\.|;|=|>|<|\?|!|#|%|@|\^|&|\*|\|)/],
|
||||
|
||||
"mediumseagreen" => [/(\".*\")/, /(\'.*\')/]
|
||||
|
||||
};
|
||||
|
||||
tokens.each { | color, tokens |
|
||||
for token in tokens do
|
||||
if(token.class == String)
|
||||
content.gsub!(token, "$$#{color}$$" + token + "$$/#{color}$$")
|
||||
elsif
|
||||
content.gsub!(token, "$$#{color}$$" + '\1' + "$$/#{color}$$")
|
||||
end
|
||||
end
|
||||
};
|
||||
|
||||
tokens.each { | color, tokens |
|
||||
content.gsub!("$$#{color}$$", "<span style='color:#{color}'>");
|
||||
content.gsub!("$$/#{color}$$", "</span>");
|
||||
};
|
||||
|
||||
return content
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
78
plugins/MBPlugin.rb
Normal file
78
plugins/MBPlugin.rb
Normal file
|
@ -0,0 +1,78 @@
|
|||
require 'rutie'
|
||||
|
||||
module MB
|
||||
|
||||
class MBPlugin
|
||||
# Plugin Properties
|
||||
|
||||
def self.get_id()
|
||||
"[Enter Plugin ID]"
|
||||
end
|
||||
|
||||
def self.get_name()
|
||||
"[Eenter Plugin Name]"
|
||||
end
|
||||
|
||||
def self.get_version()
|
||||
"1.0.0"
|
||||
end
|
||||
|
||||
def self.get_author()
|
||||
"[Enter Author name]"
|
||||
end
|
||||
|
||||
def self.get_webpage()
|
||||
"[Enter Web URL]"
|
||||
end
|
||||
|
||||
def self.get_description()
|
||||
"[Enter Description]"
|
||||
end
|
||||
|
||||
# Plugin Event Hooks
|
||||
|
||||
def self.init()
|
||||
raise "Operation not supported";
|
||||
end
|
||||
|
||||
def self.on_pasta_deleted(id, content, created, expiration, file)
|
||||
raise "Operation not supported";
|
||||
end
|
||||
|
||||
def self.on_pasta_expired(id, content, created, expiration, file)
|
||||
raise "Operation not supported";
|
||||
end
|
||||
|
||||
def self.on_pasta_created(id, content, created, expiration, file)
|
||||
raise "Operation not supported";
|
||||
end
|
||||
|
||||
def self.on_pasta_read(id, content, created, expiration, file)
|
||||
raise "Operation not supported";
|
||||
end
|
||||
|
||||
# Rust Function Calls
|
||||
|
||||
def self.init()
|
||||
raise "Operation not supported";
|
||||
end
|
||||
|
||||
def self.P=on_pasta_deleted(id, content, created, expiration, file)
|
||||
raise "Operation not supported";
|
||||
end
|
||||
|
||||
def self.on_pasta_expired(id, content, created, expiration, file)
|
||||
raise "Operation not supported";
|
||||
end
|
||||
|
||||
def self.on_pasta_created(id, content, created, expiration, file)
|
||||
raise "Operation not supported";
|
||||
end
|
||||
|
||||
def self.on_pasta_read(id, content, created, expiration, file)
|
||||
raise "Operation not supported";
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
42
plugins/helloWorld.rb
Normal file
42
plugins/helloWorld.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
module MBP
|
||||
class HelloWorld < MBPlugin
|
||||
|
||||
def self.get_id()
|
||||
"HelloWorld"
|
||||
end
|
||||
|
||||
def self.get_name()
|
||||
"Hello World Plugin"
|
||||
end
|
||||
|
||||
def self.get_version()
|
||||
"1.0.0"
|
||||
end
|
||||
|
||||
def self.get_description()
|
||||
"This is just a demo plugin. It does not do anything."
|
||||
end
|
||||
|
||||
def self.get_author()
|
||||
"Daniel Szabo"
|
||||
end
|
||||
|
||||
def self.get_webpage()
|
||||
"https://github.com/szabodanika/microbin"
|
||||
end
|
||||
|
||||
def self.init()
|
||||
# Ignore event
|
||||
"OK"
|
||||
end
|
||||
|
||||
def self.on_pasta_created(content)
|
||||
return content
|
||||
end
|
||||
|
||||
def self.on_pasta_read(content)
|
||||
return content
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
services:
|
||||
- type: web
|
||||
name: microbin
|
||||
plan: free
|
||||
numInstances: 1
|
||||
env: rust
|
||||
repo: https://github.com/szabodanika/microbin.git
|
||||
buildCommand: cargo build --release
|
||||
startCommand: ./target/release/microbin --editable --highlightsyntax
|
48
src/animalnumbers.rs
Normal file
48
src/animalnumbers.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
const ANIMAL_NAMES: &[&str] = &[
|
||||
"ant", "eel", "mole", "sloth", "ape", "emu", "monkey", "snail", "bat", "falcon", "mouse",
|
||||
"snake", "bear", "fish", "otter", "spider", "bee", "fly", "parrot", "squid", "bird", "fox",
|
||||
"panda", "swan", "bison", "frog", "pig", "tiger", "camel", "gecko", "pigeon", "toad", "cat",
|
||||
"goat", "pony", "turkey", "cobra", "goose", "pug", "turtle", "crow", "hawk", "rabbit", "viper",
|
||||
"deer", "horse", "rat", "wasp", "dog", "jaguar", "raven", "whale", "dove", "koala", "seal",
|
||||
"wolf", "duck", "lion", "shark", "worm", "eagle", "lizard", "sheep", "zebra",
|
||||
];
|
||||
|
||||
pub fn to_animal_names(mut number: u64) -> String {
|
||||
let mut result: Vec<&str> = Vec::new();
|
||||
|
||||
if number == 0 {
|
||||
return ANIMAL_NAMES[0].parse().unwrap();
|
||||
}
|
||||
|
||||
// max 4 animals so 6 * 6 = 64 bits
|
||||
let mut power = 6;
|
||||
loop {
|
||||
let digit = number / ANIMAL_NAMES.len().pow(power) as u64;
|
||||
if !(result.is_empty() && digit == 0) {
|
||||
result.push(ANIMAL_NAMES[digit as usize]);
|
||||
}
|
||||
number -= digit * ANIMAL_NAMES.len().pow(power) as u64;
|
||||
if power > 0 {
|
||||
power -= 1;
|
||||
} else if power == 0 || number == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.join("-")
|
||||
}
|
||||
|
||||
pub fn to_u64(animal_names: &str) -> u64 {
|
||||
let mut result: u64 = 0;
|
||||
|
||||
let animals: Vec<&str> = animal_names.split("-").collect();
|
||||
|
||||
let mut pow = animals.len();
|
||||
for i in 0..animals.len() {
|
||||
pow -= 1;
|
||||
result += (ANIMAL_NAMES.iter().position(|&r| r == animals[i]).unwrap()
|
||||
* ANIMAL_NAMES.len().pow(pow as u32)) as u64;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
234
src/args.rs
234
src/args.rs
|
@ -1,234 +0,0 @@
|
|||
use clap::Parser;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Serialize;
|
||||
use std::convert::Infallible;
|
||||
use std::fmt;
|
||||
use std::net::IpAddr;
|
||||
use std::str::FromStr;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ARGS: Args = Args::parse();
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone, Serialize)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
#[clap(long, env = "MICROBIN_BASIC_AUTH_USERNAME")]
|
||||
pub auth_basic_username: Option<String>,
|
||||
|
||||
#[clap(long, env = "MICROBIN_BASIC_AUTH_PASSWORD")]
|
||||
pub auth_basic_password: Option<String>,
|
||||
|
||||
#[clap(long, env = "MICROBIN_ADMIN_USERNAME", default_value = "admin")]
|
||||
pub auth_admin_username: String,
|
||||
|
||||
#[clap(long, env = "MICROBIN_ADMIN_PASSWORD", default_value = "m1cr0b1n")]
|
||||
pub auth_admin_password: String,
|
||||
|
||||
#[clap(long, env = "MICROBIN_EDITABLE")]
|
||||
pub editable: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_FOOTER_TEXT")]
|
||||
pub footer_text: Option<String>,
|
||||
|
||||
#[clap(long, env = "MICROBIN_HIDE_FOOTER")]
|
||||
pub hide_footer: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_HIDE_HEADER")]
|
||||
pub hide_header: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_HIDE_LOGO")]
|
||||
pub hide_logo: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_NO_LISTING")]
|
||||
pub no_listing: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_HIGHLIGHTSYNTAX")]
|
||||
pub highlightsyntax: bool,
|
||||
|
||||
#[clap(short, long, env = "MICROBIN_PORT", default_value_t = 8080)]
|
||||
pub port: u16,
|
||||
|
||||
#[clap(short, long, env="MICROBIN_BIND", default_value_t = IpAddr::from([0, 0, 0, 0]))]
|
||||
pub bind: IpAddr,
|
||||
|
||||
#[clap(long, env = "MICROBIN_PRIVATE")]
|
||||
pub private: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_PURE_HTML")]
|
||||
pub pure_html: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_JSON_DB")]
|
||||
pub json_db: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_PUBLIC_PATH")]
|
||||
pub public_path: Option<PublicUrl>,
|
||||
|
||||
#[clap(long, env = "MICROBIN_SHORT_PATH")]
|
||||
pub short_path: Option<PublicUrl>,
|
||||
|
||||
#[clap(long, env = "MICROBIN_UPLOADER_PASSWORD")]
|
||||
pub uploader_password: Option<String>,
|
||||
|
||||
#[clap(long, env = "MICROBIN_READONLY")]
|
||||
pub readonly: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_SHOW_READ_STATS")]
|
||||
pub show_read_stats: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_TITLE")]
|
||||
pub title: Option<String>,
|
||||
|
||||
#[clap(short, long, env = "MICROBIN_THREADS", default_value_t = 1)]
|
||||
pub threads: u8,
|
||||
|
||||
#[clap(short, long, env = "MICROBIN_GC_DAYS", default_value_t = 90)]
|
||||
pub gc_days: u16,
|
||||
|
||||
#[clap(long, env = "MICROBIN_ENABLE_BURN_AFTER")]
|
||||
pub enable_burn_after: bool,
|
||||
|
||||
#[clap(short, long, env = "MICROBIN_DEFAULT_BURN_AFTER", default_value_t = 0)]
|
||||
pub default_burn_after: u16,
|
||||
|
||||
#[clap(long, env = "MICROBIN_WIDE")]
|
||||
pub wide: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_QR")]
|
||||
pub qr: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_ETERNAL_PASTA")]
|
||||
pub eternal_pasta: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_ENABLE_READONLY")]
|
||||
pub enable_readonly: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_DEFAULT_EXPIRY", default_value = "24hour")]
|
||||
pub default_expiry: String,
|
||||
|
||||
#[clap(long, env = "MICROBIN_DATA_DIR", default_value = "microbin_data")]
|
||||
pub data_dir: String,
|
||||
|
||||
#[clap(short, long, env = "MICROBIN_NO_FILE_UPLOAD")]
|
||||
pub no_file_upload: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_CUSTOM_CSS")]
|
||||
pub custom_css: Option<String>,
|
||||
|
||||
#[clap(long, env = "MICROBIN_HASH_IDS")]
|
||||
pub hash_ids: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_LIST_SERVER")]
|
||||
pub list_server: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_DISABLE_TELEMETRY")]
|
||||
pub disable_telemetry: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_DISABLE_UPDATE_CHECKING")]
|
||||
pub disable_update_checking: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_ENCRYPTION_CLIENT_SIDE")]
|
||||
pub encryption_client_side: bool,
|
||||
|
||||
#[clap(long, env = "MICROBIN_ENCRYPTION_SERVER_SIDE")]
|
||||
pub encryption_server_side: bool,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
env = "MICROBIN_MAX_FILE_SIZE_ENCRYPTED_MB",
|
||||
default_value_t = 256
|
||||
)]
|
||||
pub max_file_size_encrypted_mb: usize,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
env = "MICROBIN_MAX_FILE_SIZE_UNENCRYPTED_MB",
|
||||
default_value_t = 2048
|
||||
)]
|
||||
pub max_file_size_unencrypted_mb: usize,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub fn public_path_as_str(&self) -> String {
|
||||
if self.public_path.is_some() {
|
||||
self.public_path.as_ref().unwrap().to_string()
|
||||
} else {
|
||||
String::from("")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn short_path_as_str(&self) -> String {
|
||||
if self.short_path.is_some() {
|
||||
self.short_path.as_ref().unwrap().to_string()
|
||||
} else if self.public_path.is_some() {
|
||||
self.public_path.as_ref().unwrap().to_string()
|
||||
} else {
|
||||
String::from("")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn without_secrets(self) -> Args {
|
||||
Args {
|
||||
auth_basic_username: None,
|
||||
auth_basic_password: None,
|
||||
auth_admin_username: String::from(""),
|
||||
auth_admin_password: String::from(""),
|
||||
editable: self.editable,
|
||||
footer_text: self.footer_text,
|
||||
hide_footer: self.hide_footer,
|
||||
hide_header: self.hide_header,
|
||||
hide_logo: self.hide_logo,
|
||||
no_listing: self.no_listing,
|
||||
highlightsyntax: self.highlightsyntax,
|
||||
port: self.port,
|
||||
bind: self.bind,
|
||||
private: self.private,
|
||||
pure_html: self.pure_html,
|
||||
json_db: self.json_db,
|
||||
public_path: self.public_path,
|
||||
short_path: self.short_path,
|
||||
uploader_password: None,
|
||||
readonly: self.readonly,
|
||||
show_read_stats: self.show_read_stats,
|
||||
title: self.title,
|
||||
list_server: self.list_server,
|
||||
threads: self.threads,
|
||||
gc_days: self.gc_days,
|
||||
enable_burn_after: self.enable_burn_after,
|
||||
default_burn_after: self.default_burn_after,
|
||||
wide: self.wide,
|
||||
qr: self.qr,
|
||||
eternal_pasta: self.eternal_pasta,
|
||||
enable_readonly: self.enable_readonly,
|
||||
default_expiry: self.default_expiry,
|
||||
data_dir: String::from(""),
|
||||
no_file_upload: self.no_file_upload,
|
||||
custom_css: self.custom_css,
|
||||
hash_ids: self.hash_ids,
|
||||
disable_telemetry: self.disable_telemetry,
|
||||
encryption_client_side: self.encryption_client_side,
|
||||
encryption_server_side: self.encryption_server_side,
|
||||
max_file_size_encrypted_mb: self.max_file_size_encrypted_mb,
|
||||
max_file_size_unencrypted_mb: self.max_file_size_unencrypted_mb,
|
||||
disable_update_checking: self.disable_update_checking,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct PublicUrl(pub String);
|
||||
|
||||
impl fmt::Display for PublicUrl {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PublicUrl {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let uri = s.strip_suffix('/').unwrap_or(s).to_owned();
|
||||
Ok(PublicUrl(uri))
|
||||
}
|
||||
}
|
53
src/dbio.rs
Normal file
53
src/dbio.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{BufReader, BufWriter};
|
||||
|
||||
use crate::Pasta;
|
||||
|
||||
static DATABASE_PATH: &'static str = "pasta_data/database.json";
|
||||
|
||||
pub fn save_to_file(pasta_data: &Vec<Pasta>) {
|
||||
let mut file = File::create(DATABASE_PATH);
|
||||
match file {
|
||||
Ok(_) => {
|
||||
let writer = BufWriter::new(file.unwrap());
|
||||
serde_json::to_writer(writer, &pasta_data).expect("Failed to create JSON writer");
|
||||
}
|
||||
Err(_) => {
|
||||
log::info!("Database file {} not found!", DATABASE_PATH);
|
||||
file = File::create(DATABASE_PATH);
|
||||
match file {
|
||||
Ok(_) => {
|
||||
log::info!("Database file {} created.", DATABASE_PATH);
|
||||
save_to_file(pasta_data);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Failed to create database file {}: {}!",
|
||||
&DATABASE_PATH,
|
||||
&err
|
||||
);
|
||||
panic!("Failed to create database file {}: {}!", DATABASE_PATH, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_from_file() -> io::Result<Vec<Pasta>> {
|
||||
let file = File::open(DATABASE_PATH);
|
||||
match file {
|
||||
Ok(_) => {
|
||||
let reader = BufReader::new(file.unwrap());
|
||||
let data: Vec<Pasta> = serde_json::from_reader(reader).unwrap();
|
||||
Ok(data)
|
||||
}
|
||||
Err(_) => {
|
||||
log::info!("Database file {} not found!", DATABASE_PATH);
|
||||
save_to_file(&Vec::<Pasta>::new());
|
||||
|
||||
log::info!("Database file {} created.", DATABASE_PATH);
|
||||
load_from_file()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
use crate::args::{Args, ARGS};
|
||||
use crate::pasta::Pasta;
|
||||
use crate::util::misc::remove_expired;
|
||||
use crate::util::version::{fetch_latest_version, Version, CURRENT_VERSION};
|
||||
use crate::AppState;
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::{get, post, web, Error, HttpResponse};
|
||||
use askama::Template;
|
||||
use futures::TryStreamExt;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "admin.html")]
|
||||
struct AdminTemplate<'a> {
|
||||
pastas: &'a Vec<Pasta>,
|
||||
args: &'a Args,
|
||||
status: &'a String,
|
||||
version_string: &'a String,
|
||||
message: &'a String,
|
||||
update: &'a Option<Version>,
|
||||
}
|
||||
|
||||
#[get("/admin")]
|
||||
pub async fn get_admin() -> Result<HttpResponse, Error> {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header(("Location", "/auth_admin"))
|
||||
.finish());
|
||||
}
|
||||
|
||||
#[post("/admin")]
|
||||
pub async fn post_admin(
|
||||
data: web::Data<AppState>,
|
||||
mut payload: Multipart,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let mut username = String::from("");
|
||||
let mut password = String::from("");
|
||||
|
||||
while let Some(mut field) = payload.try_next().await? {
|
||||
if field.name() == Some("username") {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
username.push_str(std::str::from_utf8(&chunk).unwrap().to_string().as_str());
|
||||
}
|
||||
} else if field.name() == Some("password") {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
password.push_str(std::str::from_utf8(&chunk).unwrap().to_string().as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if username != ARGS.auth_admin_username || password != ARGS.auth_admin_password {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header(("Location", "/auth_admin/incorrect"))
|
||||
.finish());
|
||||
}
|
||||
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
// sort pastas in reverse-chronological order of creation time
|
||||
pastas.sort_by(|a, b| b.created.cmp(&a.created));
|
||||
|
||||
// todo status report more sophisticated
|
||||
let mut status = "OK";
|
||||
let mut message = "";
|
||||
|
||||
if ARGS.public_path.is_none() {
|
||||
status = "WARNING";
|
||||
message = "Warning: No public URL set with --public-path parameter. QR code and URL Copying functions have been disabled"
|
||||
}
|
||||
|
||||
if ARGS.auth_admin_username == "admin" && ARGS.auth_admin_password == "m1cr0b1n" {
|
||||
status = "WARNING";
|
||||
message = "Warning: You are using the default admin login details. This is a security risk, please change them."
|
||||
}
|
||||
|
||||
let update;
|
||||
|
||||
if !ARGS.disable_update_checking {
|
||||
let latest_version_res = fetch_latest_version().await;
|
||||
if latest_version_res.is_ok() {
|
||||
let latest_version = latest_version_res.unwrap();
|
||||
if latest_version.newer_than_current() {
|
||||
update = Some(latest_version);
|
||||
} else {
|
||||
update = None;
|
||||
}
|
||||
} else {
|
||||
update = None;
|
||||
}
|
||||
} else {
|
||||
update = None;
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||
AdminTemplate {
|
||||
pastas: &pastas,
|
||||
args: &ARGS,
|
||||
status: &String::from(status),
|
||||
version_string: &format!("{}", CURRENT_VERSION.long_title),
|
||||
message: &String::from(message),
|
||||
update: &update,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
))
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
use crate::args::{Args, ARGS};
|
||||
use actix_web::{get, web, HttpResponse};
|
||||
use askama::Template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "auth_admin.html")]
|
||||
struct AuthAdmin<'a> {
|
||||
args: &'a Args,
|
||||
status: String,
|
||||
}
|
||||
|
||||
#[get("/auth_admin")]
|
||||
pub async fn auth_admin() -> HttpResponse {
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthAdmin {
|
||||
args: &ARGS,
|
||||
status: String::from(""),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[get("/auth_admin/{status}")]
|
||||
pub async fn auth_admin_with_status(param: web::Path<String>) -> HttpResponse {
|
||||
let status = param.into_inner();
|
||||
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthAdmin {
|
||||
args: &ARGS,
|
||||
status,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
|
@ -1,394 +0,0 @@
|
|||
use crate::args::{Args, ARGS};
|
||||
use crate::endpoints::errors::ErrorTemplate;
|
||||
use crate::util::animalnumbers::to_u64;
|
||||
use crate::util::hashids::to_u64 as hashid_to_u64;
|
||||
use crate::util::misc::remove_expired;
|
||||
use crate::AppState;
|
||||
use actix_web::{get, web, HttpResponse};
|
||||
use askama::Template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "auth_upload.html")]
|
||||
struct AuthPasta<'a> {
|
||||
args: &'a Args,
|
||||
id: String,
|
||||
status: String,
|
||||
encrypted_key: String,
|
||||
encrypt_client: bool,
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[get("/auth/{id}")]
|
||||
pub async fn auth_upload(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let intern_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
for (_i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == intern_id {
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthPasta {
|
||||
args: &ARGS,
|
||||
id: id.into_inner(),
|
||||
status: String::from(""),
|
||||
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
|
||||
encrypt_client: pasta.encrypt_client,
|
||||
path: String::from("upload"),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/auth/{id}/{status}")]
|
||||
pub async fn auth_upload_with_status(
|
||||
data: web::Data<AppState>,
|
||||
param: web::Path<(String, String)>,
|
||||
) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let (id, status) = param.into_inner();
|
||||
|
||||
let intern_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
for (_i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == intern_id {
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthPasta {
|
||||
args: &ARGS,
|
||||
id,
|
||||
status,
|
||||
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
|
||||
encrypt_client: pasta.encrypt_client,
|
||||
path: String::from("upload"),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/auth_raw/{id}")]
|
||||
pub async fn auth_raw_pasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let intern_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
for (_i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == intern_id {
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthPasta {
|
||||
args: &ARGS,
|
||||
id: id.into_inner(),
|
||||
status: String::from(""),
|
||||
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
|
||||
encrypt_client: pasta.encrypt_client,
|
||||
path: String::from("raw"),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/auth_raw/{id}/{status}")]
|
||||
pub async fn auth_raw_pasta_with_status(
|
||||
data: web::Data<AppState>,
|
||||
param: web::Path<(String, String)>,
|
||||
) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let (id, status) = param.into_inner();
|
||||
|
||||
let intern_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
for (_i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == intern_id {
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthPasta {
|
||||
args: &ARGS,
|
||||
id,
|
||||
status,
|
||||
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
|
||||
encrypt_client: pasta.encrypt_client,
|
||||
path: String::from("raw"),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/auth_edit_private/{id}")]
|
||||
pub async fn auth_edit_private(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let intern_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
for (_, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == intern_id {
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthPasta {
|
||||
args: &ARGS,
|
||||
id: id.into_inner(),
|
||||
status: String::from(""),
|
||||
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
|
||||
encrypt_client: pasta.encrypt_client,
|
||||
path: String::from("edit_private"),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/auth_edit_private/{id}/{status}")]
|
||||
pub async fn auth_edit_private_with_status(
|
||||
data: web::Data<AppState>,
|
||||
param: web::Path<(String, String)>,
|
||||
) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let (id, status) = param.into_inner();
|
||||
|
||||
let intern_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
for (_i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == intern_id {
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthPasta {
|
||||
args: &ARGS,
|
||||
id,
|
||||
status,
|
||||
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
|
||||
encrypt_client: pasta.encrypt_client,
|
||||
path: String::from("edit_private"),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/auth_file/{id}")]
|
||||
pub async fn auth_file(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let intern_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
for (_, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == intern_id {
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthPasta {
|
||||
args: &ARGS,
|
||||
id: id.into_inner(),
|
||||
status: String::from(""),
|
||||
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
|
||||
encrypt_client: pasta.encrypt_client,
|
||||
path: String::from("secure_file"),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/auth_file/{id}/{status}")]
|
||||
pub async fn auth_file_with_status(
|
||||
data: web::Data<AppState>,
|
||||
param: web::Path<(String, String)>,
|
||||
) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let (id, status) = param.into_inner();
|
||||
|
||||
let intern_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
for (_i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == intern_id {
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthPasta {
|
||||
args: &ARGS,
|
||||
id,
|
||||
status,
|
||||
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
|
||||
encrypt_client: pasta.encrypt_client,
|
||||
path: String::from("secure_file"),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/auth_remove_private/{id}")]
|
||||
pub async fn auth_remove_private(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let intern_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
for (_, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == intern_id {
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthPasta {
|
||||
args: &ARGS,
|
||||
id: id.into_inner(),
|
||||
status: String::from(""),
|
||||
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
|
||||
encrypt_client: pasta.encrypt_client,
|
||||
path: String::from("remove"),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/auth_remove_private/{id}/{status}")]
|
||||
pub async fn auth_remove_private_with_status(
|
||||
data: web::Data<AppState>,
|
||||
param: web::Path<(String, String)>,
|
||||
) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let (id, status) = param.into_inner();
|
||||
|
||||
let intern_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
for (_i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == intern_id {
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
AuthPasta {
|
||||
args: &ARGS,
|
||||
id,
|
||||
status,
|
||||
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
|
||||
encrypt_client: pasta.encrypt_client,
|
||||
path: String::from("remove"),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
|
@ -1,340 +0,0 @@
|
|||
use crate::pasta::PastaFile;
|
||||
use crate::util::animalnumbers::to_animal_names;
|
||||
use crate::util::db::insert;
|
||||
use crate::util::hashids::to_hashids;
|
||||
use crate::util::misc::{encrypt, encrypt_file, is_valid_url};
|
||||
use crate::{AppState, Pasta, ARGS};
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::error::ErrorBadRequest;
|
||||
use actix_web::{get, web, Error, HttpResponse, Responder};
|
||||
use askama::Template;
|
||||
use bytesize::ByteSize;
|
||||
use futures::TryStreamExt;
|
||||
use log::warn;
|
||||
use rand::Rng;
|
||||
use std::io::Write;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct IndexTemplate<'a> {
|
||||
args: &'a ARGS,
|
||||
status: String,
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index() -> impl Responder {
|
||||
HttpResponse::Ok().content_type("text/html").body(
|
||||
IndexTemplate {
|
||||
args: &ARGS,
|
||||
status: String::from(""),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
#[get("/{status}")]
|
||||
pub async fn index_with_status(param: web::Path<String>) -> HttpResponse {
|
||||
let status = param.into_inner();
|
||||
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
IndexTemplate {
|
||||
args: &ARGS,
|
||||
status,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn expiration_to_timestamp(expiration: &str, timenow: i64) -> i64 {
|
||||
match expiration {
|
||||
"1min" => timenow + 60,
|
||||
"10min" => timenow + 60 * 10,
|
||||
"1hour" => timenow + 60 * 60,
|
||||
"24hour" => timenow + 60 * 60 * 24,
|
||||
"3days" => timenow + 60 * 60 * 24 * 3,
|
||||
"1week" => timenow + 60 * 60 * 24 * 7,
|
||||
"never" => {
|
||||
if ARGS.eternal_pasta {
|
||||
0
|
||||
} else {
|
||||
timenow + 60 * 60 * 24 * 7
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::error!("{}", "Unexpected expiration time!");
|
||||
timenow + 60 * 60 * 24 * 7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// receives a file through http Post on url /upload/a-b-c with a, b and c
|
||||
/// different animals. The client sends the post in response to a form.
|
||||
// TODO: form field order might need to be changed. In my testing the attachment
|
||||
// data is nestled between password encryption key etc <21-10-24, dvdsk>
|
||||
pub async fn create(
|
||||
data: web::Data<AppState>,
|
||||
mut payload: Multipart,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => {
|
||||
log::error!("SystemTime before UNIX EPOCH!");
|
||||
0
|
||||
}
|
||||
} as i64;
|
||||
|
||||
let mut new_pasta = Pasta {
|
||||
id: rand::thread_rng().gen::<u16>() as u64,
|
||||
content: String::from(""),
|
||||
file: None,
|
||||
extension: String::from(""),
|
||||
private: false,
|
||||
readonly: false,
|
||||
editable: ARGS.editable,
|
||||
encrypt_server: false,
|
||||
encrypted_key: Some(String::from("")),
|
||||
encrypt_client: false,
|
||||
created: timenow,
|
||||
read_count: 0,
|
||||
burn_after_reads: 0,
|
||||
last_read: timenow,
|
||||
pasta_type: String::from(""),
|
||||
expiration: expiration_to_timestamp(&ARGS.default_expiry, timenow),
|
||||
};
|
||||
|
||||
let mut random_key: String = String::from("");
|
||||
let mut plain_key: String = String::from("");
|
||||
let mut uploader_password = String::from("");
|
||||
|
||||
while let Some(mut field) = payload.try_next().await? {
|
||||
let Some(field_name) = field.name() else {
|
||||
continue;
|
||||
};
|
||||
match field_name {
|
||||
"uploader_password" => {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
uploader_password
|
||||
.push_str(std::str::from_utf8(&chunk).unwrap().to_string().as_str());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
"random_key" => {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
random_key = std::str::from_utf8(&chunk).unwrap().to_string();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
"privacy" => {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
let privacy = std::str::from_utf8(&chunk).unwrap();
|
||||
new_pasta.private = match privacy {
|
||||
"public" => false,
|
||||
_ => true,
|
||||
};
|
||||
new_pasta.readonly = match privacy {
|
||||
"readonly" => true,
|
||||
_ => false,
|
||||
};
|
||||
new_pasta.encrypt_client = match privacy {
|
||||
"secret" => true,
|
||||
_ => false,
|
||||
};
|
||||
new_pasta.encrypt_server = match privacy {
|
||||
"private" => true,
|
||||
"secret" => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
"plain_key" => {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
plain_key = std::str::from_utf8(&chunk).unwrap().to_string();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
"encrypted_random_key" => {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
new_pasta.encrypted_key =
|
||||
Some(std::str::from_utf8(&chunk).unwrap().to_string());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
"expiration" => {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
new_pasta.expiration =
|
||||
expiration_to_timestamp(std::str::from_utf8(&chunk).unwrap(), timenow);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
"burn_after" => {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
new_pasta.burn_after_reads = match std::str::from_utf8(&chunk).unwrap() {
|
||||
// give an extra read because the user will be
|
||||
// redirected to the pasta page automatically
|
||||
"1" => 2,
|
||||
"10" => 10,
|
||||
"100" => 100,
|
||||
"1000" => 1000,
|
||||
"10000" => 10000,
|
||||
"0" => 0,
|
||||
_ => {
|
||||
log::error!("{}", "Unexpected burn after value!");
|
||||
0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
"content" => {
|
||||
let mut content = String::from("");
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
content.push_str(std::str::from_utf8(&chunk).unwrap().to_string().as_str());
|
||||
}
|
||||
if !content.is_empty() {
|
||||
new_pasta.content = content;
|
||||
|
||||
new_pasta.pasta_type = if is_valid_url(new_pasta.content.as_str()) {
|
||||
String::from("url")
|
||||
} else {
|
||||
String::from("text")
|
||||
};
|
||||
}
|
||||
continue;
|
||||
}
|
||||
"syntax_highlight" => {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
new_pasta.extension = std::str::from_utf8(&chunk).unwrap().to_string();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
"file" => {
|
||||
if ARGS.no_file_upload {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = field.content_disposition().and_then(|cd| cd.get_filename());
|
||||
|
||||
let path = match path {
|
||||
Some("") => continue,
|
||||
Some(p) => p,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let mut file = match PastaFile::from_unsanitized(path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
warn!("Unsafe file name: {e:?}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(format!(
|
||||
"{}/attachments/{}",
|
||||
ARGS.data_dir,
|
||||
&new_pasta.id_as_animals()
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let filepath = format!(
|
||||
"{}/attachments/{}/{}",
|
||||
ARGS.data_dir,
|
||||
&new_pasta.id_as_animals(),
|
||||
&file.name()
|
||||
);
|
||||
|
||||
let mut f = web::block(|| std::fs::File::create(filepath)).await??;
|
||||
let mut size = 0;
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
size += chunk.len();
|
||||
if (new_pasta.encrypt_server
|
||||
&& size > ARGS.max_file_size_encrypted_mb * 1024 * 1024)
|
||||
|| size > ARGS.max_file_size_unencrypted_mb * 1024 * 1024
|
||||
{
|
||||
return Err(ErrorBadRequest("File exceeded size limit."));
|
||||
}
|
||||
f = web::block(move || f.write_all(&chunk).map(|_| f)).await??;
|
||||
}
|
||||
|
||||
file.size = ByteSize::b(size as u64);
|
||||
|
||||
new_pasta.file = Some(file);
|
||||
new_pasta.pasta_type = String::from("text");
|
||||
}
|
||||
field => {
|
||||
log::error!("Unexpected multipart field: {}", field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ARGS.readonly && ARGS.uploader_password.is_some() {
|
||||
if uploader_password != ARGS.uploader_password.as_ref().unwrap().to_owned() {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header(("Location", "/incorrect"))
|
||||
.finish());
|
||||
}
|
||||
}
|
||||
|
||||
let id = new_pasta.id;
|
||||
|
||||
if plain_key != *"" && new_pasta.readonly {
|
||||
new_pasta.encrypted_key = Some(encrypt(id.to_string().as_str(), &plain_key));
|
||||
}
|
||||
|
||||
if new_pasta.encrypt_server && !new_pasta.readonly && new_pasta.content != *"" {
|
||||
if new_pasta.encrypt_client {
|
||||
new_pasta.content = encrypt(&new_pasta.content, &random_key);
|
||||
} else {
|
||||
new_pasta.content = encrypt(&new_pasta.content, &plain_key);
|
||||
}
|
||||
}
|
||||
|
||||
if new_pasta.file.is_some() && new_pasta.encrypt_server && !new_pasta.readonly {
|
||||
let filepath = format!(
|
||||
"{}/attachments/{}/{}",
|
||||
ARGS.data_dir,
|
||||
&new_pasta.id_as_animals(),
|
||||
&new_pasta.file.as_ref().unwrap().name()
|
||||
);
|
||||
if new_pasta.encrypt_client {
|
||||
encrypt_file(&random_key, &filepath).expect("Failed to encrypt file with random key")
|
||||
} else {
|
||||
encrypt_file(&plain_key, &filepath).expect("Failed to encrypt file with plain key")
|
||||
}
|
||||
}
|
||||
|
||||
let encrypt_server = new_pasta.encrypt_server;
|
||||
|
||||
pastas.push(new_pasta);
|
||||
|
||||
for (_, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
insert(Some(&pastas), Some(pasta));
|
||||
}
|
||||
}
|
||||
|
||||
let slug = if ARGS.hash_ids {
|
||||
to_hashids(id)
|
||||
} else {
|
||||
to_animal_names(id)
|
||||
};
|
||||
|
||||
if encrypt_server {
|
||||
Ok(HttpResponse::Found()
|
||||
.append_header(("Location", format!("/auth/{}/success", slug)))
|
||||
.finish())
|
||||
} else {
|
||||
Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("{}/upload/{}", ARGS.public_path_as_str(), slug),
|
||||
))
|
||||
.finish())
|
||||
}
|
||||
}
|
|
@ -1,384 +0,0 @@
|
|||
use crate::args::Args;
|
||||
use crate::endpoints::errors::ErrorTemplate;
|
||||
use crate::util::animalnumbers::to_u64;
|
||||
use crate::util::db::update;
|
||||
use crate::util::hashids::to_u64 as hashid_to_u64;
|
||||
use crate::util::misc::{decrypt, encrypt, remove_expired};
|
||||
use crate::{AppState, Pasta, ARGS};
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::{get, post, web, Error, HttpResponse};
|
||||
use askama::Template;
|
||||
use futures::TryStreamExt;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "edit.html", escape = "none")]
|
||||
struct EditTemplate<'a> {
|
||||
pasta: &'a Pasta,
|
||||
args: &'a Args,
|
||||
path: &'a String,
|
||||
status: &'a String,
|
||||
}
|
||||
|
||||
#[get("/edit/{id}")]
|
||||
pub async fn get_edit(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
for pasta in pastas.iter() {
|
||||
if pasta.id == id {
|
||||
if !pasta.editable {
|
||||
return HttpResponse::Found()
|
||||
.append_header(("Location", format!("{}/", ARGS.public_path_as_str())))
|
||||
.finish();
|
||||
}
|
||||
|
||||
if pasta.encrypt_server {
|
||||
return HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth_edit_private/{}", pasta.id_as_animals()),
|
||||
))
|
||||
.finish();
|
||||
}
|
||||
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
EditTemplate {
|
||||
pasta,
|
||||
args: &ARGS,
|
||||
path: &String::from("edit"),
|
||||
status: &String::from(""),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/edit/{id}/{status}")]
|
||||
pub async fn get_edit_with_status(
|
||||
data: web::Data<AppState>,
|
||||
param: web::Path<(String, String)>,
|
||||
) -> HttpResponse {
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let (id, status) = param.into_inner();
|
||||
|
||||
let intern_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
for pasta in pastas.iter() {
|
||||
if pasta.id == intern_id {
|
||||
if !pasta.editable {
|
||||
return HttpResponse::Found()
|
||||
.append_header(("Location", format!("{}/", ARGS.public_path_as_str())))
|
||||
.finish();
|
||||
}
|
||||
|
||||
if pasta.encrypt_server {
|
||||
return HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth_edit_private/{}", pasta.id_as_animals()),
|
||||
))
|
||||
.finish();
|
||||
}
|
||||
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
EditTemplate {
|
||||
pasta,
|
||||
args: &ARGS,
|
||||
path: &String::from("edit"),
|
||||
status: &status,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[post("/edit_private/{id}")]
|
||||
pub async fn post_edit_private(
|
||||
data: web::Data<AppState>,
|
||||
id: web::Path<String>,
|
||||
mut payload: Multipart,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
let mut password = String::from("");
|
||||
|
||||
while let Some(mut field) = payload.try_next().await? {
|
||||
if field.name() == Some("password") {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
password.push_str(std::str::from_utf8(&chunk).unwrap().to_string().as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove expired pastas (including this one if needed)
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
// find the index of the pasta in the collection based on u64 id
|
||||
let mut index: usize = 0;
|
||||
let mut found: bool = false;
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
index = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found && !pastas[index].encrypt_client {
|
||||
let original_content = pastas[index].content.to_owned();
|
||||
|
||||
// decrypt content temporarily
|
||||
if password != *"" {
|
||||
let res = decrypt(&original_content, &password);
|
||||
if res.is_ok() {
|
||||
pastas[index]
|
||||
.content
|
||||
.replace_range(.., res.unwrap().as_str());
|
||||
// save pasta in database
|
||||
update(Some(&pastas), Some(&pastas[index]));
|
||||
} else {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!(
|
||||
"/auth_edit_private/{}/incorrect",
|
||||
pastas[index].id_as_animals()
|
||||
),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
}
|
||||
|
||||
// serve pasta in template
|
||||
let response = HttpResponse::Ok().content_type("text/html").body(
|
||||
EditTemplate {
|
||||
pasta: &pastas[index],
|
||||
args: &ARGS,
|
||||
path: &String::from("submit_edit_private"),
|
||||
status: &String::from(""),
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
if pastas[index].content != original_content {
|
||||
pastas[index].content = original_content;
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap()))
|
||||
}
|
||||
|
||||
#[post("/submit_edit_private/{id}")]
|
||||
pub async fn post_submit_edit_private(
|
||||
data: web::Data<AppState>,
|
||||
id: web::Path<String>,
|
||||
mut payload: Multipart,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
let mut password = String::from("");
|
||||
let mut new_content = String::from("");
|
||||
|
||||
while let Some(mut field) = payload.try_next().await? {
|
||||
if field.name() == Some("content") {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
new_content.push_str(std::str::from_utf8(&chunk).unwrap().to_string().as_str());
|
||||
}
|
||||
}
|
||||
if field.name() == Some("password") {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
password = std::str::from_utf8(&chunk).unwrap().to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove expired pastas (including this one if needed)
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
// find the index of the pasta in the collection based on u64 id
|
||||
let mut index: usize = 0;
|
||||
let mut found: bool = false;
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
index = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found && pastas[index].editable && !pastas[index].encrypt_client {
|
||||
if pastas[index].readonly {
|
||||
let res = decrypt(pastas[index].encrypted_key.as_ref().unwrap(), &password);
|
||||
if res.is_ok() {
|
||||
pastas[index]
|
||||
.content
|
||||
.replace_range(.., &encrypt(&new_content, &password));
|
||||
} else {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/edit/{}/incorrect", pastas[index].id_as_animals()),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
} else if pastas[index].private {
|
||||
let res = decrypt(&pastas[index].content, &password);
|
||||
if res.is_ok() {
|
||||
pastas[index]
|
||||
.content
|
||||
.replace_range(.., &encrypt(&new_content, &password));
|
||||
// save pasta in database
|
||||
update(Some(&pastas), Some(&pastas[index]));
|
||||
} else {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!(
|
||||
"/auth_edit_private/{}/incorrect",
|
||||
pastas[index].id_as_animals()
|
||||
),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth/{}/success", pastas[index].id_as_animals()),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap()))
|
||||
}
|
||||
|
||||
#[post("/edit/{id}")]
|
||||
pub async fn post_edit(
|
||||
data: web::Data<AppState>,
|
||||
id: web::Path<String>,
|
||||
mut payload: Multipart,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let mut new_content = String::from("");
|
||||
let mut password = String::from("");
|
||||
|
||||
while let Some(mut field) = payload.try_next().await? {
|
||||
if field.name() == Some("content") {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
new_content.push_str(std::str::from_utf8(&chunk).unwrap().to_string().as_str());
|
||||
}
|
||||
}
|
||||
if field.name() == Some("password") {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
password = std::str::from_utf8(&chunk).unwrap().to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
if pasta.editable && !pasta.encrypt_client {
|
||||
if pastas[i].readonly || pastas[i].encrypt_server {
|
||||
if password != *"" {
|
||||
let res = decrypt(pastas[i].encrypted_key.as_ref().unwrap(), &password);
|
||||
if res.is_ok() {
|
||||
pastas[i].content.replace_range(.., &new_content);
|
||||
// save pasta in database
|
||||
update(Some(&pastas), Some(&pastas[i]));
|
||||
} else {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/edit/{}/incorrect", pasta.id_as_animals()),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
} else {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/edit/{}/incorrect", pasta.id_as_animals()),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
} else {
|
||||
pastas[i].content.replace_range(.., &new_content);
|
||||
// save pasta in database
|
||||
update(Some(&pastas), Some(&pastas[i]));
|
||||
}
|
||||
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!(
|
||||
"{}/upload/{}",
|
||||
ARGS.public_path_as_str(),
|
||||
pastas[i].id_as_animals()
|
||||
),
|
||||
))
|
||||
.finish());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap()))
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
use actix_web::{Error, HttpResponse};
|
||||
use askama::Template;
|
||||
|
||||
use crate::args::{Args, ARGS};
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "error.html")]
|
||||
pub struct ErrorTemplate<'a> {
|
||||
pub args: &'a Args,
|
||||
}
|
||||
|
||||
pub async fn not_found() -> Result<HttpResponse, Error> {
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap()))
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::args::ARGS;
|
||||
use crate::util::auth;
|
||||
use crate::util::hashids::to_u64 as hashid_to_u64;
|
||||
use crate::util::misc::remove_expired;
|
||||
use crate::util::{animalnumbers::to_u64, misc::decrypt_file};
|
||||
use crate::AppState;
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::http::header;
|
||||
use actix_web::{get, post, web, Error, HttpResponse};
|
||||
|
||||
#[post("/secure_file/{id}")]
|
||||
pub async fn post_secure_file(
|
||||
data: web::Data<AppState>,
|
||||
id: web::Path<String>,
|
||||
payload: Multipart,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
// remove expired pastas (including this one if needed)
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
// find the index of the pasta in the collection based on u64 id
|
||||
let mut index: usize = 0;
|
||||
let mut found: bool = false;
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
index = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let password = auth::password_from_multipart(payload).await?;
|
||||
|
||||
if found {
|
||||
if let Some(ref pasta_file) = pastas[index].file {
|
||||
let file = File::open(format!(
|
||||
"{}/attachments/{}/data.enc",
|
||||
ARGS.data_dir,
|
||||
pastas[index].id_as_animals()
|
||||
))?;
|
||||
|
||||
// Not compatible with NamedFile from actix_files (it needs a File
|
||||
// to work therefore secure files do not support streaming
|
||||
let decrypted_data: Vec<u8> = decrypt_file(&password, &file)?;
|
||||
|
||||
// Set the content type based on the file extension
|
||||
let content_type = mime_guess::from_path(&pasta_file.name)
|
||||
.first_or_octet_stream()
|
||||
.to_string();
|
||||
|
||||
// Create a response with the decrypted data
|
||||
let response = HttpResponse::Ok()
|
||||
.content_type(content_type)
|
||||
.append_header((
|
||||
"Content-Disposition",
|
||||
format!("attachment; filename=\"{}\"", pasta_file.name()),
|
||||
))
|
||||
// TODO: make streaming <21-10-24, dvdsk>
|
||||
.body(decrypted_data);
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
Ok(HttpResponse::NotFound().finish())
|
||||
}
|
||||
|
||||
#[get("/file/{id}")]
|
||||
pub async fn get_file(
|
||||
request: actix_web::HttpRequest,
|
||||
id: web::Path<String>,
|
||||
data: web::Data<AppState>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id_intern = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
// remove expired pastas (including this one if needed)
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
// find the index of the pasta in the collection based on u64 id
|
||||
let mut index: usize = 0;
|
||||
let mut found: bool = false;
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id_intern {
|
||||
index = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
if let Some(ref pasta_file) = pastas[index].file {
|
||||
if pastas[index].encrypt_server {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth_file/{}", pastas[index].id_as_animals()),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
|
||||
// Construct the path to the file
|
||||
let file_path = format!(
|
||||
"{}/attachments/{}/{}",
|
||||
ARGS.data_dir,
|
||||
pastas[index].id_as_animals(),
|
||||
pasta_file.name()
|
||||
);
|
||||
let file_path = PathBuf::from(file_path);
|
||||
|
||||
// This will stream the file and set the content type based on the
|
||||
// file path
|
||||
let file_reponse = actix_files::NamedFile::open(file_path)?;
|
||||
let file_reponse = file_reponse.set_content_disposition(header::ContentDisposition {
|
||||
disposition: header::DispositionType::Attachment,
|
||||
parameters: vec![header::DispositionParam::Filename(
|
||||
pasta_file.name().to_string(),
|
||||
)],
|
||||
});
|
||||
// This takes care of streaming/seeking using the Range
|
||||
// header in the request.
|
||||
return Ok(file_reponse.into_response(&request));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HttpResponse::NotFound().finish())
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
use crate::args::{Args, ARGS};
|
||||
use actix_web::{get, HttpResponse};
|
||||
use askama::Template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "guide.html")]
|
||||
struct Guide<'a> {
|
||||
args: &'a Args,
|
||||
}
|
||||
|
||||
#[get("/guide")]
|
||||
pub async fn guide() -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(Guide { args: &ARGS }.render().unwrap())
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
use actix_web::{get, web, HttpResponse};
|
||||
use askama::Template;
|
||||
|
||||
use crate::args::{Args, ARGS};
|
||||
use crate::pasta::Pasta;
|
||||
use crate::util::misc::remove_expired;
|
||||
use crate::AppState;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "list.html")]
|
||||
struct ListTemplate<'a> {
|
||||
pastas: &'a Vec<Pasta>,
|
||||
args: &'a Args,
|
||||
}
|
||||
|
||||
#[get("/list")]
|
||||
pub async fn list(data: web::Data<AppState>) -> HttpResponse {
|
||||
if ARGS.no_listing {
|
||||
return HttpResponse::Found()
|
||||
.append_header(("Location", format!("{}/", ARGS.public_path_as_str())))
|
||||
.finish();
|
||||
}
|
||||
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
// sort pastas in reverse-chronological order of creation time
|
||||
pastas.sort_by(|a, b| b.created.cmp(&a.created));
|
||||
|
||||
HttpResponse::Ok().content_type("text/html").body(
|
||||
ListTemplate {
|
||||
pastas: &pastas,
|
||||
args: &ARGS,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
|
@ -1,405 +0,0 @@
|
|||
use crate::args::{Args, ARGS};
|
||||
use crate::endpoints::errors::ErrorTemplate;
|
||||
use crate::pasta::Pasta;
|
||||
use crate::util::animalnumbers::to_u64;
|
||||
use crate::util::auth;
|
||||
use crate::util::db::update;
|
||||
use crate::util::hashids::to_u64 as hashid_to_u64;
|
||||
use crate::util::misc::remove_expired;
|
||||
use crate::AppState;
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::{get, post, web, Error, HttpResponse};
|
||||
use askama::Template;
|
||||
use magic_crypt::{new_magic_crypt, MagicCryptTrait};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "upload.html", escape = "none")]
|
||||
struct PastaTemplate<'a> {
|
||||
pasta: &'a Pasta,
|
||||
args: &'a Args,
|
||||
}
|
||||
|
||||
fn pastaresponse(
|
||||
data: web::Data<AppState>,
|
||||
id: web::Path<String>,
|
||||
password: String,
|
||||
) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
// remove expired pastas (including this one if needed)
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
// find the index of the pasta in the collection based on u64 id
|
||||
let mut index: usize = 0;
|
||||
let mut found: bool = false;
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
index = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
if pastas[index].encrypt_server && password == *"" {
|
||||
return HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth/{}", pastas[index].id_as_animals()),
|
||||
))
|
||||
.finish();
|
||||
}
|
||||
|
||||
// increment read count
|
||||
pastas[index].read_count += 1;
|
||||
|
||||
// save the updated read count
|
||||
update(Some(&pastas), Some(&pastas[index]));
|
||||
|
||||
let original_content = pastas[index].content.to_owned();
|
||||
|
||||
// decrypt content temporarily
|
||||
if password != *"" && !original_content.is_empty() {
|
||||
let res = decrypt(&original_content, &password);
|
||||
if let Ok(..) = res {
|
||||
pastas[index]
|
||||
.content
|
||||
.replace_range(.., res.unwrap().as_str());
|
||||
} else {
|
||||
return HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth/{}/incorrect", pastas[index].id_as_animals()),
|
||||
))
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
|
||||
// serve pasta in template
|
||||
let response = HttpResponse::Ok().content_type("text/html").body(
|
||||
PastaTemplate {
|
||||
pasta: &pastas[index],
|
||||
args: &ARGS,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
if pastas[index].content != original_content {
|
||||
pastas[index].content = original_content;
|
||||
}
|
||||
|
||||
// get current unix time in seconds
|
||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => {
|
||||
log::error!("SystemTime before UNIX EPOCH!");
|
||||
0
|
||||
}
|
||||
} as i64;
|
||||
|
||||
// update last read time
|
||||
pastas[index].last_read = timenow;
|
||||
|
||||
// save the updated read count
|
||||
update(Some(&pastas), Some(&pastas[index]));
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// otherwise send pasta not found error
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[post("/upload/{id}")]
|
||||
pub async fn postpasta(
|
||||
data: web::Data<AppState>,
|
||||
id: web::Path<String>,
|
||||
payload: Multipart,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let password = auth::password_from_multipart(payload).await?;
|
||||
Ok(pastaresponse(data, id, password))
|
||||
}
|
||||
|
||||
#[post("/p/{id}")]
|
||||
pub async fn postshortpasta(
|
||||
data: web::Data<AppState>,
|
||||
id: web::Path<String>,
|
||||
payload: Multipart,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let password = auth::password_from_multipart(payload).await?;
|
||||
Ok(pastaresponse(data, id, password))
|
||||
}
|
||||
|
||||
#[get("/upload/{id}")]
|
||||
pub async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
pastaresponse(data, id, String::from(""))
|
||||
}
|
||||
|
||||
#[get("/p/{id}")]
|
||||
pub async fn getshortpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
pastaresponse(data, id, String::from(""))
|
||||
}
|
||||
|
||||
fn urlresponse(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
// remove expired pastas (including this one if needed)
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
// find the index of the pasta in the collection based on u64 id
|
||||
let mut index: usize = 0;
|
||||
let mut found: bool = false;
|
||||
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
index = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
// increment read count
|
||||
pastas[index].read_count += 1;
|
||||
|
||||
// save the updated read count
|
||||
update(Some(&pastas), Some(&pastas[index]));
|
||||
|
||||
// send redirect if it's a url pasta
|
||||
if pastas[index].pasta_type == "url" {
|
||||
let response = HttpResponse::Found()
|
||||
.append_header(("Location", String::from(&pastas[index].content)))
|
||||
.finish();
|
||||
|
||||
// get current unix time in seconds
|
||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => {
|
||||
log::error!("SystemTime before UNIX EPOCH!");
|
||||
0
|
||||
}
|
||||
} as i64;
|
||||
|
||||
// update last read time
|
||||
pastas[index].last_read = timenow;
|
||||
|
||||
// save the updated read count
|
||||
update(Some(&pastas), Some(&pastas[index]));
|
||||
|
||||
return response;
|
||||
// send error if we're trying to open a non-url pasta as a redirect
|
||||
} else {
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise send pasta not found error
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/url/{id}")]
|
||||
pub async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
urlresponse(data, id)
|
||||
}
|
||||
|
||||
#[get("/u/{id}")]
|
||||
pub async fn shortredirecturl(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
urlresponse(data, id)
|
||||
}
|
||||
|
||||
#[get("/raw/{id}")]
|
||||
pub async fn getrawpasta(
|
||||
data: web::Data<AppState>,
|
||||
id: web::Path<String>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
// remove expired pastas (including this one if needed)
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
// find the index of the pasta in the collection based on u64 id
|
||||
let mut index: usize = 0;
|
||||
let mut found: bool = false;
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
index = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
if pastas[index].encrypt_server {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth_raw/{}", pastas[index].id_as_animals()),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
|
||||
// increment read count
|
||||
pastas[index].read_count += 1;
|
||||
|
||||
// save the updated read count
|
||||
update(Some(&pastas), Some(&pastas[index]));
|
||||
|
||||
// get current unix time in seconds
|
||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => {
|
||||
log::error!("SystemTime before UNIX EPOCH!");
|
||||
0
|
||||
}
|
||||
} as i64;
|
||||
|
||||
// update last read time
|
||||
pastas[index].last_read = timenow;
|
||||
|
||||
// send raw content of pasta
|
||||
let response = Ok(HttpResponse::NotFound()
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.body(pastas[index].content.to_owned()));
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// otherwise send pasta not found error as raw text
|
||||
Ok(HttpResponse::NotFound()
|
||||
.content_type("text/html")
|
||||
.body(String::from("Upload not found! :-(")))
|
||||
}
|
||||
|
||||
#[post("/raw/{id}")]
|
||||
pub async fn postrawpasta(
|
||||
data: web::Data<AppState>,
|
||||
id: web::Path<String>,
|
||||
payload: Multipart,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let password = auth::password_from_multipart(payload).await?;
|
||||
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
// remove expired pastas (including this one if needed)
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
// find the index of the pasta in the collection based on u64 id
|
||||
let mut index: usize = 0;
|
||||
let mut found: bool = false;
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
index = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
if pastas[index].encrypt_server && password == *"" {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth/{}", pastas[index].id_as_animals()),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
|
||||
// increment read count
|
||||
pastas[index].read_count += 1;
|
||||
|
||||
// save the updated read count
|
||||
update(Some(&pastas), Some(&pastas[index]));
|
||||
|
||||
let original_content = pastas[index].content.to_owned();
|
||||
|
||||
// decrypt content temporarily
|
||||
if password != *"" {
|
||||
let res = decrypt(&original_content, &password);
|
||||
if res.is_ok() {
|
||||
pastas[index]
|
||||
.content
|
||||
.replace_range(.., res.unwrap().as_str());
|
||||
} else {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth/{}/incorrect", pastas[index].id_as_animals()),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
}
|
||||
|
||||
// get current unix time in seconds
|
||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => {
|
||||
log::error!("SystemTime before UNIX EPOCH!");
|
||||
0
|
||||
}
|
||||
} as i64;
|
||||
|
||||
// update last read time
|
||||
pastas[index].last_read = timenow;
|
||||
|
||||
// save the updated read count
|
||||
update(Some(&pastas), Some(&pastas[index]));
|
||||
|
||||
// send raw content of pasta
|
||||
let response = Ok(HttpResponse::NotFound()
|
||||
.content_type("text/html")
|
||||
.body(pastas[index].content.to_owned()));
|
||||
|
||||
if pastas[index].content != original_content {
|
||||
pastas[index].content = original_content;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// otherwise send pasta not found error as raw text
|
||||
Ok(HttpResponse::NotFound()
|
||||
.content_type("text/html")
|
||||
.body(String::from("Upload not found! :-(")))
|
||||
}
|
||||
|
||||
fn decrypt(text_str: &str, key_str: &str) -> Result<String, magic_crypt::MagicCryptError> {
|
||||
let mc = new_magic_crypt!(key_str, 256);
|
||||
|
||||
mc.decrypt_base64_to_string(text_str)
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
use crate::args::{Args, ARGS};
|
||||
use crate::endpoints::errors::ErrorTemplate;
|
||||
use crate::pasta::Pasta;
|
||||
use crate::util::animalnumbers::to_u64;
|
||||
use crate::util::hashids::to_u64 as hashid_to_u64;
|
||||
use crate::util::misc::{self, remove_expired};
|
||||
use crate::AppState;
|
||||
use actix_web::{get, web, HttpResponse};
|
||||
use askama::Template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "qr.html", escape = "none")]
|
||||
struct QRTemplate<'a> {
|
||||
qr: &'a String,
|
||||
pasta: &'a Pasta,
|
||||
args: &'a Args,
|
||||
}
|
||||
|
||||
#[get("/qr/{id}")]
|
||||
pub async fn getqr(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
// get access to the pasta collection
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let u64_id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id).unwrap_or(0)
|
||||
};
|
||||
|
||||
// remove expired pastas (including this one if needed)
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
// find the index of the pasta in the collection based on u64 id
|
||||
let mut index: usize = 0;
|
||||
let mut found: bool = false;
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == u64_id {
|
||||
index = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
// generate the QR code as an SVG - if its a file or text pastas, this will point to the /upload endpoint, otherwise to the /url endpoint, essentially directly taking the user to the url stored in the pasta
|
||||
let svg: String = match pastas[index].pasta_type.as_str() {
|
||||
"url" => misc::string_to_qr_svg(
|
||||
format!("{}/url/{}", &ARGS.public_path_as_str(), &id).as_str(),
|
||||
),
|
||||
_ => misc::string_to_qr_svg(
|
||||
format!("{}/upload/{}", &ARGS.public_path_as_str(), &id).as_str(),
|
||||
),
|
||||
};
|
||||
|
||||
// serve qr code in template
|
||||
return HttpResponse::Ok().content_type("text/html").body(
|
||||
QRTemplate {
|
||||
qr: &svg,
|
||||
pasta: &pastas[index],
|
||||
args: &ARGS,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// otherwise
|
||||
// send pasta not found error
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
use actix_multipart::Multipart;
|
||||
use actix_web::{get, post, web, Error, HttpResponse};
|
||||
|
||||
use crate::args::ARGS;
|
||||
use crate::endpoints::errors::ErrorTemplate;
|
||||
use crate::pasta::PastaFile;
|
||||
use crate::util::animalnumbers::to_u64;
|
||||
use crate::util::auth;
|
||||
use crate::util::db::delete;
|
||||
use crate::util::hashids::to_u64 as hashid_to_u64;
|
||||
use crate::util::misc::{decrypt, remove_expired};
|
||||
use crate::AppState;
|
||||
use askama::Template;
|
||||
use std::fs;
|
||||
|
||||
#[get("/remove/{id}")]
|
||||
pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
// if it's encrypted or read-only, it needs password to be deleted
|
||||
if pasta.encrypt_server || pasta.readonly {
|
||||
return HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth_remove_private/{}", pasta.id_as_animals()),
|
||||
))
|
||||
.finish();
|
||||
}
|
||||
|
||||
// remove the file itself
|
||||
if let Some(PastaFile { name, .. }) = &pasta.file {
|
||||
if fs::remove_file(format!(
|
||||
"{}/attachments/{}/{}",
|
||||
ARGS.data_dir,
|
||||
pasta.id_as_animals(),
|
||||
name
|
||||
))
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Failed to delete file {}!", name)
|
||||
}
|
||||
|
||||
// and remove the containing directory
|
||||
if fs::remove_dir(format!(
|
||||
"{}/attachments/{}/",
|
||||
ARGS.data_dir,
|
||||
pasta.id_as_animals()
|
||||
))
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Failed to delete directory {}!", name)
|
||||
}
|
||||
}
|
||||
|
||||
// remove it from in-memory pasta list
|
||||
pastas.remove(i);
|
||||
|
||||
delete(Some(&pastas), Some(id));
|
||||
|
||||
return HttpResponse::Found()
|
||||
.append_header(("Location", format!("{}/list", ARGS.public_path_as_str())))
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[post("/remove/{id}")]
|
||||
pub async fn post_remove(
|
||||
data: web::Data<AppState>,
|
||||
id: web::Path<String>,
|
||||
payload: Multipart,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let id = if ARGS.hash_ids {
|
||||
hashid_to_u64(&id).unwrap_or(0)
|
||||
} else {
|
||||
to_u64(&id.into_inner()).unwrap_or(0)
|
||||
};
|
||||
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
let password = auth::password_from_multipart(payload).await?;
|
||||
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
if pastas[i].readonly || pastas[i].encrypt_server {
|
||||
if password != *"" {
|
||||
let res = decrypt(pastas[i].content.to_owned().as_str(), &password);
|
||||
if res.is_ok() {
|
||||
// remove the file itself
|
||||
if let Some(PastaFile { name, .. }) = &pasta.file {
|
||||
if fs::remove_file(format!(
|
||||
"{}/attachments/{}/{}",
|
||||
ARGS.data_dir,
|
||||
pasta.id_as_animals(),
|
||||
name
|
||||
))
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Failed to delete file {}!", name)
|
||||
}
|
||||
|
||||
// and remove the containing directory
|
||||
if fs::remove_dir(format!(
|
||||
"{}/attachments/{}/",
|
||||
ARGS.data_dir,
|
||||
pasta.id_as_animals()
|
||||
))
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Failed to delete directory {}!", name)
|
||||
}
|
||||
}
|
||||
|
||||
// remove it from in-memory pasta list
|
||||
pastas.remove(i);
|
||||
|
||||
delete(Some(&pastas), Some(id));
|
||||
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("{}/list", ARGS.public_path_as_str()),
|
||||
))
|
||||
.finish());
|
||||
} else {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth_remove_private/{}/incorrect", pasta.id_as_animals()),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
} else {
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!("/auth_remove_private/{}/incorrect", pasta.id_as_animals()),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(HttpResponse::Found()
|
||||
.append_header((
|
||||
"Location",
|
||||
format!(
|
||||
"{}/upload/{}",
|
||||
ARGS.public_path_as_str(),
|
||||
pastas[i].id_as_animals()
|
||||
),
|
||||
))
|
||||
.finish());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap()))
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
use actix_web::{web, HttpResponse, Responder};
|
||||
use mime_guess::from_path;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "templates/assets/"]
|
||||
struct Asset;
|
||||
|
||||
fn handle_embedded_file(path: &str) -> HttpResponse {
|
||||
match Asset::get(path) {
|
||||
Some(content) => HttpResponse::Ok()
|
||||
.content_type(from_path(path).first_or_octet_stream().as_ref())
|
||||
.body(content.data.into_owned()),
|
||||
None => HttpResponse::NotFound().body("404 Not Found"),
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_web::get("/static/{_:.*}")]
|
||||
async fn static_resources(path: web::Path<String>) -> impl Responder {
|
||||
handle_embedded_file(path.as_str())
|
||||
}
|
491
src/main.rs
491
src/main.rs
|
@ -1,63 +1,353 @@
|
|||
extern crate core;
|
||||
|
||||
use crate::args::ARGS;
|
||||
use crate::endpoints::{
|
||||
admin, auth_admin, auth_upload, create, edit, errors, file, guide, list,
|
||||
pasta as pasta_endpoint, qr, remove, static_resources,
|
||||
};
|
||||
use crate::pasta::Pasta;
|
||||
use crate::util::db::read_all;
|
||||
use crate::util::telemetry::start_telemetry_thread;
|
||||
use actix_web::middleware::Condition;
|
||||
use actix_web::{middleware, web, App, HttpServer};
|
||||
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||
use chrono::Local;
|
||||
use env_logger::Builder;
|
||||
use log::LevelFilter;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::sync::Mutex;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub mod args;
|
||||
pub mod pasta;
|
||||
use actix_files;
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::dev::ServiceRequest;
|
||||
use actix_web::middleware::Condition;
|
||||
use actix_web::{error, get, middleware, web, App, Error, HttpResponse, HttpServer, Responder};
|
||||
use actix_web_httpauth::extractors::basic::BasicAuth;
|
||||
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||
use askama::Template;
|
||||
use chrono::Local;
|
||||
use clap::Parser;
|
||||
use futures::TryStreamExt as _;
|
||||
use lazy_static::lazy_static;
|
||||
use linkify::{LinkFinder, LinkKind};
|
||||
use log::LevelFilter;
|
||||
use rand::Rng;
|
||||
use std::fs;
|
||||
|
||||
pub mod util {
|
||||
pub mod animalnumbers;
|
||||
pub mod auth;
|
||||
pub mod db;
|
||||
pub mod db_json;
|
||||
#[cfg(feature = "default")]
|
||||
pub mod db_sqlite;
|
||||
pub mod hashids;
|
||||
pub mod misc;
|
||||
pub mod syntaxhighlighter;
|
||||
pub mod telemetry;
|
||||
pub mod version;
|
||||
pub mod http_client;
|
||||
use crate::animalnumbers::{to_animal_names, to_u64};
|
||||
use crate::dbio::save_to_file;
|
||||
use crate::pasta::Pasta;
|
||||
|
||||
mod animalnumbers;
|
||||
mod dbio;
|
||||
mod pasta;
|
||||
mod plugins;
|
||||
|
||||
lazy_static! {
|
||||
static ref ARGS: Args = Args::parse();
|
||||
}
|
||||
|
||||
pub mod endpoints {
|
||||
pub mod admin;
|
||||
pub mod auth_admin;
|
||||
pub mod auth_upload;
|
||||
pub mod create;
|
||||
pub mod edit;
|
||||
pub mod errors;
|
||||
pub mod file;
|
||||
pub mod guide;
|
||||
pub mod list;
|
||||
pub mod pasta;
|
||||
pub mod qr;
|
||||
pub mod remove;
|
||||
pub mod static_resources;
|
||||
struct AppState {
|
||||
pastas: Mutex<Vec<Pasta>>,
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
pub pastas: Mutex<Vec<Pasta>>,
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[clap(short, long, default_value_t = 8080)]
|
||||
port: u32,
|
||||
|
||||
#[clap(short, long, default_value_t = 1)]
|
||||
threads: u8,
|
||||
|
||||
#[clap(short, long)]
|
||||
wide: bool,
|
||||
|
||||
#[clap(short, long, default_value_t = 3)]
|
||||
animals: u8,
|
||||
|
||||
#[clap(long)]
|
||||
hide_header: bool,
|
||||
|
||||
#[clap(long)]
|
||||
hide_footer: bool,
|
||||
|
||||
#[clap(long)]
|
||||
pure_html: bool,
|
||||
|
||||
#[clap(long)]
|
||||
no_listing: bool,
|
||||
|
||||
#[clap(long)]
|
||||
auth_username: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
auth_password: Option<String>,
|
||||
}
|
||||
|
||||
async fn auth_validator(
|
||||
req: ServiceRequest,
|
||||
credentials: BasicAuth,
|
||||
) -> Result<ServiceRequest, Error> {
|
||||
// check if username matches
|
||||
if credentials.user_id().as_ref() == ARGS.auth_username.as_ref().unwrap() {
|
||||
return match ARGS.auth_password.as_ref() {
|
||||
Some(cred_pass) => match credentials.password() {
|
||||
None => Err(error::ErrorBadRequest("Invalid login details.")),
|
||||
Some(arg_pass) => {
|
||||
if arg_pass == cred_pass {
|
||||
Ok(req)
|
||||
} else {
|
||||
Err(error::ErrorBadRequest("Invalid login details."))
|
||||
}
|
||||
}
|
||||
},
|
||||
None => Ok(req),
|
||||
};
|
||||
} else {
|
||||
Err(error::ErrorBadRequest("Invalid login details."))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct IndexTemplate<'a> {
|
||||
args: &'a Args,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "error.html")]
|
||||
struct ErrorTemplate<'a> {
|
||||
args: &'a Args,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "pasta.html", escape = "none")]
|
||||
struct PastaTemplate<'a> {
|
||||
pasta: &'a Pasta,
|
||||
args: &'a Args,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "pastalist.html")]
|
||||
struct PastaListTemplate<'a> {
|
||||
pastas: &'a Vec<Pasta>,
|
||||
args: &'a Args,
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn index() -> impl Responder {
|
||||
HttpResponse::Found()
|
||||
.content_type("text/html")
|
||||
.body(IndexTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
async fn not_found() -> Result<HttpResponse, Error> {
|
||||
Ok(HttpResponse::Found()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap()))
|
||||
}
|
||||
|
||||
async fn create(data: web::Data<AppState>, mut payload: Multipart) -> Result<HttpResponse, Error> {
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
|
||||
} as i64;
|
||||
|
||||
let mut new_pasta = Pasta {
|
||||
id: rand::thread_rng().gen::<u16>() as u64,
|
||||
content: String::from("No Text Content"),
|
||||
file: String::from("no-file"),
|
||||
created: timenow,
|
||||
pasta_type: String::from(""),
|
||||
expiration: 0,
|
||||
};
|
||||
|
||||
while let Some(mut field) = payload.try_next().await? {
|
||||
match field.name() {
|
||||
"expiration" => {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
new_pasta.expiration = match std::str::from_utf8(&chunk).unwrap() {
|
||||
"1min" => timenow + 60,
|
||||
"10min" => timenow + 60 * 10,
|
||||
"1hour" => timenow + 60 * 60,
|
||||
"24hour" => timenow + 60 * 60 * 24,
|
||||
"1week" => timenow + 60 * 60 * 24 * 7,
|
||||
"never" => 0,
|
||||
_ => panic!("Unexpected expiration time!"),
|
||||
};
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
"content" => {
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
new_pasta.content =
|
||||
plugins::on_pasta_created(std::str::from_utf8(&chunk).unwrap());
|
||||
new_pasta.pasta_type = if is_valid_url(new_pasta.content.as_str()) {
|
||||
String::from("url")
|
||||
} else {
|
||||
String::from("text")
|
||||
};
|
||||
}
|
||||
continue;
|
||||
}
|
||||
"file" => {
|
||||
let content_disposition = field.content_disposition();
|
||||
|
||||
let filename = match content_disposition.get_filename() {
|
||||
Some("") => continue,
|
||||
Some(filename) => filename.replace(' ', "_").to_string(),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(format!("./pasta_data/{}", &new_pasta.id_as_animals()))
|
||||
.unwrap();
|
||||
|
||||
let filepath = format!("./pasta_data/{}/{}", &new_pasta.id_as_animals(), &filename);
|
||||
|
||||
new_pasta.file = filename;
|
||||
|
||||
let mut f = web::block(|| std::fs::File::create(filepath)).await??;
|
||||
|
||||
while let Some(chunk) = field.try_next().await? {
|
||||
f = web::block(move || f.write_all(&chunk).map(|_| f)).await??;
|
||||
}
|
||||
|
||||
new_pasta.pasta_type = String::from("text");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let id = new_pasta.id;
|
||||
|
||||
pastas.push(new_pasta);
|
||||
|
||||
save_to_file(&pastas);
|
||||
|
||||
Ok(HttpResponse::Found()
|
||||
.append_header(("Location", format!("/pasta/{}", to_animal_names(id))))
|
||||
.finish())
|
||||
}
|
||||
|
||||
#[get("/pasta/{id}")]
|
||||
async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = to_u64(&*id.into_inner());
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
for pasta in pastas.iter() {
|
||||
if pasta.id == id {
|
||||
let pasta_copy = Pasta {
|
||||
id: pasta.id,
|
||||
content: plugins::on_pasta_read(&pasta.content),
|
||||
file: pasta.file.to_string(),
|
||||
created: pasta.created,
|
||||
pasta_type: pasta.pasta_type.to_string(),
|
||||
expiration: pasta.expiration,
|
||||
};
|
||||
|
||||
return HttpResponse::Found().content_type("text/html").body(
|
||||
PastaTemplate {
|
||||
pasta: &pasta_copy,
|
||||
args: &ARGS,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Found()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/url/{id}")]
|
||||
async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = to_u64(&*id.into_inner());
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
for pasta in pastas.iter() {
|
||||
if pasta.id == id {
|
||||
if pasta.pasta_type == "url" {
|
||||
return HttpResponse::Found()
|
||||
.append_header(("Location", String::from(&pasta.content)))
|
||||
.finish();
|
||||
} else {
|
||||
return HttpResponse::Found()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Found()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/raw/{id}")]
|
||||
async fn getrawpasta(data: web::Data<AppState>, id: web::Path<String>) -> String {
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = to_u64(&*id.into_inner());
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
for pasta in pastas.iter() {
|
||||
if pasta.id == id {
|
||||
return pasta.content.to_owned();
|
||||
}
|
||||
}
|
||||
|
||||
String::from("Pasta not found! :-(")
|
||||
}
|
||||
|
||||
#[get("/remove/{id}")]
|
||||
async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = to_u64(&*id.into_inner());
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
for (i, pasta) in pastas.iter().enumerate() {
|
||||
if pasta.id == id {
|
||||
pastas.remove(i);
|
||||
return HttpResponse::Found()
|
||||
.append_header(("Location", "/pastalist"))
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Found()
|
||||
.content_type("text/html")
|
||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/pastalist")]
|
||||
async fn list(data: web::Data<AppState>) -> HttpResponse {
|
||||
if ARGS.no_listing {
|
||||
return HttpResponse::Found()
|
||||
.append_header(("Location", "/"))
|
||||
.finish();
|
||||
}
|
||||
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
||||
HttpResponse::Found().content_type("text/html").body(
|
||||
PastaListTemplate {
|
||||
pastas: &pastas,
|
||||
args: &ARGS,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let args: Args = Args::parse();
|
||||
|
||||
Builder::new()
|
||||
.format(|buf, record| {
|
||||
writeln!(
|
||||
|
@ -72,86 +362,73 @@ async fn main() -> std::io::Result<()> {
|
|||
.init();
|
||||
|
||||
log::info!(
|
||||
"MicroBin starting on http://{}:{}",
|
||||
ARGS.bind.to_string(),
|
||||
ARGS.port.to_string()
|
||||
"MicroBin starting on http://127.0.0.1:{}",
|
||||
args.port.to_string()
|
||||
);
|
||||
|
||||
match fs::create_dir_all(format!("{}/public", ARGS.data_dir)) {
|
||||
match std::fs::create_dir_all("./pasta_data") {
|
||||
Ok(dir) => dir,
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
"Couldn't create data directory {}/attachments/: {:?}",
|
||||
ARGS.data_dir,
|
||||
error
|
||||
);
|
||||
panic!(
|
||||
"Couldn't create data directory {}/attachments/: {:?}",
|
||||
ARGS.data_dir, error
|
||||
);
|
||||
log::error!("Couldn't create data directory ./pasta_data: {:?}", error);
|
||||
panic!("Couldn't create data directory ./pasta_data: {:?}", error);
|
||||
}
|
||||
};
|
||||
|
||||
let data = web::Data::new(AppState {
|
||||
pastas: Mutex::new(read_all()),
|
||||
pastas: Mutex::new(dbio::load_from_file().unwrap()),
|
||||
});
|
||||
|
||||
if !ARGS.disable_telemetry {
|
||||
start_telemetry_thread();
|
||||
}
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.app_data(data.clone())
|
||||
.wrap(middleware::NormalizePath::trim())
|
||||
.service(create::index)
|
||||
.service(guide::guide)
|
||||
.service(auth_admin::auth_admin)
|
||||
.service(auth_upload::auth_file_with_status)
|
||||
.service(auth_admin::auth_admin_with_status)
|
||||
.service(auth_upload::auth_upload_with_status)
|
||||
.service(auth_upload::auth_raw_pasta_with_status)
|
||||
.service(auth_upload::auth_edit_private_with_status)
|
||||
.service(auth_upload::auth_remove_private_with_status)
|
||||
.service(auth_upload::auth_file)
|
||||
.service(auth_upload::auth_upload)
|
||||
.service(auth_upload::auth_raw_pasta)
|
||||
.service(auth_upload::auth_edit_private)
|
||||
.service(auth_upload::auth_remove_private)
|
||||
.service(pasta_endpoint::getpasta)
|
||||
.service(pasta_endpoint::postpasta)
|
||||
.service(pasta_endpoint::getshortpasta)
|
||||
.service(pasta_endpoint::postshortpasta)
|
||||
.service(pasta_endpoint::getrawpasta)
|
||||
.service(pasta_endpoint::postrawpasta)
|
||||
.service(pasta_endpoint::redirecturl)
|
||||
.service(pasta_endpoint::shortredirecturl)
|
||||
.service(edit::get_edit)
|
||||
.service(edit::get_edit_with_status)
|
||||
.service(edit::post_edit)
|
||||
.service(edit::post_edit_private)
|
||||
.service(edit::post_submit_edit_private)
|
||||
.service(admin::get_admin)
|
||||
.service(admin::post_admin)
|
||||
.service(static_resources::static_resources)
|
||||
.service(qr::getqr)
|
||||
.service(file::get_file)
|
||||
.service(file::post_secure_file)
|
||||
.service(web::resource("/upload").route(web::post().to(create::create)))
|
||||
.default_service(web::route().to(errors::not_found))
|
||||
.service(index)
|
||||
.service(getpasta)
|
||||
.service(redirecturl)
|
||||
.service(getrawpasta)
|
||||
.service(actix_files::Files::new("/static", "./static"))
|
||||
.service(actix_files::Files::new("/file", "./pasta_data"))
|
||||
.service(web::resource("/upload").route(web::post().to(create)))
|
||||
.default_service(web::route().to(not_found))
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(remove::remove)
|
||||
.service(remove::post_remove)
|
||||
.service(list::list)
|
||||
.service(create::index_with_status)
|
||||
.service(remove)
|
||||
.service(list)
|
||||
.wrap(Condition::new(
|
||||
ARGS.auth_basic_username.is_some()
|
||||
&& ARGS.auth_basic_username.as_ref().unwrap().trim() != "",
|
||||
HttpAuthentication::basic(util::auth::auth_validator),
|
||||
args.auth_username.is_some(),
|
||||
HttpAuthentication::basic(auth_validator),
|
||||
))
|
||||
})
|
||||
.bind((ARGS.bind, ARGS.port))?
|
||||
.workers(ARGS.threads as usize)
|
||||
.bind(format!("0.0.0.0:{}", args.port.to_string()))?
|
||||
.workers(args.threads as usize)
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
fn remove_expired(pastas: &mut Vec<Pasta>) {
|
||||
// get current time - this will be needed to check which pastas have expired
|
||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
|
||||
} as i64;
|
||||
|
||||
pastas.retain(|p| {
|
||||
// expiration is `never` or not reached
|
||||
if p.expiration == 0 || p.expiration > timenow {
|
||||
// keep
|
||||
true
|
||||
} else {
|
||||
// remove the file itself
|
||||
fs::remove_file(format!("./pasta_data/{}/{}", p.id_as_animals(), p.file));
|
||||
// and remove the containing directory
|
||||
fs::remove_dir(format!("./pasta_data/{}/", p.id_as_animals()));
|
||||
// remove
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn is_valid_url(url: &str) -> bool {
|
||||
let finder = LinkFinder::new();
|
||||
let spans: Vec<_> = finder.spans(url).collect();
|
||||
spans[0].as_str() == url && Some(&LinkKind::Url) == spans[0].kind()
|
||||
}
|
||||
|
|
252
src/pasta.rs
252
src/pasta.rs
|
@ -1,257 +1,51 @@
|
|||
use bytesize::ByteSize;
|
||||
use chrono::{Datelike, Local, TimeZone, Timelike};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::args::ARGS;
|
||||
use crate::util::animalnumbers::to_animal_names;
|
||||
use crate::util::hashids::to_hashids;
|
||||
use crate::util::syntaxhighlighter::html_highlight;
|
||||
use chrono::{DateTime, Datelike, NaiveDateTime, Timelike, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Eq)]
|
||||
pub struct PastaFile {
|
||||
pub name: String,
|
||||
pub size: ByteSize,
|
||||
}
|
||||
use crate::to_animal_names;
|
||||
|
||||
impl PastaFile {
|
||||
pub fn from_unsanitized(path: &str) -> Result<Self, &'static str> {
|
||||
let path = Path::new(path);
|
||||
let name = path.file_name().ok_or("Path did not contain a file name")?;
|
||||
let name = name.to_string_lossy().replace(' ', "_");
|
||||
Ok(Self {
|
||||
name,
|
||||
size: ByteSize::b(0),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn is_image(&self) -> bool {
|
||||
let lowercase_name = self.name.to_lowercase();
|
||||
let extensions = [
|
||||
".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".ico", ".svg", ".tiff", ".tif",
|
||||
".jfif", ".pjpeg", ".pjp", ".avif", ".jxl", ".heif",
|
||||
];
|
||||
extensions.iter().any(|&ext| lowercase_name.ends_with(ext))
|
||||
}
|
||||
|
||||
pub fn is_video(&self) -> bool {
|
||||
let lowercase_name = self.name.to_lowercase();
|
||||
let extensions = [
|
||||
".mp4", ".mov", ".wmv", ".webm", ".avi", ".flv", ".mkv", ".mts",
|
||||
];
|
||||
extensions.iter().any(|&ext| lowercase_name.ends_with(ext))
|
||||
}
|
||||
|
||||
pub fn embeddable(&self) -> bool {
|
||||
self.is_image() || self.is_video()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Pasta {
|
||||
pub id: u64,
|
||||
pub content: String,
|
||||
pub file: Option<PastaFile>,
|
||||
pub extension: String,
|
||||
pub private: bool,
|
||||
pub readonly: bool,
|
||||
pub editable: bool,
|
||||
pub encrypt_server: bool,
|
||||
pub encrypt_client: bool,
|
||||
pub encrypted_key: Option<String>,
|
||||
pub file: String,
|
||||
pub created: i64,
|
||||
pub expiration: i64,
|
||||
pub last_read: i64,
|
||||
pub read_count: u64,
|
||||
pub burn_after_reads: u64,
|
||||
pub pasta_type: String,
|
||||
}
|
||||
|
||||
impl Pasta {
|
||||
pub fn id_as_animals(&self) -> String {
|
||||
if ARGS.hash_ids {
|
||||
to_hashids(self.id)
|
||||
} else {
|
||||
to_animal_names(self.id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_file(&self) -> bool {
|
||||
self.file.is_some()
|
||||
}
|
||||
|
||||
pub fn total_size_as_string(&self) -> String {
|
||||
let total_size_bytes = if self.has_file() {
|
||||
self.file.as_ref().unwrap().size.as_u64() as usize + self.content.as_bytes().len()
|
||||
} else {
|
||||
self.content.as_bytes().len()
|
||||
};
|
||||
|
||||
if total_size_bytes < 1024 {
|
||||
format!("{} B", total_size_bytes)
|
||||
} else if total_size_bytes < 1024 * 1024 {
|
||||
format!("{} KB", total_size_bytes / 1024)
|
||||
} else if total_size_bytes < 1024 * 1024 * 1024 {
|
||||
format!("{} MB", total_size_bytes / (1024 * 1024))
|
||||
} else {
|
||||
format!("{} GB", total_size_bytes / (1024 * 1024 * 1024))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_embeddable(&self) -> bool {
|
||||
return self.has_file()
|
||||
&& self.file.as_ref().unwrap().embeddable()
|
||||
&& !(self.encrypt_server || self.encrypt_client);
|
||||
to_animal_names(self.id)
|
||||
}
|
||||
|
||||
pub fn created_as_string(&self) -> String {
|
||||
Local.timestamp_opt(self.created, 0).map(|date| {
|
||||
format!(
|
||||
"{:02}-{:02} {:02}:{:02}",
|
||||
date.month(),
|
||||
date.day(),
|
||||
date.hour(),
|
||||
date.minute(),
|
||||
)
|
||||
}).earliest().unwrap_or_else(|| {
|
||||
log::error!("Failed to process created date");
|
||||
String::from("Unknow")
|
||||
})
|
||||
let date = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.created, 0), Utc);
|
||||
format!(
|
||||
"{:02}-{:02} {}:{}",
|
||||
date.month(),
|
||||
date.day(),
|
||||
date.hour(),
|
||||
date.minute(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn expiration_as_string(&self) -> String {
|
||||
if self.expiration == 0 {
|
||||
String::from("Never")
|
||||
} else {
|
||||
Local.timestamp_opt(self.expiration, 0).map(|date| {
|
||||
format!(
|
||||
"{:02}-{:02} {:02}:{:02}",
|
||||
date.month(),
|
||||
date.day(),
|
||||
date.hour(),
|
||||
date.minute(),
|
||||
)
|
||||
}).earliest().unwrap_or_else(|| {
|
||||
log::error!("Failed to process expiration");
|
||||
String::from("Never")
|
||||
})
|
||||
let date =
|
||||
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.expiration, 0), Utc);
|
||||
format!(
|
||||
"{:02}-{:02} {}:{}",
|
||||
date.month(),
|
||||
date.day(),
|
||||
date.hour(),
|
||||
date.minute(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_read_time_ago_as_string(&self) -> String {
|
||||
// get current unix time in seconds
|
||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => {
|
||||
log::error!("SystemTime before UNIX EPOCH!");
|
||||
0
|
||||
}
|
||||
} as i64;
|
||||
|
||||
// get seconds since last read and convert it to days
|
||||
let days = ((timenow - self.last_read) / 86400) as u16;
|
||||
if days > 1 {
|
||||
return format!("{} days ago", days);
|
||||
};
|
||||
|
||||
// it's less than 1 day, let's do hours then
|
||||
let hours = ((timenow - self.last_read) / 3600) as u16;
|
||||
if hours > 1 {
|
||||
return format!("{} hours ago", hours);
|
||||
};
|
||||
|
||||
// it's less than 1 hour, let's do minutes then
|
||||
let minutes = ((timenow - self.last_read) / 60) as u16;
|
||||
if minutes > 1 {
|
||||
return format!("{} minutes ago", minutes);
|
||||
};
|
||||
|
||||
// it's less than 1 minute, let's do seconds then
|
||||
let seconds = (timenow - self.last_read) as u16;
|
||||
if seconds > 1 {
|
||||
return format!("{} seconds ago", seconds);
|
||||
};
|
||||
|
||||
// it's less than 1 second?????
|
||||
String::from("just now")
|
||||
}
|
||||
|
||||
pub fn short_last_read_time_ago_as_string(&self) -> String {
|
||||
// get current unix time in seconds
|
||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => {
|
||||
log::error!("SystemTime before UNIX EPOCH!");
|
||||
0
|
||||
}
|
||||
} as i64;
|
||||
|
||||
// get seconds since last read and convert it to days
|
||||
let days = ((timenow - self.last_read) / 86400) as u16;
|
||||
if days > 1 {
|
||||
return format!("{} d ago", days);
|
||||
};
|
||||
|
||||
// it's less than 1 day, let's do hours then
|
||||
let hours = ((timenow - self.last_read) / 3600) as u16;
|
||||
if hours > 1 {
|
||||
return format!("{} h ago", hours);
|
||||
};
|
||||
|
||||
// it's less than 1 hour, let's do minutes then
|
||||
let minutes = ((timenow - self.last_read) / 60) as u16;
|
||||
if minutes > 1 {
|
||||
return format!("{} m ago", minutes);
|
||||
};
|
||||
|
||||
// it's less than 1 minute, let's do seconds then
|
||||
let seconds = (timenow - self.last_read) as u16;
|
||||
if seconds > 1 {
|
||||
return format!("{} s ago", seconds);
|
||||
};
|
||||
|
||||
// it's less than 1 second?????
|
||||
String::from("just now")
|
||||
}
|
||||
|
||||
pub fn last_read_days_ago(&self) -> u16 {
|
||||
// get current unix time in seconds
|
||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => {
|
||||
log::error!("SystemTime before UNIX EPOCH!");
|
||||
0
|
||||
}
|
||||
} as i64;
|
||||
|
||||
// get seconds since last read and convert it to days
|
||||
((timenow - self.last_read) / 86400) as u16
|
||||
}
|
||||
|
||||
pub fn content_syntax_highlighted(&self) -> String {
|
||||
html_highlight(&self.content, &self.extension)
|
||||
}
|
||||
|
||||
pub fn content_not_highlighted(&self) -> String {
|
||||
html_highlight(&self.content, "txt")
|
||||
}
|
||||
|
||||
pub fn content_escaped(&self) -> String {
|
||||
html_escape::encode_text(
|
||||
&self
|
||||
.content
|
||||
.replace('\\', "\\\\")
|
||||
.replace('`', "\\`")
|
||||
.replace('$', "\\$"),
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Pasta {
|
||||
|
|
134
src/plugins.rs
Normal file
134
src/plugins.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
extern crate rutie;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use log::{error, log};
|
||||
use rutie::{AnyException, AnyObject, Object, RString, VM};
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read};
|
||||
use std::{fs, io};
|
||||
|
||||
const CACHE_PLUGINS: bool = false;
|
||||
|
||||
lazy_static! {
|
||||
static ref PLUGIN_IDENTIFIERS: Vec<String> = init();
|
||||
}
|
||||
|
||||
fn init() -> Vec<String> {
|
||||
VM::init();
|
||||
|
||||
let plugin_paths = load_plugin_paths();
|
||||
|
||||
let plugin_codes = read_plugins(plugin_paths.clone());
|
||||
|
||||
feed_plugins(plugin_codes);
|
||||
|
||||
let identifiers = get_plugin_identifiers(plugin_paths);
|
||||
|
||||
init_plugins(&identifiers);
|
||||
|
||||
identifiers
|
||||
}
|
||||
|
||||
pub fn pasta_filter(s: &str) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn on_pasta_read(s: &str) -> String {
|
||||
let mut processed_content: String = String::from(s);
|
||||
|
||||
for PLUGIN_IDENTIFIER in PLUGIN_IDENTIFIERS.iter() {
|
||||
processed_content = eval_for_string(PLUGIN_IDENTIFIER, "on_pasta_read", s);
|
||||
}
|
||||
|
||||
processed_content
|
||||
}
|
||||
|
||||
pub fn on_pasta_created(s: &str) -> String {
|
||||
let mut processed_content: String = String::from(s);
|
||||
|
||||
for PLUGIN_IDENTIFIER in PLUGIN_IDENTIFIERS.iter() {
|
||||
processed_content = eval_for_string(PLUGIN_IDENTIFIER, "on_pasta_created", s);
|
||||
}
|
||||
|
||||
processed_content
|
||||
}
|
||||
|
||||
pub fn init_plugins(plugin_identifiers: &Vec<String>) {
|
||||
for PLUGIN_IDENTIFIER in plugin_identifiers.iter() {
|
||||
eval_for_string(PLUGIN_IDENTIFIER, "init", "");
|
||||
|
||||
let init_result = eval_for_string(&PLUGIN_IDENTIFIER, "init", "");
|
||||
let id = eval_for_string(&PLUGIN_IDENTIFIER, "get_id", "");
|
||||
let name = eval_for_string(&id, "get_name", "");
|
||||
let version = eval_for_string(&id, "get_version", "");
|
||||
|
||||
log::info!("Initialised plugin {id} - {name} ({version})");
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_for_string(plugin_id: &str, function: &str, parameter: &str) -> String {
|
||||
match VM::eval(&*format!("MBP::{}::{}({})", plugin_id, function, parameter)) {
|
||||
Ok(result) => match result.try_convert_to::<RString>() {
|
||||
Ok(ruby_string) => ruby_string.to_string(),
|
||||
Err(err) => err.to_string(),
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Failed to run function '{}' on plugin {}: {}",
|
||||
function,
|
||||
plugin_id,
|
||||
err
|
||||
);
|
||||
err.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_plugin_paths() -> Vec<String> {
|
||||
let paths = fs::read_dir("./plugins").expect("Failed to access ./plugins library.");
|
||||
|
||||
let mut plugin_paths: Vec<String> = Vec::new();
|
||||
|
||||
for path in paths {
|
||||
plugin_paths.push(path.unwrap().path().to_str().unwrap().parse().unwrap());
|
||||
}
|
||||
|
||||
plugin_paths
|
||||
}
|
||||
|
||||
fn read_plugins(plugin_paths: Vec<String>) -> Vec<String> {
|
||||
let mut plugin_codes: Vec<String> = Vec::new();
|
||||
|
||||
for plugin_path in plugin_paths {
|
||||
let plugin_code = match fs::read_to_string(&plugin_path) {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
log::error!("Failed to read plugin file {}: {}", plugin_path, err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
plugin_codes.push(plugin_code);
|
||||
}
|
||||
|
||||
plugin_codes
|
||||
}
|
||||
|
||||
fn feed_plugins(plugin_codes: Vec<String>) {
|
||||
for plugin_code in plugin_codes {
|
||||
match VM::eval(plugin_code.as_str()) {
|
||||
Ok(result) => {}
|
||||
Err(error) => {
|
||||
log::error!("Failed to initialise plugin: {}", error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_plugin_identifiers(plugin_paths: Vec<String>) -> Vec<String> {
|
||||
let mut plugin_ids: Vec<String> = Vec::new();
|
||||
for plugin_path in plugin_paths {
|
||||
plugin_ids.push(plugin_path.replace("./plugins/", "").replace(".rb", ""))
|
||||
}
|
||||
plugin_ids
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
const ANIMAL_NAMES: &[&str] = &[
|
||||
"ant", "eel", "mole", "sloth", "ape", "emu", "monkey", "snail", "bat", "falcon", "mouse",
|
||||
"snake", "bear", "fish", "otter", "spider", "bee", "fly", "parrot", "squid", "bird", "fox",
|
||||
"panda", "swan", "bison", "frog", "pig", "tiger", "camel", "gecko", "pigeon", "toad", "cat",
|
||||
"goat", "pony", "turkey", "cobra", "goose", "pug", "turtle", "crow", "hawk", "rabbit", "viper",
|
||||
"deer", "horse", "rat", "wasp", "dog", "jaguar", "raven", "whale", "dove", "koala", "seal",
|
||||
"wolf", "duck", "lion", "shark", "worm", "eagle", "lizard", "sheep", "zebra",
|
||||
];
|
||||
const ANIMAL_COUNT: u64 = ANIMAL_NAMES.len() as u64;
|
||||
|
||||
pub fn to_animal_names(number: u64) -> String {
|
||||
let mut result: Vec<&str> = Vec::new();
|
||||
|
||||
if number == 0 {
|
||||
return ANIMAL_NAMES[0].parse().unwrap();
|
||||
}
|
||||
|
||||
let mut value = number;
|
||||
while value != 0 {
|
||||
let digit = (value % ANIMAL_COUNT) as usize;
|
||||
value /= ANIMAL_COUNT;
|
||||
result.push(ANIMAL_NAMES[digit]);
|
||||
}
|
||||
|
||||
// We calculated the numbers in Little-Endian,
|
||||
// now convert to Big-Endian for backwards compatibility with old data.
|
||||
result.reverse();
|
||||
|
||||
result.join("-")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_animal_names() {
|
||||
assert_eq!(to_animal_names(0), "ant");
|
||||
assert_eq!(to_animal_names(1), "eel");
|
||||
assert_eq!(to_animal_names(64), "eel-ant");
|
||||
assert_eq!(to_animal_names(12345), "sloth-ant-lion");
|
||||
}
|
||||
|
||||
pub fn to_u64(animal_names: &str) -> Result<u64, &str> {
|
||||
let mut result: u64 = 0;
|
||||
|
||||
for animal in animal_names.split('-') {
|
||||
let animal_index = ANIMAL_NAMES.iter().position(|&r| r == animal);
|
||||
match animal_index {
|
||||
None => return Err("Failed to convert animal name to u64!"),
|
||||
Some(idx) => {
|
||||
result = result * ANIMAL_COUNT + (idx as u64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_animal_name_to_u64() {
|
||||
assert_eq!(to_u64("ant"), Ok(0));
|
||||
assert_eq!(to_u64("eel"), Ok(1));
|
||||
assert_eq!(to_u64("eel-ant"), Ok(64));
|
||||
assert_eq!(to_u64("sloth-ant-lion"), Ok(12345));
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
use actix_multipart::Multipart;
|
||||
use actix_web::dev::ServiceRequest;
|
||||
use actix_web::web::Bytes;
|
||||
use actix_web::{error, Error};
|
||||
use actix_web_httpauth::extractors::basic::BasicAuth;
|
||||
use futures::TryStreamExt;
|
||||
|
||||
use crate::args::ARGS;
|
||||
|
||||
pub async fn auth_validator(
|
||||
req: ServiceRequest,
|
||||
creds: BasicAuth,
|
||||
) -> Result<ServiceRequest, (Error, ServiceRequest)> {
|
||||
match (
|
||||
ARGS.auth_basic_username.as_ref(),
|
||||
ARGS.auth_basic_password.as_ref(),
|
||||
creds.password(),
|
||||
) {
|
||||
(Some(conf_user), Some(conf_pwd), Some(cred_pwd))
|
||||
if creds.user_id() == conf_user && conf_pwd == cred_pwd =>
|
||||
{
|
||||
Ok(req)
|
||||
}
|
||||
_ => Err((error::ErrorBadRequest("Invalid login details."), req)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn password_from_multipart(mut payload: Multipart) -> Result<String, Error> {
|
||||
let mut password = String::new();
|
||||
|
||||
while let Some(mut field) = payload.try_next().await? {
|
||||
if field.name() == Some("password") {
|
||||
let password_bytes = field.bytes(1024).await.unwrap_or(Ok(Bytes::new()))?;
|
||||
password = String::from_utf8_lossy(&password_bytes).to_string();
|
||||
}
|
||||
}
|
||||
Ok(password)
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
use crate::{args::ARGS, pasta::Pasta};
|
||||
|
||||
#[cfg(not(feature = "default"))]
|
||||
const PANIC_MSG: &'static str = "Can not run without argument json-db, this version of microbin was compiled without rusqlite support. Make sure you do not pass in no-default-features during compilation";
|
||||
|
||||
#[cfg(feature = "default")]
|
||||
pub fn read_all() -> Vec<Pasta> {
|
||||
if ARGS.json_db {
|
||||
super::db_json::read_all()
|
||||
} else {
|
||||
super::db_sqlite::read_all()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "default"))]
|
||||
pub fn read_all() -> Vec<Pasta> {
|
||||
if ARGS.json_db {
|
||||
super::db_json::read_all()
|
||||
} else {
|
||||
panic!("{}", PANIC_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn insert(pastas: Option<&Vec<Pasta>>, pasta: Option<&Pasta>) {
|
||||
if ARGS.json_db {
|
||||
super::db_json::update_all(pastas.expect("Called insert() without passing Pasta vector"));
|
||||
} else {
|
||||
#[cfg(feature = "default")]
|
||||
super::db_sqlite::insert(pasta.expect("Called insert() without passing new Pasta"));
|
||||
#[cfg(not(feature = "default"))]
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn update(pastas: Option<&Vec<Pasta>>, pasta: Option<&Pasta>) {
|
||||
if ARGS.json_db {
|
||||
super::db_json::update_all(pastas.expect("Called update() without passing Pasta vector"));
|
||||
} else {
|
||||
#[cfg(feature = "default")]
|
||||
super::db_sqlite::update(pasta.expect("Called insert() without passing Pasta to update"));
|
||||
#[cfg(not(feature = "default"))]
|
||||
panic!("{}", PANIC_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn update_all(pastas: &Vec<Pasta>) {
|
||||
if ARGS.json_db {
|
||||
super::db_json::update_all(pastas);
|
||||
} else {
|
||||
#[cfg(feature = "default")]
|
||||
super::db_sqlite::update_all(pastas);
|
||||
#[cfg(not(feature = "default"))]
|
||||
panic!("{}", PANIC_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn delete(pastas: Option<&Vec<Pasta>>, id: Option<u64>) {
|
||||
if ARGS.json_db {
|
||||
super::db_json::update_all(pastas.expect("Called delete() without passing Pasta vector"));
|
||||
} else {
|
||||
#[cfg(feature = "default")]
|
||||
super::db_sqlite::delete_by_id(id.expect("Called delete() without passing Pasta id"));
|
||||
#[cfg(not(feature = "default"))]
|
||||
panic!("{}", PANIC_MSG);
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{BufReader, BufWriter};
|
||||
|
||||
use crate::Pasta;
|
||||
|
||||
static DATABASE_PATH: &str = "pasta_data/database.json";
|
||||
|
||||
pub fn read_all() -> Vec<Pasta> {
|
||||
load_from_file().expect("Failed to load pastas from JSON")
|
||||
}
|
||||
|
||||
pub fn update_all(pastas: &Vec<Pasta>) {
|
||||
save_to_file(pastas);
|
||||
}
|
||||
|
||||
fn save_to_file(pasta_data: &Vec<Pasta>) {
|
||||
// This uses a two stage write. First we write to a new file, if this fails
|
||||
// only the new pasta's are lost. Then we replace the current database with
|
||||
// the new file. This either succeeds or fails. The database is never left
|
||||
// in an undefined state.
|
||||
let tmp_file_path = DATABASE_PATH.to_string() + ".tmp";
|
||||
let tmp_file = File::create(&tmp_file_path).expect(&format!(
|
||||
"failed to create temporary database file for writing. path: {tmp_file_path}"
|
||||
));
|
||||
|
||||
let writer = BufWriter::new(tmp_file);
|
||||
serde_json::to_writer(writer, &pasta_data)
|
||||
.expect("Should be able to write out data to database file");
|
||||
std::fs::rename(tmp_file_path, DATABASE_PATH).expect("Could not update database");
|
||||
}
|
||||
|
||||
fn load_from_file() -> io::Result<Vec<Pasta>> {
|
||||
let file = File::open(DATABASE_PATH);
|
||||
match file {
|
||||
Ok(_) => {
|
||||
let reader = BufReader::new(file.unwrap());
|
||||
let data: Vec<Pasta> = match serde_json::from_reader(reader) {
|
||||
Ok(t) => t,
|
||||
_ => Vec::new(),
|
||||
};
|
||||
Ok(data)
|
||||
}
|
||||
Err(_) => {
|
||||
log::info!("Database file {} not found!", DATABASE_PATH);
|
||||
save_to_file(&Vec::<Pasta>::new());
|
||||
|
||||
log::info!("Database file {} created.", DATABASE_PATH);
|
||||
load_from_file()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,297 +0,0 @@
|
|||
use bytesize::ByteSize;
|
||||
use rusqlite::{params, Connection};
|
||||
|
||||
use crate::{args::ARGS, pasta::PastaFile, Pasta};
|
||||
|
||||
pub fn read_all() -> Vec<Pasta> {
|
||||
select_all_from_db()
|
||||
}
|
||||
|
||||
pub fn update_all(pastas: &[Pasta]) {
|
||||
rewrite_all_to_db(pastas);
|
||||
}
|
||||
|
||||
pub fn rewrite_all_to_db(pasta_data: &[Pasta]) {
|
||||
let conn = Connection::open(format!("{}/database.sqlite", ARGS.data_dir))
|
||||
.expect("Failed to open SQLite database!");
|
||||
|
||||
conn.execute(
|
||||
"
|
||||
DROP TABLE IF EXISTS pasta;
|
||||
);",
|
||||
params![],
|
||||
)
|
||||
.expect("Failed to drop SQLite table for Pasta!");
|
||||
|
||||
conn.execute(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS pasta (
|
||||
id INTEGER PRIMARY KEY,
|
||||
content TEXT NOT NULL,
|
||||
file_name TEXT,
|
||||
file_size INTEGER,
|
||||
extension TEXT NOT NULL,
|
||||
read_only INTEGER NOT NULL,
|
||||
private INTEGER NOT NULL,
|
||||
editable INTEGER NOT NULL,
|
||||
encrypt_server INTEGER NOT NULL,
|
||||
encrypt_client INTEGER NOT NULL,
|
||||
encrypted_key TEXT,
|
||||
created INTEGER NOT NULL,
|
||||
expiration INTEGER NOT NULL,
|
||||
last_read INTEGER NOT NULL,
|
||||
read_count INTEGER NOT NULL,
|
||||
burn_after_reads INTEGER NOT NULL,
|
||||
pasta_type TEXT NOT NULL
|
||||
);",
|
||||
params![],
|
||||
)
|
||||
.expect("Failed to create SQLite table for Pasta!");
|
||||
|
||||
for pasta in pasta_data.iter() {
|
||||
conn.execute(
|
||||
"INSERT INTO pasta (
|
||||
id,
|
||||
content,
|
||||
file_name,
|
||||
file_size,
|
||||
extension,
|
||||
private,
|
||||
read_only,
|
||||
editable,
|
||||
encrypt_server,
|
||||
encrypt_client,
|
||||
encrypted_key,
|
||||
created,
|
||||
expiration,
|
||||
last_read,
|
||||
read_count,
|
||||
burn_after_reads,
|
||||
pasta_type
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)",
|
||||
params![
|
||||
pasta.id,
|
||||
pasta.content,
|
||||
pasta.file.as_ref().map_or("", |f| f.name.as_str()),
|
||||
pasta.file.as_ref().map_or(0, |f| f.size.as_u64()),
|
||||
pasta.extension,
|
||||
pasta.private as i32,
|
||||
pasta.readonly as i32,
|
||||
pasta.editable as i32,
|
||||
pasta.encrypt_server as i32,
|
||||
pasta.encrypt_client as i32,
|
||||
pasta.encrypted_key.as_deref(),
|
||||
pasta.created,
|
||||
pasta.expiration,
|
||||
pasta.last_read,
|
||||
pasta.read_count,
|
||||
pasta.burn_after_reads,
|
||||
pasta.pasta_type,
|
||||
],
|
||||
)
|
||||
.expect("Failed to insert pasta.");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_all_from_db() -> Vec<Pasta> {
|
||||
let conn = Connection::open(format!("{}/database.sqlite", ARGS.data_dir))
|
||||
.expect("Failed to open SQLite database!");
|
||||
|
||||
conn.execute(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS pasta (
|
||||
id INTEGER PRIMARY KEY,
|
||||
content TEXT NOT NULL,
|
||||
file_name TEXT,
|
||||
file_size INTEGER,
|
||||
extension TEXT NOT NULL,
|
||||
read_only INTEGER NOT NULL,
|
||||
private INTEGER NOT NULL,
|
||||
editable INTEGER NOT NULL,
|
||||
encrypt_server INTEGER NOT NULL,
|
||||
encrypt_client INTEGER NOT NULL,
|
||||
encrypted_key TEXT,
|
||||
created INTEGER NOT NULL,
|
||||
expiration INTEGER NOT NULL,
|
||||
last_read INTEGER NOT NULL,
|
||||
read_count INTEGER NOT NULL,
|
||||
burn_after_reads INTEGER NOT NULL,
|
||||
pasta_type TEXT NOT NULL
|
||||
);",
|
||||
params![],
|
||||
)
|
||||
.expect("Failed to create SQLite table for Pasta!");
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT * FROM pasta ORDER BY created ASC")
|
||||
.expect("Failed to prepare SQL statement to load pastas");
|
||||
|
||||
let pasta_iter = stmt
|
||||
.query_map([], |row| {
|
||||
Ok(Pasta {
|
||||
id: row.get(0)?,
|
||||
content: row.get(1)?,
|
||||
file: if let (Some(file_name), Some(file_size)) = (row.get(2)?, row.get(3)?) {
|
||||
let file_size: u64 = file_size;
|
||||
if file_name != "" && file_size != 0 {
|
||||
Some(PastaFile {
|
||||
name: file_name,
|
||||
size: ByteSize::b(file_size),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
extension: row.get(4)?,
|
||||
readonly: row.get(5)?,
|
||||
private: row.get(6)?,
|
||||
editable: row.get(7)?,
|
||||
encrypt_server: row.get(8)?,
|
||||
encrypt_client: row.get(9)?,
|
||||
encrypted_key: row.get(10)?,
|
||||
created: row.get(11)?,
|
||||
expiration: row.get(12)?,
|
||||
last_read: row.get(13)?,
|
||||
read_count: row.get(14)?,
|
||||
burn_after_reads: row.get(15)?,
|
||||
pasta_type: row.get(16)?,
|
||||
})
|
||||
})
|
||||
.expect("Failed to select Pastas from SQLite database.");
|
||||
|
||||
pasta_iter
|
||||
.map(|r| r.expect("Failed to get pasta"))
|
||||
.collect::<Vec<Pasta>>()
|
||||
}
|
||||
|
||||
pub fn insert(pasta: &Pasta) {
|
||||
let conn = Connection::open(format!("{}/database.sqlite", ARGS.data_dir))
|
||||
.expect("Failed to open SQLite database!");
|
||||
|
||||
conn.execute(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS pasta (
|
||||
id INTEGER PRIMARY KEY,
|
||||
content TEXT NOT NULL,
|
||||
file_name TEXT,
|
||||
file_size INTEGER,
|
||||
extension TEXT NOT NULL,
|
||||
read_only INTEGER NOT NULL,
|
||||
private INTEGER NOT NULL,
|
||||
editable INTEGER NOT NULL,
|
||||
encrypt_server INTEGER NOT NULL,
|
||||
encrypt_client INTEGER NOT NULL,
|
||||
encrypted_key TEXT,
|
||||
created INTEGER NOT NULL,
|
||||
expiration INTEGER NOT NULL,
|
||||
last_read INTEGER NOT NULL,
|
||||
read_count INTEGER NOT NULL,
|
||||
burn_after_reads INTEGER NOT NULL,
|
||||
pasta_type TEXT NOT NULL
|
||||
);",
|
||||
params![],
|
||||
)
|
||||
.expect("Failed to create SQLite table for Pasta!");
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO pasta (
|
||||
id,
|
||||
content,
|
||||
file_name,
|
||||
file_size,
|
||||
extension,
|
||||
read_only,
|
||||
private,
|
||||
editable,
|
||||
encrypt_server,
|
||||
encrypt_client,
|
||||
encrypted_key,
|
||||
created,
|
||||
expiration,
|
||||
last_read,
|
||||
read_count,
|
||||
burn_after_reads,
|
||||
pasta_type
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)",
|
||||
params![
|
||||
pasta.id,
|
||||
pasta.content,
|
||||
pasta.file.as_ref().map_or("", |f| f.name.as_str()),
|
||||
pasta.file.as_ref().map_or(0, |f| f.size.as_u64()),
|
||||
pasta.extension,
|
||||
pasta.readonly as i32,
|
||||
pasta.private as i32,
|
||||
pasta.editable as i32,
|
||||
pasta.encrypt_server as i32,
|
||||
pasta.encrypt_client as i32,
|
||||
pasta.encrypted_key.as_deref(),
|
||||
pasta.created,
|
||||
pasta.expiration,
|
||||
pasta.last_read,
|
||||
pasta.read_count,
|
||||
pasta.burn_after_reads,
|
||||
pasta.pasta_type,
|
||||
],
|
||||
)
|
||||
.expect("Failed to insert pasta.");
|
||||
}
|
||||
|
||||
pub fn update(pasta: &Pasta) {
|
||||
let conn = Connection::open(format!("{}/database.sqlite", ARGS.data_dir))
|
||||
.expect("Failed to open SQLite database!");
|
||||
|
||||
conn.execute(
|
||||
"UPDATE pasta SET
|
||||
content = ?2,
|
||||
file_name = ?3,
|
||||
file_size = ?4,
|
||||
extension = ?5,
|
||||
read_only = ?6,
|
||||
private = ?7,
|
||||
editable = ?8,
|
||||
encrypt_server = ?9,
|
||||
encrypt_client = ?10,
|
||||
encrypted_key = ?11,
|
||||
created = ?12,
|
||||
expiration = ?13,
|
||||
last_read = ?14,
|
||||
read_count = ?15,
|
||||
burn_after_reads = ?16,
|
||||
pasta_type = ?17
|
||||
WHERE id = ?1;",
|
||||
params![
|
||||
pasta.id,
|
||||
pasta.content,
|
||||
pasta.file.as_ref().map_or("", |f| f.name.as_str()),
|
||||
pasta.file.as_ref().map_or(0, |f| f.size.as_u64()),
|
||||
pasta.extension,
|
||||
pasta.readonly as i32,
|
||||
pasta.private as i32,
|
||||
pasta.editable as i32,
|
||||
pasta.encrypt_server as i32,
|
||||
pasta.encrypt_client as i32,
|
||||
pasta.encrypted_key.as_deref(),
|
||||
pasta.created,
|
||||
pasta.expiration,
|
||||
pasta.last_read,
|
||||
pasta.read_count,
|
||||
pasta.burn_after_reads,
|
||||
pasta.pasta_type,
|
||||
],
|
||||
)
|
||||
.expect("Failed to update pasta.");
|
||||
}
|
||||
|
||||
pub fn delete_by_id(id: u64) {
|
||||
let conn = Connection::open(format!("{}/database.sqlite", ARGS.data_dir))
|
||||
.expect("Failed to open SQLite database!");
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM pasta
|
||||
WHERE id = ?1;",
|
||||
params![id],
|
||||
)
|
||||
.expect("Failed to delete pasta.");
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
use harsh::Harsh;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref HARSH: Harsh = Harsh::builder().length(6).build().unwrap();
|
||||
}
|
||||
|
||||
pub fn to_hashids(number: u64) -> String {
|
||||
HARSH.encode(&[number])
|
||||
}
|
||||
|
||||
pub fn to_u64(hash_id: &str) -> Result<u64, &str> {
|
||||
let ids = HARSH
|
||||
.decode(hash_id)
|
||||
.map_err(|_e| "Failed to decode hash ID")?;
|
||||
let id = ids.first().ok_or("No ID found in hash ID")?;
|
||||
Ok(*id)
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
#[cfg(not(any(feature = "default", feature = "__rustcrypto-tls")))]
|
||||
compile_error! {"You must either have the default feature enabled (remove
|
||||
the no-default-features rust argument) or the no-c-deps feature"}
|
||||
|
||||
#[cfg(feature = "default")]
|
||||
pub fn new() -> reqwest::blocking::Client {
|
||||
reqwest::blocking::Client::new()
|
||||
}
|
||||
|
||||
#[cfg(feature = "default")]
|
||||
pub fn new_async() -> reqwest::Client {
|
||||
reqwest::Client::new()
|
||||
}
|
||||
|
||||
#[cfg(feature = "__rustcrypto-tls")]
|
||||
pub fn new() -> reqwest::blocking::Client {
|
||||
reqwest::blocking::Client::builder()
|
||||
.use_preconfigured_tls(tls_config())
|
||||
.build()
|
||||
.expect("Could not create HTTP client.")
|
||||
}
|
||||
|
||||
#[cfg(feature = "__rustcrypto-tls")]
|
||||
pub fn new_async() -> reqwest::Client {
|
||||
reqwest::Client::builder()
|
||||
.use_preconfigured_tls(tls_config())
|
||||
.build()
|
||||
.expect("Could not create HTTP client.")
|
||||
}
|
||||
|
||||
#[cfg(feature = "__rustcrypto-tls")]
|
||||
fn tls_config() -> rustls::ClientConfig {
|
||||
use std::sync::Arc;
|
||||
|
||||
let root_store = rustls::RootCertStore {
|
||||
roots: webpki_roots::TLS_SERVER_ROOTS.into(),
|
||||
};
|
||||
|
||||
let provider = Arc::new(rustls_rustcrypto::provider());
|
||||
rustls::ClientConfig::builder_with_provider(provider)
|
||||
.with_safe_default_protocol_versions()
|
||||
.expect("Should support safe default protocols")
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth()
|
||||
}
|
150
src/util/misc.rs
150
src/util/misc.rs
|
@ -1,150 +0,0 @@
|
|||
use crate::args::ARGS;
|
||||
use linkify::{LinkFinder, LinkKind};
|
||||
use magic_crypt::{new_magic_crypt, MagicCryptTrait};
|
||||
use qrcode_generator::QrCodeEcc;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{BufReader, Read, Write};
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::Pasta;
|
||||
|
||||
use super::db::delete;
|
||||
|
||||
pub fn remove_expired(pastas: &mut Vec<Pasta>) {
|
||||
// get current time - this will be needed to check which pastas have expired
|
||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => {
|
||||
log::error!("SystemTime before UNIX EPOCH!");
|
||||
0
|
||||
}
|
||||
} as i64;
|
||||
|
||||
pastas.retain(|p| {
|
||||
// keep if:
|
||||
// expiration is `never` or not reached
|
||||
// AND
|
||||
// read count is less than burn limit, or no limit set
|
||||
// AND
|
||||
// has been read in the last N days where N is the arg --gc-days OR N is 0 (no GC)
|
||||
if (p.expiration == 0 || p.expiration > timenow)
|
||||
&& (p.read_count < p.burn_after_reads || p.burn_after_reads == 0)
|
||||
&& (p.last_read_days_ago() < ARGS.gc_days || ARGS.gc_days == 0)
|
||||
{
|
||||
// keep
|
||||
true
|
||||
} else {
|
||||
// remove from database
|
||||
delete(None, Some(p.id));
|
||||
|
||||
// remove the file itself
|
||||
if let Some(file) = &p.file {
|
||||
if fs::remove_file(format!(
|
||||
"{}/attachments/{}/{}",
|
||||
ARGS.data_dir,
|
||||
p.id_as_animals(),
|
||||
file.name()
|
||||
))
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Failed to delete file {}!", file.name())
|
||||
}
|
||||
|
||||
// and remove the containing directory
|
||||
if fs::remove_dir(format!(
|
||||
"{}/attachments/{}/",
|
||||
ARGS.data_dir,
|
||||
p.id_as_animals()
|
||||
))
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Failed to delete directory {}!", file.name())
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn string_to_qr_svg(str: &str) -> String {
|
||||
qrcode_generator::to_svg_to_string(str, QrCodeEcc::Low, 256, None::<&str>).unwrap()
|
||||
}
|
||||
|
||||
pub fn is_valid_url(url: &str) -> bool {
|
||||
let finder = LinkFinder::new();
|
||||
let spans: Vec<_> = finder.spans(url).collect();
|
||||
spans[0].as_str() == url && Some(&LinkKind::Url) == spans[0].kind()
|
||||
}
|
||||
|
||||
pub fn encrypt(text_str: &str, key_str: &str) -> String {
|
||||
if text_str.is_empty() {
|
||||
return String::from("");
|
||||
}
|
||||
|
||||
let mc = new_magic_crypt!(key_str, 256);
|
||||
|
||||
mc.encrypt_str_to_base64(text_str)
|
||||
}
|
||||
|
||||
pub fn decrypt(text_str: &str, key_str: &str) -> Result<String, magic_crypt::MagicCryptError> {
|
||||
if text_str.is_empty() {
|
||||
return Ok(String::from(""));
|
||||
}
|
||||
|
||||
let mc = new_magic_crypt!(key_str, 256);
|
||||
|
||||
mc.decrypt_base64_to_string(text_str)
|
||||
}
|
||||
|
||||
pub fn encrypt_file(
|
||||
passphrase: &str,
|
||||
input_file_path: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Read the input file into memory
|
||||
let file = File::open(input_file_path).expect("Tried to encrypt non-existent file");
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut input_data = Vec::new();
|
||||
reader.read_to_end(&mut input_data)?;
|
||||
|
||||
// Create a MagicCrypt instance with the given passphrase
|
||||
let mc = new_magic_crypt!(passphrase, 256);
|
||||
|
||||
// Encrypt the input data
|
||||
let ciphertext = mc.encrypt_bytes_to_bytes(&input_data[..]);
|
||||
|
||||
// Write the encrypted data to a new file with the .enc extension
|
||||
let mut f = File::create(
|
||||
Path::new(input_file_path)
|
||||
.with_file_name("data")
|
||||
.with_extension("enc"),
|
||||
)?;
|
||||
f.write_all(ciphertext.as_slice())?;
|
||||
|
||||
// Delete the original input file
|
||||
// input_file.seek(SeekFrom::Start(0))?;
|
||||
fs::remove_file(input_file_path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decrypt_file(
|
||||
passphrase: &str,
|
||||
input_file: &File,
|
||||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
// Read the input file into memory
|
||||
let mut reader = BufReader::new(input_file);
|
||||
let mut ciphertext = Vec::new();
|
||||
reader.read_to_end(&mut ciphertext)?;
|
||||
|
||||
// Create a MagicCrypt instance with the given passphrase
|
||||
let mc = new_magic_crypt!(passphrase, 256);
|
||||
// Encrypt the input data
|
||||
let res = mc.decrypt_bytes_to_bytes(&ciphertext[..]);
|
||||
|
||||
if res.is_err() {
|
||||
return Err("Failed to decrypt file".into());
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::{Style, ThemeSet};
|
||||
use syntect::html::append_highlighted_html_for_styled_line;
|
||||
use syntect::html::IncludeBackground::No;
|
||||
use syntect::parsing::SyntaxSet;
|
||||
use syntect::util::LinesWithEndings;
|
||||
|
||||
pub fn html_highlight(text: &str, extension: &str) -> String {
|
||||
let ps = SyntaxSet::load_defaults_newlines();
|
||||
let ts = ThemeSet::load_defaults();
|
||||
|
||||
let syntax = ps
|
||||
.find_syntax_by_extension(extension)
|
||||
.or_else(|| Option::from(ps.find_syntax_plain_text()))
|
||||
.unwrap();
|
||||
let mut h = HighlightLines::new(syntax, &ts.themes["InspiredGitHub"]);
|
||||
|
||||
let mut highlighted_content: String = String::from("");
|
||||
|
||||
for line in LinesWithEndings::from(text) {
|
||||
let ranges: Vec<(Style, &str)> = h.highlight_line(line, &ps).unwrap();
|
||||
append_highlighted_html_for_styled_line(&ranges[..], No, &mut highlighted_content)
|
||||
.expect("Failed to append highlighted line!");
|
||||
}
|
||||
|
||||
let mut highlighted_content2: String = String::from("");
|
||||
for line in highlighted_content.lines() {
|
||||
highlighted_content2 += &*format!("<code-line>{}</code-line>\n", line);
|
||||
}
|
||||
|
||||
// Rewrite colours to ones that are compatible with water.css and both light/dark modes
|
||||
highlighted_content2 = highlighted_content2.replace("style=\"color:#323232;\"", "");
|
||||
highlighted_content2 =
|
||||
highlighted_content2.replace("style=\"color:#183691;\"", "style=\"color:blue;\"");
|
||||
|
||||
highlighted_content2
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
use std::{
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use crate::args::ARGS;
|
||||
|
||||
pub fn start_telemetry_thread() {
|
||||
// Start a new thread that calls the send_telemetry function every 24 hours
|
||||
thread::spawn(|| {
|
||||
let mut last_run = Instant::now();
|
||||
loop {
|
||||
let _ = send_telemetry();
|
||||
|
||||
// Wait for 24 hours since the last run
|
||||
let next_run = last_run + Duration::from_secs(60 * 60 * 24);
|
||||
let now = Instant::now();
|
||||
if next_run > now {
|
||||
thread::sleep(next_run - now);
|
||||
}
|
||||
last_run = Instant::now();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn send_telemetry() -> Result<(), reqwest::Error> {
|
||||
// Convert the telemetry object to JSON
|
||||
let json_body = json!(ARGS.to_owned().without_secrets().to_owned()).to_string();
|
||||
|
||||
// Send the telemetry data to the API
|
||||
crate::util::http_client::new()
|
||||
.post("https://api.microbin.eu/telemetry/")
|
||||
.header("Content-Type", "application/json")
|
||||
.body(json_body)
|
||||
.send()?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Version {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
pub patch: u32,
|
||||
pub title: Cow<'static, str>,
|
||||
pub long_title: Cow<'static, str>,
|
||||
pub description: Cow<'static, str>,
|
||||
pub date: Cow<'static, str>,
|
||||
pub update_type: Cow<'static, str>,
|
||||
}
|
||||
|
||||
pub static CURRENT_VERSION: Version = Version {
|
||||
major: 2,
|
||||
minor: 0,
|
||||
patch: 4,
|
||||
title: Cow::Borrowed("2.0.4"),
|
||||
long_title: Cow::Borrowed("Version 2.0.4, Build 20230711"),
|
||||
description: Cow::Borrowed("This version includes bug fixes and performance improvements."),
|
||||
date: Cow::Borrowed("2023-07-11"),
|
||||
update_type: Cow::Borrowed("beta"),
|
||||
};
|
||||
|
||||
impl Version {
|
||||
pub fn newer_than(&self, other: &Version) -> bool {
|
||||
if self.major != other.major {
|
||||
self.major > other.major
|
||||
} else if self.minor != other.minor {
|
||||
self.minor > other.minor
|
||||
} else {
|
||||
self.patch > other.patch
|
||||
}
|
||||
}
|
||||
|
||||
pub fn newer_than_current(&self) -> bool {
|
||||
self.newer_than(&CURRENT_VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch_latest_version() -> Result<Version, reqwest::Error> {
|
||||
let url = "https://api.microbin.eu/version/";
|
||||
let http_client = crate::util::http_client::new_async();
|
||||
let response = http_client.get(url).send().await?;
|
||||
let version = response.json::<Version>().await?;
|
||||
|
||||
Ok(version)
|
||||
}
|
1
static/water.css
Normal file
1
static/water.css
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,448 +0,0 @@
|
|||
{% include "header.html" %}
|
||||
|
||||
<h2>Welcome to MicroBin</h2>
|
||||
<div style="height: 200px;">
|
||||
<div style="float: left">
|
||||
<h4>Links</h4>
|
||||
<a href="https://microbin.eu/docs/intro" style="margin-right: 1rem">Documentation and Help</a>
|
||||
<br>
|
||||
<a href="https://github.com/szabodanika/microbin" style="margin-right: 1rem">Source Code</a>
|
||||
<br>
|
||||
<a href="https://github.com/szabodanika/microbin/issues" style="margin-right: 1rem">Feedback</a>
|
||||
<br>
|
||||
<a href="https://microbin.eu/donate">Donate and Sponsor</a>
|
||||
</div>
|
||||
|
||||
<div style="float: right">
|
||||
<h4>Info</h4>
|
||||
<table style="width: 400px">
|
||||
<tr>
|
||||
<td><b>Version</b></td>
|
||||
<td>{{version_string}} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Status</b></td>
|
||||
<td>{{status}} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Uploads</b></td>
|
||||
<td>{{pastas.len()}} </td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>Update</h4>
|
||||
{% if update.is_some() %}
|
||||
<p><b>Update available</b> {{update.as_ref().unwrap().long_title}}</p>
|
||||
<p><b>Date</b> {{update.as_ref().unwrap().date}}</p>
|
||||
<p><b>Update type</b> {{update.as_ref().unwrap().update_type}}</p>
|
||||
<p><b>Description</b> {{update.as_ref().unwrap().description}}</p>
|
||||
{%- else %}
|
||||
<p>No updates available.</p>
|
||||
{%- endif %}
|
||||
|
||||
|
||||
{% if message != "" %}
|
||||
<h4>Messages</h4>
|
||||
<p>{{message}}</p>
|
||||
{%- endif %}
|
||||
|
||||
|
||||
<h3>Uploads</h3>
|
||||
{% if args.pure_html %}
|
||||
<table border="1" style="width: 100%;">
|
||||
{% else %}
|
||||
<table style="width: 100%; font-size: smaller;">
|
||||
{% endif %}
|
||||
<thead>
|
||||
<th style="width: 15%;">
|
||||
Key
|
||||
</th>
|
||||
<th style="width: 15%;">
|
||||
Valid
|
||||
</th>
|
||||
<th style="width: 8%;">
|
||||
Size
|
||||
</th>
|
||||
<th>
|
||||
Encryption
|
||||
</th>
|
||||
<th style="width: 5%;">
|
||||
Priv.
|
||||
</th>
|
||||
<th style="width: 5%;">
|
||||
Edit.
|
||||
</th>
|
||||
<th style="width: 8%;">
|
||||
Content
|
||||
</th>
|
||||
<th>
|
||||
Hits
|
||||
</th>
|
||||
<th style="width: 8%;">
|
||||
<!-- Actions -->
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pasta in pastas %}
|
||||
{% if pasta.pasta_type == "text" %}
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
href="{{ args.public_path_as_str()}}/upload/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{pasta.created_as_string()}}
|
||||
→
|
||||
{{pasta.expiration_as_string()}}
|
||||
</td>
|
||||
<td>
|
||||
{{pasta.total_size_as_string()}}
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.encrypt_client %}
|
||||
CLIENT
|
||||
{%- endif %}
|
||||
{% if pasta.encrypt_client && pasta.encrypt_server%}
|
||||
+
|
||||
{%- endif %}
|
||||
{% if pasta.encrypt_server %}
|
||||
SERVER
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.private %}
|
||||
✔️
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.editable %}
|
||||
✔️
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.content != "" %}
|
||||
<a style="margin-right:1rem"
|
||||
href="{{ args.public_path_as_str()}}/raw/{{pasta.id_as_animals()}}">Text</a>
|
||||
{%- endif %}
|
||||
{% if pasta.file.is_some() %}
|
||||
<a style="margin-right:1rem" href="{{ args.public_path_as_str() }}/file/{{pasta.id_as_animals()}}">
|
||||
{% if pasta.file.as_ref().unwrap().is_image() %}
|
||||
Image
|
||||
{%- else if pasta.file.as_ref().unwrap().is_video() %}
|
||||
Video
|
||||
{%- else %}
|
||||
File
|
||||
{%- endif %}
|
||||
</a>
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if args.show_read_stats %} {% if pasta.read_count == 1 %}
|
||||
<span style="font-size: small">{{pasta.read_count}} hits <br> last
|
||||
{{pasta.short_last_read_time_ago_as_string()}}</span>
|
||||
{%- else %}
|
||||
<span style="font-size: small">{{pasta.read_count}} hits <br> last
|
||||
{{pasta.short_last_read_time_ago_as_string()}}</span>
|
||||
{%- endif %} {%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.editable %}
|
||||
<a style="margin-right:1rem" href="{{ args.public_path_as_str()
|
||||
}}/edit/{{pasta.id_as_animals()}}">Edit</a>
|
||||
<br>
|
||||
{%- endif %}
|
||||
<a href="{{ args.public_path_as_str() }}/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<h3>URL Redirects</h3>
|
||||
{% if args.pure_html %}
|
||||
<table border="1" style="width: 100%;">
|
||||
{% else %}
|
||||
<table style="width: 100%; font-size: smaller;">
|
||||
{% endif %}
|
||||
<thead>
|
||||
<th style="width: 15%;">
|
||||
Key
|
||||
</th>
|
||||
<th style="width: 15%;">
|
||||
Valid
|
||||
</th>
|
||||
<th>
|
||||
Encryption
|
||||
</th>
|
||||
<th style="width: 5%;">
|
||||
Priv.
|
||||
</th>
|
||||
<th style="width: 5%;">
|
||||
Edit.
|
||||
</th>
|
||||
<th style="width: 8%;">
|
||||
Content
|
||||
</th>
|
||||
<th>
|
||||
Hits
|
||||
</th>
|
||||
<th style="width: 8%;">
|
||||
<!-- Actions -->
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pasta in pastas %}
|
||||
{% if pasta.pasta_type == "url" %}
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
href="{{ args.public_path_as_str()}}/upload/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{pasta.created_as_string()}}
|
||||
→
|
||||
{{pasta.expiration_as_string()}}
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.encrypt_client %}
|
||||
CLIENT
|
||||
{%- endif %}
|
||||
{% if pasta.encrypt_client && pasta.encrypt_server%}
|
||||
+
|
||||
{%- endif %}
|
||||
{% if pasta.encrypt_server %}
|
||||
SERVER
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.private %}
|
||||
✔️
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.editable %}
|
||||
✔️
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.content != "" %}
|
||||
<a style="margin-right:1rem"
|
||||
href="{{ args.public_path_as_str()}}/raw/{{pasta.id_as_animals()}}">Text</a>
|
||||
{%- endif %}
|
||||
{% if pasta.file.is_some() %}
|
||||
<a style="margin-right:1rem"
|
||||
href="{{ args.public_path_as_str() }}/file/{{pasta.id_as_animals()}}">
|
||||
{% if pasta.file.as_ref().unwrap().is_image() %}
|
||||
Image
|
||||
{%- else if pasta.file.as_ref().unwrap().is_video() %}
|
||||
Video
|
||||
{%- else %}
|
||||
File
|
||||
{%- endif %}
|
||||
</a>
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if args.show_read_stats %} {% if pasta.read_count == 1 %}
|
||||
<span style="font-size: small">{{pasta.read_count}} hits <br> last
|
||||
{{pasta.short_last_read_time_ago_as_string()}}</span>
|
||||
{%- else %}
|
||||
<span style="font-size: small">{{pasta.read_count}} hits <br> last
|
||||
{{pasta.short_last_read_time_ago_as_string()}}</span>
|
||||
{%- endif %} {%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.editable %}
|
||||
<a style="margin-right:1rem" href="{{ args.public_path_as_str()
|
||||
}}/edit/{{pasta.id_as_animals()}}">Edit</a>
|
||||
<br>
|
||||
{%- endif %}
|
||||
<a href="{{ args.public_path_as_str() }}/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
|
||||
|
||||
<h3>Environmental Variables</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 35%">Argument</th>
|
||||
<th style="width: 15%">Value</th>
|
||||
<th style="width: 35%">Argument</th>
|
||||
<th style="width: 15%">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>auth_basic_username</td>
|
||||
{% if args.auth_basic_username.as_ref().is_some() %}
|
||||
<td>set</td>
|
||||
{% else %}
|
||||
<td>unset</td>
|
||||
{% endif %}
|
||||
|
||||
<td>auth_basic_password</td>
|
||||
{% if args.auth_basic_password.as_ref().is_some() %}
|
||||
<td>set</td>
|
||||
{% else %}
|
||||
<td>unset</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>editable</td>
|
||||
<td>{{ args.editable }}</td>
|
||||
|
||||
<td>footer_text</td>
|
||||
{% if args.footer_text.as_ref().is_some() %}
|
||||
<td>{{ args.footer_text.as_ref().unwrap() }}</td>
|
||||
{% else %}
|
||||
<td>unset</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>hide_footer</td>
|
||||
<td>{{ args.hide_footer }}</td>
|
||||
<td>hide_header</td>
|
||||
<td>{{ args.hide_header }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>hide_logo</td>
|
||||
<td>{{ args.hide_logo }}</td>
|
||||
<td>no_listing</td>
|
||||
<td>{{ args.no_listing }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>highlightsyntax</td>
|
||||
<td>{{ args.highlightsyntax }}</td>
|
||||
<td>port</td>
|
||||
<td>{{ args.port }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>bind</td>
|
||||
<td>{{ args.bind }}</td>
|
||||
<td>private</td>
|
||||
<td>{{ args.private }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pure_html</td>
|
||||
<td>{{ args.pure_html }}</td>
|
||||
<td>json_db</td>
|
||||
<td>{{ args.json_db }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>public_path</td>
|
||||
{% if args.public_path.as_ref().is_some() %}
|
||||
<td>{{ args.public_path_as_str() }}</td>
|
||||
{% else %}
|
||||
<td>unset</td>
|
||||
{% endif %}
|
||||
<td>short_path</td>
|
||||
{% if args.short_path.as_ref().is_some() %}
|
||||
<td>{{ args.short_path_as_str() }}</td>
|
||||
{% else %}
|
||||
<td>unset</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>readonly</td>
|
||||
<td>{{ args.readonly }}</td>
|
||||
<td>show_read_stats</td>
|
||||
<td>{{ args.show_read_stats }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>title</td>
|
||||
{% if args.title.as_ref().is_some() %}
|
||||
<td>{{ args.title.as_ref().unwrap() }}</td>
|
||||
{% else %}
|
||||
<td>unset</td>
|
||||
{% endif %}
|
||||
<td>threads</td>
|
||||
<td>{{ args.threads }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>gc_days</td>
|
||||
<td>{{ args.gc_days }}</td>
|
||||
<td>enable_burn_after</td>
|
||||
<td>{{ args.enable_burn_after }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>default_burn_after</td>
|
||||
<td>{{ args.default_burn_after }}</td>
|
||||
<td>wide</td>
|
||||
<td>{{ args.wide }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>qr</td>
|
||||
<td>{{ args.qr }}</td>
|
||||
<td>eternal_pasta</td>
|
||||
<td>{{ args.eternal_pasta }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enable_readonly</td>
|
||||
<td>{{ args.enable_readonly }}</td>
|
||||
<td>default_expiry</td>
|
||||
<td>{{ args.default_expiry }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>no_file_upload</td>
|
||||
<td>{{ args.no_file_upload }}</td>
|
||||
<td>custom_css</td>
|
||||
{% if args.custom_css.as_ref().is_some() %}
|
||||
<td>{{ args.custom_css.as_ref().unwrap() }}</td>
|
||||
{% else %}
|
||||
<td>unset</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>hash_ids</td>
|
||||
<td>{{ args.hash_ids }}</td>
|
||||
<td>encryption_client_side</td>
|
||||
<td>{{ args.encryption_client_side }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>encryption_server_side</td>
|
||||
<td>{{ args.encryption_server_side }}</td>
|
||||
<td>max_file_size_encrypted_mb</td>
|
||||
<td>{{ args.max_file_size_encrypted_mb }} MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>max_file_size_unencrypted_mb</td>
|
||||
<td>{{ args.max_file_size_unencrypted_mb }} MB</td>
|
||||
<td>uploader_password</td>
|
||||
{% if args.uploader_password.as_ref().is_some() %}
|
||||
<td>set</td>
|
||||
{% else %}
|
||||
<td>unset</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include "footer.html" %}
|
||||
<script>
|
||||
const copyURLBtns = document.getElementsByClassName("copy-button");
|
||||
|
||||
for (var i = 0; i < copyURLBtns.length; i++) {
|
||||
copyURLBtns.item(i).addEventListener("click", event => {
|
||||
event.srcElement
|
||||
navigator.clipboard.writeText(event.srcElement.getAttribute("data-url"))
|
||||
event.srcElement.innerHTML = "Copied"
|
||||
setTimeout(() => {
|
||||
event.srcElement.innerHTML = "Copy"
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -1,803 +0,0 @@
|
|||
/*! MIT License. Copyright 2015-2018 Richard Moore <me@ricmoo.com>. See LICENSE.txt. */
|
||||
(function(root) {
|
||||
"use strict";
|
||||
|
||||
function checkInt(value) {
|
||||
return (parseInt(value) === value);
|
||||
}
|
||||
|
||||
function checkInts(arrayish) {
|
||||
if (!checkInt(arrayish.length)) { return false; }
|
||||
|
||||
for (var i = 0; i < arrayish.length; i++) {
|
||||
if (!checkInt(arrayish[i]) || arrayish[i] < 0 || arrayish[i] > 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function coerceArray(arg, copy) {
|
||||
|
||||
// ArrayBuffer view
|
||||
if (arg.buffer && arg.name === 'Uint8Array') {
|
||||
|
||||
if (copy) {
|
||||
if (arg.slice) {
|
||||
arg = arg.slice();
|
||||
} else {
|
||||
arg = Array.prototype.slice.call(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
// It's an array; check it is a valid representation of a byte
|
||||
if (Array.isArray(arg)) {
|
||||
if (!checkInts(arg)) {
|
||||
throw new Error('Array contains invalid value: ' + arg);
|
||||
}
|
||||
|
||||
return new Uint8Array(arg);
|
||||
}
|
||||
|
||||
// Something else, but behaves like an array (maybe a Buffer? Arguments?)
|
||||
if (checkInt(arg.length) && checkInts(arg)) {
|
||||
return new Uint8Array(arg);
|
||||
}
|
||||
|
||||
throw new Error('unsupported array-like object');
|
||||
}
|
||||
|
||||
function createArray(length) {
|
||||
return new Uint8Array(length);
|
||||
}
|
||||
|
||||
function copyArray(sourceArray, targetArray, targetStart, sourceStart, sourceEnd) {
|
||||
if (sourceStart != null || sourceEnd != null) {
|
||||
if (sourceArray.slice) {
|
||||
sourceArray = sourceArray.slice(sourceStart, sourceEnd);
|
||||
} else {
|
||||
sourceArray = Array.prototype.slice.call(sourceArray, sourceStart, sourceEnd);
|
||||
}
|
||||
}
|
||||
targetArray.set(sourceArray, targetStart);
|
||||
}
|
||||
|
||||
|
||||
|
||||
var convertUtf8 = (function() {
|
||||
function toBytes(text) {
|
||||
var result = [], i = 0;
|
||||
text = encodeURI(text);
|
||||
while (i < text.length) {
|
||||
var c = text.charCodeAt(i++);
|
||||
|
||||
// if it is a % sign, encode the following 2 bytes as a hex value
|
||||
if (c === 37) {
|
||||
result.push(parseInt(text.substr(i, 2), 16))
|
||||
i += 2;
|
||||
|
||||
// otherwise, just the actual byte
|
||||
} else {
|
||||
result.push(c)
|
||||
}
|
||||
}
|
||||
|
||||
return coerceArray(result);
|
||||
}
|
||||
|
||||
function fromBytes(bytes) {
|
||||
var result = [], i = 0;
|
||||
|
||||
while (i < bytes.length) {
|
||||
var c = bytes[i];
|
||||
|
||||
if (c < 128) {
|
||||
result.push(String.fromCharCode(c));
|
||||
i++;
|
||||
} else if (c > 191 && c < 224) {
|
||||
result.push(String.fromCharCode(((c & 0x1f) << 6) | (bytes[i + 1] & 0x3f)));
|
||||
i += 2;
|
||||
} else {
|
||||
result.push(String.fromCharCode(((c & 0x0f) << 12) | ((bytes[i + 1] & 0x3f) << 6) | (bytes[i + 2] & 0x3f)));
|
||||
i += 3;
|
||||
}
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
return {
|
||||
toBytes: toBytes,
|
||||
fromBytes: fromBytes,
|
||||
}
|
||||
})();
|
||||
|
||||
var convertHex = (function() {
|
||||
function toBytes(text) {
|
||||
var result = [];
|
||||
for (var i = 0; i < text.length; i += 2) {
|
||||
result.push(parseInt(text.substr(i, 2), 16));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// http://ixti.net/development/javascript/2011/11/11/base64-encodedecode-of-utf8-in-browser-with-js.html
|
||||
var Hex = '0123456789abcdef';
|
||||
|
||||
function fromBytes(bytes) {
|
||||
var result = [];
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
var v = bytes[i];
|
||||
result.push(Hex[(v & 0xf0) >> 4] + Hex[v & 0x0f]);
|
||||
}
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
return {
|
||||
toBytes: toBytes,
|
||||
fromBytes: fromBytes,
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
// Number of rounds by keysize
|
||||
var numberOfRounds = {16: 10, 24: 12, 32: 14}
|
||||
|
||||
// Round constant words
|
||||
var rcon = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91];
|
||||
|
||||
// S-box and Inverse S-box (S is for Substitution)
|
||||
var S = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16];
|
||||
var Si =[0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d];
|
||||
|
||||
// Transformations for encryption
|
||||
var T1 = [0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a];
|
||||
var T2 = [0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616];
|
||||
var T3 = [0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16];
|
||||
var T4 = [0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c];
|
||||
|
||||
// Transformations for decryption
|
||||
var T5 = [0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742];
|
||||
var T6 = [0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857];
|
||||
var T7 = [0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8];
|
||||
var T8 = [0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0];
|
||||
|
||||
// Transformations for decryption key expansion
|
||||
var U1 = [0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3];
|
||||
var U2 = [0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697];
|
||||
var U3 = [0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46];
|
||||
var U4 = [0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d];
|
||||
|
||||
function convertToInt32(bytes) {
|
||||
var result = [];
|
||||
for (var i = 0; i < bytes.length; i += 4) {
|
||||
result.push(
|
||||
(bytes[i ] << 24) |
|
||||
(bytes[i + 1] << 16) |
|
||||
(bytes[i + 2] << 8) |
|
||||
bytes[i + 3]
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var AES = function(key) {
|
||||
if (!(this instanceof AES)) {
|
||||
throw Error('AES must be instanitated with `new`');
|
||||
}
|
||||
|
||||
Object.defineProperty(this, 'key', {
|
||||
value: coerceArray(key, true)
|
||||
});
|
||||
|
||||
this._prepare();
|
||||
}
|
||||
|
||||
|
||||
AES.prototype._prepare = function() {
|
||||
|
||||
var rounds = numberOfRounds[this.key.length];
|
||||
if (rounds == null) {
|
||||
throw new Error('invalid key size (must be 16, 24 or 32 bytes)');
|
||||
}
|
||||
|
||||
// encryption round keys
|
||||
this._Ke = [];
|
||||
|
||||
// decryption round keys
|
||||
this._Kd = [];
|
||||
|
||||
for (var i = 0; i <= rounds; i++) {
|
||||
this._Ke.push([0, 0, 0, 0]);
|
||||
this._Kd.push([0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
var roundKeyCount = (rounds + 1) * 4;
|
||||
var KC = this.key.length / 4;
|
||||
|
||||
// convert the key into ints
|
||||
var tk = convertToInt32(this.key);
|
||||
|
||||
// copy values into round key arrays
|
||||
var index;
|
||||
for (var i = 0; i < KC; i++) {
|
||||
index = i >> 2;
|
||||
this._Ke[index][i % 4] = tk[i];
|
||||
this._Kd[rounds - index][i % 4] = tk[i];
|
||||
}
|
||||
|
||||
// key expansion (fips-197 section 5.2)
|
||||
var rconpointer = 0;
|
||||
var t = KC, tt;
|
||||
while (t < roundKeyCount) {
|
||||
tt = tk[KC - 1];
|
||||
tk[0] ^= ((S[(tt >> 16) & 0xFF] << 24) ^
|
||||
(S[(tt >> 8) & 0xFF] << 16) ^
|
||||
(S[ tt & 0xFF] << 8) ^
|
||||
S[(tt >> 24) & 0xFF] ^
|
||||
(rcon[rconpointer] << 24));
|
||||
rconpointer += 1;
|
||||
|
||||
// key expansion (for non-256 bit)
|
||||
if (KC != 8) {
|
||||
for (var i = 1; i < KC; i++) {
|
||||
tk[i] ^= tk[i - 1];
|
||||
}
|
||||
|
||||
// key expansion for 256-bit keys is "slightly different" (fips-197)
|
||||
} else {
|
||||
for (var i = 1; i < (KC / 2); i++) {
|
||||
tk[i] ^= tk[i - 1];
|
||||
}
|
||||
tt = tk[(KC / 2) - 1];
|
||||
|
||||
tk[KC / 2] ^= (S[ tt & 0xFF] ^
|
||||
(S[(tt >> 8) & 0xFF] << 8) ^
|
||||
(S[(tt >> 16) & 0xFF] << 16) ^
|
||||
(S[(tt >> 24) & 0xFF] << 24));
|
||||
|
||||
for (var i = (KC / 2) + 1; i < KC; i++) {
|
||||
tk[i] ^= tk[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// copy values into round key arrays
|
||||
var i = 0, r, c;
|
||||
while (i < KC && t < roundKeyCount) {
|
||||
r = t >> 2;
|
||||
c = t % 4;
|
||||
this._Ke[r][c] = tk[i];
|
||||
this._Kd[rounds - r][c] = tk[i++];
|
||||
t++;
|
||||
}
|
||||
}
|
||||
|
||||
// inverse-cipher-ify the decryption round key (fips-197 section 5.3)
|
||||
for (var r = 1; r < rounds; r++) {
|
||||
for (var c = 0; c < 4; c++) {
|
||||
tt = this._Kd[r][c];
|
||||
this._Kd[r][c] = (U1[(tt >> 24) & 0xFF] ^
|
||||
U2[(tt >> 16) & 0xFF] ^
|
||||
U3[(tt >> 8) & 0xFF] ^
|
||||
U4[ tt & 0xFF]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AES.prototype.encrypt = function(plaintext) {
|
||||
if (plaintext.length != 16) {
|
||||
throw new Error('invalid plaintext size (must be 16 bytes)');
|
||||
}
|
||||
|
||||
var rounds = this._Ke.length - 1;
|
||||
var a = [0, 0, 0, 0];
|
||||
|
||||
// convert plaintext to (ints ^ key)
|
||||
var t = convertToInt32(plaintext);
|
||||
for (var i = 0; i < 4; i++) {
|
||||
t[i] ^= this._Ke[0][i];
|
||||
}
|
||||
|
||||
// apply round transforms
|
||||
for (var r = 1; r < rounds; r++) {
|
||||
for (var i = 0; i < 4; i++) {
|
||||
a[i] = (T1[(t[ i ] >> 24) & 0xff] ^
|
||||
T2[(t[(i + 1) % 4] >> 16) & 0xff] ^
|
||||
T3[(t[(i + 2) % 4] >> 8) & 0xff] ^
|
||||
T4[ t[(i + 3) % 4] & 0xff] ^
|
||||
this._Ke[r][i]);
|
||||
}
|
||||
t = a.slice();
|
||||
}
|
||||
|
||||
// the last round is special
|
||||
var result = createArray(16), tt;
|
||||
for (var i = 0; i < 4; i++) {
|
||||
tt = this._Ke[rounds][i];
|
||||
result[4 * i ] = (S[(t[ i ] >> 24) & 0xff] ^ (tt >> 24)) & 0xff;
|
||||
result[4 * i + 1] = (S[(t[(i + 1) % 4] >> 16) & 0xff] ^ (tt >> 16)) & 0xff;
|
||||
result[4 * i + 2] = (S[(t[(i + 2) % 4] >> 8) & 0xff] ^ (tt >> 8)) & 0xff;
|
||||
result[4 * i + 3] = (S[ t[(i + 3) % 4] & 0xff] ^ tt ) & 0xff;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
AES.prototype.decrypt = function(ciphertext) {
|
||||
if (ciphertext.length != 16) {
|
||||
throw new Error('invalid ciphertext size (must be 16 bytes)');
|
||||
}
|
||||
|
||||
var rounds = this._Kd.length - 1;
|
||||
var a = [0, 0, 0, 0];
|
||||
|
||||
// convert plaintext to (ints ^ key)
|
||||
var t = convertToInt32(ciphertext);
|
||||
for (var i = 0; i < 4; i++) {
|
||||
t[i] ^= this._Kd[0][i];
|
||||
}
|
||||
|
||||
// apply round transforms
|
||||
for (var r = 1; r < rounds; r++) {
|
||||
for (var i = 0; i < 4; i++) {
|
||||
a[i] = (T5[(t[ i ] >> 24) & 0xff] ^
|
||||
T6[(t[(i + 3) % 4] >> 16) & 0xff] ^
|
||||
T7[(t[(i + 2) % 4] >> 8) & 0xff] ^
|
||||
T8[ t[(i + 1) % 4] & 0xff] ^
|
||||
this._Kd[r][i]);
|
||||
}
|
||||
t = a.slice();
|
||||
}
|
||||
|
||||
// the last round is special
|
||||
var result = createArray(16), tt;
|
||||
for (var i = 0; i < 4; i++) {
|
||||
tt = this._Kd[rounds][i];
|
||||
result[4 * i ] = (Si[(t[ i ] >> 24) & 0xff] ^ (tt >> 24)) & 0xff;
|
||||
result[4 * i + 1] = (Si[(t[(i + 3) % 4] >> 16) & 0xff] ^ (tt >> 16)) & 0xff;
|
||||
result[4 * i + 2] = (Si[(t[(i + 2) % 4] >> 8) & 0xff] ^ (tt >> 8)) & 0xff;
|
||||
result[4 * i + 3] = (Si[ t[(i + 1) % 4] & 0xff] ^ tt ) & 0xff;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mode Of Operation - Electonic Codebook (ECB)
|
||||
*/
|
||||
var ModeOfOperationECB = function(key) {
|
||||
if (!(this instanceof ModeOfOperationECB)) {
|
||||
throw Error('AES must be instanitated with `new`');
|
||||
}
|
||||
|
||||
this.description = "Electronic Code Block";
|
||||
this.name = "ecb";
|
||||
|
||||
this._aes = new AES(key);
|
||||
}
|
||||
|
||||
ModeOfOperationECB.prototype.encrypt = function(plaintext) {
|
||||
plaintext = coerceArray(plaintext);
|
||||
|
||||
if ((plaintext.length % 16) !== 0) {
|
||||
throw new Error('invalid plaintext size (must be multiple of 16 bytes)');
|
||||
}
|
||||
|
||||
var ciphertext = createArray(plaintext.length);
|
||||
var block = createArray(16);
|
||||
|
||||
for (var i = 0; i < plaintext.length; i += 16) {
|
||||
copyArray(plaintext, block, 0, i, i + 16);
|
||||
block = this._aes.encrypt(block);
|
||||
copyArray(block, ciphertext, i);
|
||||
}
|
||||
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
ModeOfOperationECB.prototype.decrypt = function(ciphertext) {
|
||||
ciphertext = coerceArray(ciphertext);
|
||||
|
||||
if ((ciphertext.length % 16) !== 0) {
|
||||
throw new Error('invalid ciphertext size (must be multiple of 16 bytes)');
|
||||
}
|
||||
|
||||
var plaintext = createArray(ciphertext.length);
|
||||
var block = createArray(16);
|
||||
|
||||
for (var i = 0; i < ciphertext.length; i += 16) {
|
||||
copyArray(ciphertext, block, 0, i, i + 16);
|
||||
block = this._aes.decrypt(block);
|
||||
copyArray(block, plaintext, i);
|
||||
}
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mode Of Operation - Cipher Block Chaining (CBC)
|
||||
*/
|
||||
var ModeOfOperationCBC = function(key, iv) {
|
||||
if (!(this instanceof ModeOfOperationCBC)) {
|
||||
throw Error('AES must be instanitated with `new`');
|
||||
}
|
||||
|
||||
this.description = "Cipher Block Chaining";
|
||||
this.name = "cbc";
|
||||
|
||||
if (!iv) {
|
||||
iv = createArray(16);
|
||||
|
||||
} else if (iv.length != 16) {
|
||||
throw new Error('invalid initialation vector size (must be 16 bytes)');
|
||||
}
|
||||
|
||||
this._lastCipherblock = coerceArray(iv, true);
|
||||
|
||||
this._aes = new AES(key);
|
||||
}
|
||||
|
||||
ModeOfOperationCBC.prototype.encrypt = function(plaintext) {
|
||||
plaintext = coerceArray(plaintext);
|
||||
|
||||
if ((plaintext.length % 16) !== 0) {
|
||||
throw new Error('invalid plaintext size (must be multiple of 16 bytes)');
|
||||
}
|
||||
|
||||
var ciphertext = createArray(plaintext.length);
|
||||
var block = createArray(16);
|
||||
|
||||
for (var i = 0; i < plaintext.length; i += 16) {
|
||||
copyArray(plaintext, block, 0, i, i + 16);
|
||||
|
||||
for (var j = 0; j < 16; j++) {
|
||||
block[j] ^= this._lastCipherblock[j];
|
||||
}
|
||||
|
||||
this._lastCipherblock = this._aes.encrypt(block);
|
||||
copyArray(this._lastCipherblock, ciphertext, i);
|
||||
}
|
||||
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
ModeOfOperationCBC.prototype.decrypt = function(ciphertext) {
|
||||
ciphertext = coerceArray(ciphertext);
|
||||
|
||||
if ((ciphertext.length % 16) !== 0) {
|
||||
throw new Error('invalid ciphertext size (must be multiple of 16 bytes)');
|
||||
}
|
||||
|
||||
var plaintext = createArray(ciphertext.length);
|
||||
var block = createArray(16);
|
||||
|
||||
for (var i = 0; i < ciphertext.length; i += 16) {
|
||||
copyArray(ciphertext, block, 0, i, i + 16);
|
||||
block = this._aes.decrypt(block);
|
||||
|
||||
for (var j = 0; j < 16; j++) {
|
||||
plaintext[i + j] = block[j] ^ this._lastCipherblock[j];
|
||||
}
|
||||
|
||||
copyArray(ciphertext, this._lastCipherblock, 0, i, i + 16);
|
||||
}
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mode Of Operation - Cipher Feedback (CFB)
|
||||
*/
|
||||
var ModeOfOperationCFB = function(key, iv, segmentSize) {
|
||||
if (!(this instanceof ModeOfOperationCFB)) {
|
||||
throw Error('AES must be instanitated with `new`');
|
||||
}
|
||||
|
||||
this.description = "Cipher Feedback";
|
||||
this.name = "cfb";
|
||||
|
||||
if (!iv) {
|
||||
iv = createArray(16);
|
||||
|
||||
} else if (iv.length != 16) {
|
||||
throw new Error('invalid initialation vector size (must be 16 size)');
|
||||
}
|
||||
|
||||
if (!segmentSize) { segmentSize = 1; }
|
||||
|
||||
this.segmentSize = segmentSize;
|
||||
|
||||
this._shiftRegister = coerceArray(iv, true);
|
||||
|
||||
this._aes = new AES(key);
|
||||
}
|
||||
|
||||
ModeOfOperationCFB.prototype.encrypt = function(plaintext) {
|
||||
if ((plaintext.length % this.segmentSize) != 0) {
|
||||
throw new Error('invalid plaintext size (must be segmentSize bytes)');
|
||||
}
|
||||
|
||||
var encrypted = coerceArray(plaintext, true);
|
||||
|
||||
var xorSegment;
|
||||
for (var i = 0; i < encrypted.length; i += this.segmentSize) {
|
||||
xorSegment = this._aes.encrypt(this._shiftRegister);
|
||||
for (var j = 0; j < this.segmentSize; j++) {
|
||||
encrypted[i + j] ^= xorSegment[j];
|
||||
}
|
||||
|
||||
// Shift the register
|
||||
copyArray(this._shiftRegister, this._shiftRegister, 0, this.segmentSize);
|
||||
copyArray(encrypted, this._shiftRegister, 16 - this.segmentSize, i, i + this.segmentSize);
|
||||
}
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
ModeOfOperationCFB.prototype.decrypt = function(ciphertext) {
|
||||
if ((ciphertext.length % this.segmentSize) != 0) {
|
||||
throw new Error('invalid ciphertext size (must be segmentSize bytes)');
|
||||
}
|
||||
|
||||
var plaintext = coerceArray(ciphertext, true);
|
||||
|
||||
var xorSegment;
|
||||
for (var i = 0; i < plaintext.length; i += this.segmentSize) {
|
||||
xorSegment = this._aes.encrypt(this._shiftRegister);
|
||||
|
||||
for (var j = 0; j < this.segmentSize; j++) {
|
||||
plaintext[i + j] ^= xorSegment[j];
|
||||
}
|
||||
|
||||
// Shift the register
|
||||
copyArray(this._shiftRegister, this._shiftRegister, 0, this.segmentSize);
|
||||
copyArray(ciphertext, this._shiftRegister, 16 - this.segmentSize, i, i + this.segmentSize);
|
||||
}
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mode Of Operation - Output Feedback (OFB)
|
||||
*/
|
||||
var ModeOfOperationOFB = function(key, iv) {
|
||||
if (!(this instanceof ModeOfOperationOFB)) {
|
||||
throw Error('AES must be instanitated with `new`');
|
||||
}
|
||||
|
||||
this.description = "Output Feedback";
|
||||
this.name = "ofb";
|
||||
|
||||
if (!iv) {
|
||||
iv = createArray(16);
|
||||
|
||||
} else if (iv.length != 16) {
|
||||
throw new Error('invalid initialation vector size (must be 16 bytes)');
|
||||
}
|
||||
|
||||
this._lastPrecipher = coerceArray(iv, true);
|
||||
this._lastPrecipherIndex = 16;
|
||||
|
||||
this._aes = new AES(key);
|
||||
}
|
||||
|
||||
ModeOfOperationOFB.prototype.encrypt = function(plaintext) {
|
||||
var encrypted = coerceArray(plaintext, true);
|
||||
|
||||
for (var i = 0; i < encrypted.length; i++) {
|
||||
if (this._lastPrecipherIndex === 16) {
|
||||
this._lastPrecipher = this._aes.encrypt(this._lastPrecipher);
|
||||
this._lastPrecipherIndex = 0;
|
||||
}
|
||||
encrypted[i] ^= this._lastPrecipher[this._lastPrecipherIndex++];
|
||||
}
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
// Decryption is symetric
|
||||
ModeOfOperationOFB.prototype.decrypt = ModeOfOperationOFB.prototype.encrypt;
|
||||
|
||||
|
||||
/**
|
||||
* Counter object for CTR common mode of operation
|
||||
*/
|
||||
var Counter = function(initialValue) {
|
||||
if (!(this instanceof Counter)) {
|
||||
throw Error('Counter must be instanitated with `new`');
|
||||
}
|
||||
|
||||
// We allow 0, but anything false-ish uses the default 1
|
||||
if (initialValue !== 0 && !initialValue) { initialValue = 1; }
|
||||
|
||||
if (typeof(initialValue) === 'number') {
|
||||
this._counter = createArray(16);
|
||||
this.setValue(initialValue);
|
||||
|
||||
} else {
|
||||
this.setBytes(initialValue);
|
||||
}
|
||||
}
|
||||
|
||||
Counter.prototype.setValue = function(value) {
|
||||
if (typeof(value) !== 'number' || parseInt(value) != value) {
|
||||
throw new Error('invalid counter value (must be an integer)');
|
||||
}
|
||||
|
||||
// We cannot safely handle numbers beyond the safe range for integers
|
||||
if (value > Number.MAX_SAFE_INTEGER) {
|
||||
throw new Error('integer value out of safe range');
|
||||
}
|
||||
|
||||
for (var index = 15; index >= 0; --index) {
|
||||
this._counter[index] = value % 256;
|
||||
value = parseInt(value / 256);
|
||||
}
|
||||
}
|
||||
|
||||
Counter.prototype.setBytes = function(bytes) {
|
||||
bytes = coerceArray(bytes, true);
|
||||
|
||||
if (bytes.length != 16) {
|
||||
throw new Error('invalid counter bytes size (must be 16 bytes)');
|
||||
}
|
||||
|
||||
this._counter = bytes;
|
||||
};
|
||||
|
||||
Counter.prototype.increment = function() {
|
||||
for (var i = 15; i >= 0; i--) {
|
||||
if (this._counter[i] === 255) {
|
||||
this._counter[i] = 0;
|
||||
} else {
|
||||
this._counter[i]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mode Of Operation - Counter (CTR)
|
||||
*/
|
||||
var ModeOfOperationCTR = function(key, counter) {
|
||||
if (!(this instanceof ModeOfOperationCTR)) {
|
||||
throw Error('AES must be instanitated with `new`');
|
||||
}
|
||||
|
||||
this.description = "Counter";
|
||||
this.name = "ctr";
|
||||
|
||||
if (!(counter instanceof Counter)) {
|
||||
counter = new Counter(counter)
|
||||
}
|
||||
|
||||
this._counter = counter;
|
||||
|
||||
this._remainingCounter = null;
|
||||
this._remainingCounterIndex = 16;
|
||||
|
||||
this._aes = new AES(key);
|
||||
}
|
||||
|
||||
ModeOfOperationCTR.prototype.encrypt = function(plaintext) {
|
||||
var encrypted = coerceArray(plaintext, true);
|
||||
|
||||
for (var i = 0; i < encrypted.length; i++) {
|
||||
if (this._remainingCounterIndex === 16) {
|
||||
this._remainingCounter = this._aes.encrypt(this._counter._counter);
|
||||
this._remainingCounterIndex = 0;
|
||||
this._counter.increment();
|
||||
}
|
||||
encrypted[i] ^= this._remainingCounter[this._remainingCounterIndex++];
|
||||
}
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
// Decryption is symetric
|
||||
ModeOfOperationCTR.prototype.decrypt = ModeOfOperationCTR.prototype.encrypt;
|
||||
|
||||
|
||||
///////////////////////
|
||||
// Padding
|
||||
|
||||
// See:https://tools.ietf.org/html/rfc2315
|
||||
function pkcs7pad(data) {
|
||||
data = coerceArray(data, true);
|
||||
var padder = 16 - (data.length % 16);
|
||||
var result = createArray(data.length + padder);
|
||||
copyArray(data, result);
|
||||
for (var i = data.length; i < result.length; i++) {
|
||||
result[i] = padder;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function pkcs7strip(data) {
|
||||
data = coerceArray(data, true);
|
||||
if (data.length < 16) { throw new Error('PKCS#7 invalid length'); }
|
||||
|
||||
var padder = data[data.length - 1];
|
||||
if (padder > 16) { throw new Error('PKCS#7 padding byte out of range'); }
|
||||
|
||||
var length = data.length - padder;
|
||||
for (var i = 0; i < padder; i++) {
|
||||
if (data[length + i] !== padder) {
|
||||
throw new Error('PKCS#7 invalid padding byte');
|
||||
}
|
||||
}
|
||||
|
||||
var result = createArray(length);
|
||||
copyArray(data, result, 0, 0, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
// Exporting
|
||||
|
||||
|
||||
// The block cipher
|
||||
var aesjs = {
|
||||
AES: AES,
|
||||
Counter: Counter,
|
||||
|
||||
ModeOfOperation: {
|
||||
ecb: ModeOfOperationECB,
|
||||
cbc: ModeOfOperationCBC,
|
||||
cfb: ModeOfOperationCFB,
|
||||
ofb: ModeOfOperationOFB,
|
||||
ctr: ModeOfOperationCTR
|
||||
},
|
||||
|
||||
utils: {
|
||||
hex: convertHex,
|
||||
utf8: convertUtf8
|
||||
},
|
||||
|
||||
padding: {
|
||||
pkcs7: {
|
||||
pad: pkcs7pad,
|
||||
strip: pkcs7strip
|
||||
}
|
||||
},
|
||||
|
||||
_arrayTest: {
|
||||
coerceArray: coerceArray,
|
||||
createArray: createArray,
|
||||
copyArray: copyArray,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// node.js
|
||||
if (typeof exports !== 'undefined') {
|
||||
module.exports = aesjs
|
||||
|
||||
// RequireJS/AMD
|
||||
// http://www.requirejs.org/docs/api.html
|
||||
// https://github.com/amdjs/amdjs-api/wiki/AMD
|
||||
} else if (typeof(define) === 'function' && define.amd) {
|
||||
define([], function() { return aesjs; });
|
||||
|
||||
// Web Browsers
|
||||
} else {
|
||||
|
||||
// If there was an existing library at "aesjs" make sure it's still available
|
||||
if (root.aesjs) {
|
||||
aesjs._aesjs = root.aesjs;
|
||||
}
|
||||
|
||||
root.aesjs = aesjs;
|
||||
}
|
||||
|
||||
|
||||
})(this);
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
|
@ -1,29 +0,0 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2006, Ivan Sagalaev.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
97
templates/assets/highlight/highlight.min.css
vendored
97
templates/assets/highlight/highlight.min.css
vendored
|
@ -1,97 +0,0 @@
|
|||
/*!
|
||||
Theme: Default Description: Original highlight.js style Author: (c) Ivan
|
||||
Sagalaev <maniac@softwaremaniacs.org> Maintainer: @highlightjs/core-team
|
||||
Website: https://highlightjs.org/ License: see project LICENSE Touched: 2021
|
||||
*/
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em
|
||||
}
|
||||
|
||||
code.hljs {
|
||||
padding: 3px 5px
|
||||
}
|
||||
|
||||
.hljs {
|
||||
background: #f3f3f3;
|
||||
color: #444
|
||||
}
|
||||
|
||||
.hljs-comment {
|
||||
color: #697070
|
||||
}
|
||||
|
||||
.hljs-punctuation,
|
||||
.hljs-tag {
|
||||
color: #444a
|
||||
}
|
||||
|
||||
.hljs-tag .hljs-attr,
|
||||
.hljs-tag .hljs-name {
|
||||
color: #444
|
||||
}
|
||||
|
||||
.hljs-attribute,
|
||||
.hljs-doctag,
|
||||
.hljs-keyword,
|
||||
.hljs-meta .hljs-keyword,
|
||||
.hljs-name,
|
||||
.hljs-selector-tag {
|
||||
font-weight: 700
|
||||
}
|
||||
|
||||
.hljs-deletion,
|
||||
.hljs-number,
|
||||
.hljs-quote,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id,
|
||||
.hljs-string,
|
||||
.hljs-template-tag,
|
||||
.hljs-type {
|
||||
color: #800
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-title {
|
||||
color: #800;
|
||||
font-weight: 700
|
||||
}
|
||||
|
||||
.hljs-link,
|
||||
.hljs-operator,
|
||||
.hljs-regexp,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-symbol,
|
||||
.hljs-template-variable,
|
||||
.hljs-variable {
|
||||
color: #ab5656
|
||||
}
|
||||
|
||||
.hljs-literal {
|
||||
color: #695
|
||||
}
|
||||
|
||||
.hljs-addition,
|
||||
.hljs-built_in,
|
||||
.hljs-bullet,
|
||||
.hljs-code {
|
||||
color: #397300
|
||||
}
|
||||
|
||||
.hljs-meta {
|
||||
color: #1f7199
|
||||
}
|
||||
|
||||
.hljs-meta .hljs-string {
|
||||
color: #38a
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: 700
|
||||
}
|
2818
templates/assets/highlight/highlight.min.js
vendored
2818
templates/assets/highlight/highlight.min.js
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 29 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.5 KiB |
File diff suppressed because it is too large
Load diff
|
@ -1,28 +0,0 @@
|
|||
{% include "header.html" %}
|
||||
|
||||
<form id="auth-form" method="POST" action="/admin/" enctype="multipart/form-data">
|
||||
<label for="username"> Administrator username</label>
|
||||
<input id="username-field" placeholder="Username" type="username" autocomplete="off" name="username">
|
||||
<label for="password"> Administrator password.</label>
|
||||
<input id="password-field" placeholder="Password" type="password" autocomplete="off" name="password">
|
||||
<button>Sign in</button>
|
||||
{% if status == "incorrect" %}
|
||||
<p>
|
||||
Incorrect username or password.
|
||||
</p>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
{% include "footer.html" %} {% if !args.pure_html %}
|
||||
<style>
|
||||
#auth-form {
|
||||
background-color: var(--background-alt);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
</style>
|
||||
{% endif %}
|
|
@ -1,119 +0,0 @@
|
|||
{% include "header.html" %}
|
||||
|
||||
{% if encrypt_client %}
|
||||
|
||||
<form id="auth-form" method="POST" action="/{{path}}/{{id}}" enctype="multipart/form-data">
|
||||
{% if status == "success" %}
|
||||
<b>
|
||||
Success!
|
||||
</b> <br>
|
||||
{% endif %}
|
||||
<label for="password"> Please enter the
|
||||
password to access or modify this upload. <sup>
|
||||
<a href="/guide#encryption">﹖</a></sup></label>
|
||||
<input id="password-field" required placeholder="Password" type="password" autocomplete="off">
|
||||
<input id="password-hidden" name="password" type="hidden">
|
||||
<button>Okay</button>
|
||||
|
||||
{% if status == "incorrect" %}
|
||||
<b>
|
||||
Incorrect password.
|
||||
</b>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
<script>
|
||||
|
||||
const form = document.getElementById("auth-form");
|
||||
const passwordField = document.getElementById("password-field");
|
||||
const passwordHiddenField = document.getElementById("password-hidden");
|
||||
|
||||
form.onsubmit = function () {
|
||||
|
||||
if (passwordField.value.trim() == "") {
|
||||
passwordField.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
let key = decryptWithPassword(passwordField.value, "{{ encrypted_key }}");
|
||||
|
||||
if (key) {
|
||||
passwordHiddenField.value = key;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function decryptWithPassword(password, encryptedHex) {
|
||||
const passwordBytes = aesjs.utils.utf8.toBytes(password.padStart(32, "#"));
|
||||
const encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex);
|
||||
const aesCtr = new aesjs.ModeOfOperation.ctr(passwordBytes);
|
||||
const decryptedBytes = aesCtr.decrypt(encryptedBytes);
|
||||
const res = aesjs.utils.utf8.fromBytes(decryptedBytes);
|
||||
|
||||
if (res.endsWith("!0K")) {
|
||||
return res.substring(0, res.length - "!0K".length);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{% else %}
|
||||
|
||||
<form id="auth-form" method="POST" action="/{{path}}/{{id}}" enctype="multipart/form-data">
|
||||
{% if status == "success" %}
|
||||
<b>
|
||||
Success!
|
||||
</b> <br>
|
||||
{% endif %}
|
||||
<label for="password" style="margin-bottom: 0.5rem;"> Please enter the
|
||||
password to access or modify this upload. <sup>
|
||||
<a href="/guide#encryption">﹖</a></sup></label>
|
||||
<input id="password-field" placeholder="Password" name="password" type="password" autocomplete="off" />
|
||||
<button>Okay</button>
|
||||
|
||||
{% if status == "incorrect" %}
|
||||
<b>
|
||||
Incorrect password.
|
||||
</b>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
const form = document.getElementById("auth-form");
|
||||
const passwordField = document.getElementById("password-field");
|
||||
|
||||
form.onsubmit = function () {
|
||||
|
||||
if (passwordField.value.trim() == "") {
|
||||
passwordField.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
let key = decryptWithPassword(passwordField.value, "{{ encrypted_key }}");
|
||||
|
||||
if (key) {
|
||||
passwordHiddenField.value = key;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% include "footer.html" %} {% if !args.pure_html %}
|
||||
<style>
|
||||
#auth-form {
|
||||
background-color: var(--background-alt);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
</style>
|
||||
{% endif %}
|
|
@ -1,40 +0,0 @@
|
|||
{% include "header.html" %}
|
||||
<form action="/{{ path }}/{{ pasta.id_as_animals() }}" method="POST" enctype="multipart/form-data">
|
||||
<h4>
|
||||
Editing upload '{{ pasta.id_as_animals() }}'
|
||||
</h4>
|
||||
<label>Content</label>
|
||||
<br>
|
||||
<textarea style="width: 100%; min-height: 100px; font-family: monospace;" name="content" id="content" {% if status
|
||||
!="incorrect" %} autofocus {% endif %}>{{ pasta.content_escaped() }}</textarea>
|
||||
<br>
|
||||
<div>
|
||||
{% if pasta.readonly || pasta.encrypt_server %}
|
||||
<div style="float: left; height: 90px;">
|
||||
<label for="password">Re-enter Password <sup><a href="/guide#password">﹖</a></sup></label><br>
|
||||
<input {% if status=="incorrect" %} autofocus {% endif %} style="width: 130px; height: 28px;"
|
||||
type="password" id="password" name="password" autocomplete="off" />
|
||||
{% if status == "incorrect" %}
|
||||
<p>
|
||||
Incorrect password.
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div style="float: right; height: 90px; justify-content: end;">
|
||||
<label for="password_field"></label><br>
|
||||
<input style="width: 140px; float: right; background-color:
|
||||
#2975D2; color: white;" id="submit-button" type="submit" value="Save" />
|
||||
</td>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
</form>
|
||||
{% include "footer.html" %}
|
|
@ -4,7 +4,7 @@
|
|||
<b>Not Found</b>
|
||||
<br>
|
||||
<br>
|
||||
<a href="{{ args.public_path_as_str() }}/"> Go Home</a>
|
||||
<a href="/" > Go Home</a>
|
||||
<br>
|
||||
<br>
|
||||
{% include "footer.html" %}
|
||||
{% include "footer.html" %}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
|
||||
{% if !args.hide_footer %}
|
||||
|
||||
<p style="font-size: small; text-align: center;margin-top: 2rem;">
|
||||
{% if args.footer_text.as_ref().is_none() %}
|
||||
<a href="https://microbin.eu">MicroBin</a> by Dániel Szabó and the FOSS
|
||||
Community. Let's keep the Web <b>compact</b>, <b>accessible</b> and
|
||||
<b>humane</b>! {%- else %} {{ args.footer_text.as_ref().unwrap()|safe }} {%-
|
||||
endif %}
|
||||
<hr>
|
||||
<p style="font-size: smaller">
|
||||
MicroBin by Daniel Szabo. Fork me on <a href="https://github.com/szabodanika/microbin">GitHub</a>!
|
||||
Let's keep the Web <b>compact</b>, <b>accessible</b> and <b>humane</b>!
|
||||
</p>
|
||||
|
||||
{%- endif %}
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
|
@ -1,82 +0,0 @@
|
|||
{% include "header.html" %}
|
||||
<a id="options">
|
||||
<h2>Options</h2>
|
||||
</a>
|
||||
|
||||
<a id="expiration">
|
||||
<h3>Expiration</h3>
|
||||
</a>
|
||||
<p>
|
||||
Use the expiration dropdown to choose how long you want your upload to exist.
|
||||
When the selected time has expired, it will be removed from the server.
|
||||
</p>
|
||||
|
||||
{% if args.enable_burn_after %}
|
||||
<a id="burn-after">
|
||||
<h3>Burn After</h3>
|
||||
</a>
|
||||
<p>
|
||||
Use the burn after dropdown to set a limit on how many times your data can be
|
||||
accessed before it will be removed from the server.
|
||||
</p>
|
||||
{%- endif %}
|
||||
|
||||
{% if args.highlightsyntax %}
|
||||
<a id="syntax">
|
||||
<h3>Syntax Highlighting</h3>
|
||||
</a>
|
||||
<p>
|
||||
Use the syntax highlighting dropdown to enable syntax highlighting for your upload, making it easier to read.
|
||||
You may choose to have the syntax highlighting done by your browser, which
|
||||
will also recognise the language automatically. You can select server-side
|
||||
highlighting, where you need to select the language yourself, but the code
|
||||
will get highlighting without javascript.
|
||||
</p>
|
||||
{%- endif %}
|
||||
|
||||
<a id="password">
|
||||
<h3>Password</h3>
|
||||
</a>
|
||||
<p>
|
||||
Use the password field to set a password for your upload. This will encrypt
|
||||
your data while stored on our server with your password, and you will need to
|
||||
enter the password to access (in case of private and secret uploads) or to
|
||||
modify (in case of read-only uploads). Your password is encrypted, and in case
|
||||
of secret uploads, we never even see it.
|
||||
</p>
|
||||
|
||||
<a id="privacy">
|
||||
<h3>Privacy</h3>
|
||||
</a>
|
||||
<p>
|
||||
Use this dropdown to select the level of protection your upload needs. Use
|
||||
lower privacy levels if you or your organisation host MicroBin, and higher
|
||||
privacy levels if you are using a public MicroBin service.
|
||||
</p>
|
||||
<h4>Level 1: Public</h4>
|
||||
<p>This privacy level allows everyone to find, see, modify and remove your upload.</p>
|
||||
<h4>Level 2: Unlisted (recommended)</h4>
|
||||
<p>Unlisted uploads cannot be found unless someone knows its unique, random
|
||||
identifier. If someone knows this identifier, they can see, modify and remove
|
||||
the upload.</p>
|
||||
<h4>Level 3: Read-only</h4>
|
||||
<p>With this privacy setting, the upload cannot be found unless someone knows
|
||||
its unique, random identifier. If someone knows this identifier, they can see
|
||||
the contents but cannot modify or remove it without entering the password of
|
||||
the upload.
|
||||
</p>
|
||||
<h4>Level 4: Private</h4>
|
||||
<p>With this privacy setting, the upload cannot be found unless someone knows
|
||||
its unique, random identifier. If someone knows this identifier, they cannot
|
||||
see, modify or remove it without entering the password of the upload. Your
|
||||
upload and its attachments are encrypted, so they are stored safely.</p>
|
||||
<h4>Level 5: Secret</h4>
|
||||
<p>With this privacy setting, the upload cannot be found unless someone knows
|
||||
its unique, random identifier. If someone knows this identifier, they cannot
|
||||
see, modify or remove it without entering the password of the upload. Your
|
||||
browser sends us an already encrypted version, so the unencrypted data and
|
||||
password never even leave your device. This option requires you to enter your
|
||||
password many times when accessing your data, but is extremely safe.</p>
|
||||
|
||||
|
||||
{% include "footer.html" %}
|
|
@ -1,62 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
{% if args.title.as_ref().is_none() %}
|
||||
<title>MicroBin</title>
|
||||
{%- else %}
|
||||
<title>{{ args.title.as_ref().unwrap() }}</title>
|
||||
{%- endif %}
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/svg+xml" href="{{ args.public_path_as_str() }}/static/favicon.ico">
|
||||
{% if !args.pure_html %}
|
||||
<link rel="stylesheet" href="/static/water.css">
|
||||
{%- endif %}
|
||||
</head>
|
||||
<body style="
|
||||
max-width: 720px;
|
||||
margin: auto;
|
||||
padding-left:0.5rem;
|
||||
padding-right:0.5rem;
|
||||
line-height: 1.5;
|
||||
font-size: 1.1em;
|
||||
">
|
||||
|
||||
<script type="text/javascript" src="{{ args.public_path_as_str() }}/static/aes.js"></script>
|
||||
{% if !args.pure_html %} {% if args.custom_css.as_ref().is_none() ||
|
||||
args.custom_css.as_ref().unwrap() == "" %}
|
||||
<link rel="stylesheet" href="{{ args.public_path_as_str() }}/static/water.css">
|
||||
{%- else %}
|
||||
<link rel="stylesheet" href="{{ args.custom_css.as_ref().unwrap() }}">
|
||||
{%- endif %} {%- endif %}
|
||||
<br>
|
||||
|
||||
</head>
|
||||
{% if args.wide %}
|
||||
{% if !args.hide_header %}
|
||||
|
||||
<body style="max-width: 1080px; margin: auto; padding-left:0.5rem;
|
||||
padding-right:0.5rem; line-height: 1.5; font-size: 1.1em; padding-top: 2rem;">
|
||||
{%- else %}
|
||||
<b style="margin-right: 0.5rem">
|
||||
<i><span style="font-size:2.2rem; margin-right:1rem">μ</span></i> MicroBin
|
||||
</b>
|
||||
|
||||
<body style=" max-width: 800px; margin: auto; padding-left:0.5rem;
|
||||
padding-right:0.5rem; padding-top: 2rem; line-height: 1.5; font-size: 1.1em; ">
|
||||
{%- endif %}
|
||||
<br>
|
||||
{% if !args.hide_header %}
|
||||
<a href="/" style="margin-right: 0.5rem; margin-left: 0.5rem">New Pasta</a>
|
||||
|
||||
<div id="nav" style="margin-bottom: 1rem;">
|
||||
<b style="margin-right: 0.5rem">
|
||||
{% if !args.hide_logo %}
|
||||
<!-- <i><span style="font-size:2.2rem;
|
||||
margin-right:1rem">μ</span></i> -->
|
||||
<a href="/"><img width=100 style="margin-bottom: -6px; margin-right:
|
||||
0.5rem;" src="{{ args.public_path_as_str() }}/static/logo.png"></a>
|
||||
{%- endif %} {% if args.title.as_ref().is_none() %} {%- else %} {{
|
||||
args.title.as_ref().unwrap() }} {%- endif %}
|
||||
</b>
|
||||
<a href="/pastalist" style="margin-right: 0.5rem; margin-left: 0.5rem">Pasta List</a>
|
||||
|
||||
<a href="{{ args.public_path_as_str() }}/" style="margin-right: 0.5rem;
|
||||
margin-left: 0.5rem">New</a>
|
||||
<a href="https://github.com/szabodanika/microbin" style="margin-right: 0.5rem; margin-left: 0.5rem">GitHub</a>
|
||||
|
||||
<hr>
|
||||
|
||||
{% if !args.no_listing %}
|
||||
<a href="{{ args.public_path_as_str() }}/list" style="margin-right: 0.5rem; margin-left: 0.5rem">List</a>
|
||||
{%- endif %}
|
||||
|
||||
<a href="{{ args.public_path_as_str() }}/guide" style="margin-right: 0.5rem;
|
||||
margin-left: 0.5rem">Guide</a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- <hr> -->
|
||||
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
|
|
|
@ -1,448 +1,27 @@
|
|||
{% include "header.html" %}
|
||||
<form id="pasta-form" action="upload" method="POST" enctype="multipart/form-data">
|
||||
<form action="upload" method="POST" enctype="multipart/form-data">
|
||||
<br>
|
||||
<label for="expiration">Expiration</label><br>
|
||||
<select style="width: 100%;" name="expiration" id="expiration">
|
||||
<optgroup label="Expire">
|
||||
<option value="1min">1 minute</option>
|
||||
<option value="10min">10 minutes</option>
|
||||
<option value="1hour">1 hour</option>
|
||||
<option selected value="24hour">24 hours</option>
|
||||
<option value="1week">1 week</option>
|
||||
</optgroup>
|
||||
<option value="never">Never Expire</option>
|
||||
</select>
|
||||
<br>
|
||||
<div id="settings">
|
||||
<div>
|
||||
<label for="expiration">Expiration <sup> <a href="/guide#expiration">﹖</a></sup></label><br>
|
||||
<select style="width: 100%;" name="expiration" id="expiration">
|
||||
<optgroup label="Expire after">
|
||||
{% if args.default_expiry == "1min" %}
|
||||
<option selected value="1min">
|
||||
{%- else %}
|
||||
<option value="1min">
|
||||
{%- endif %} 1 minute
|
||||
</option>
|
||||
{% if args.default_expiry == "10min" %}
|
||||
<option selected value="10min">
|
||||
{%- else %}
|
||||
<option value="10min">
|
||||
{%- endif %} 10 minutes
|
||||
</option>
|
||||
{% if args.default_expiry == "1hour" %}
|
||||
<option selected value="1hour">
|
||||
{%- else %}
|
||||
<option value="1hour">
|
||||
{%- endif %} 1 hour
|
||||
</option>
|
||||
{% if args.default_expiry == "24hour" %}
|
||||
<option selected value="24hour">
|
||||
{%- else %}
|
||||
<option value="24hour">
|
||||
{%- endif %} 24 hours
|
||||
</option>
|
||||
{% if args.default_expiry == "3days" %}
|
||||
<option selected value="3days">
|
||||
{%- else %}
|
||||
<option value="3days">
|
||||
{%- endif %} 3 days
|
||||
</option>
|
||||
{% if args.default_expiry == "1week" %}
|
||||
<option selected value="1week">
|
||||
{%- else %}
|
||||
<option value="1week">
|
||||
{%- endif %} 1 week
|
||||
</option>
|
||||
</optgroup>
|
||||
{% if !args.eternal_pasta %} {% if args.default_expiry ==
|
||||
"never" %}
|
||||
<option selected value="never">
|
||||
{%- else %}
|
||||
<option value="never">{%- endif %} Never Expire
|
||||
</option>
|
||||
{%- endif %}
|
||||
</select>
|
||||
</div>
|
||||
{% if args.enable_burn_after %}
|
||||
<div>
|
||||
<label for="burn_after">Burn After <sup> <a href="/guide#burn-after">﹖</a></sup></label><br>
|
||||
<select style="width: 100%;" name="burn_after" id="burn_after">
|
||||
{% if args.default_burn_after == 0 %}
|
||||
<option selected value="0">
|
||||
{%- else %}
|
||||
<option value="0">
|
||||
{%- endif %} No Limit
|
||||
</option>
|
||||
<optgroup label="Burn after">
|
||||
{% if args.default_burn_after == 1 %}
|
||||
<option selected value="1">
|
||||
{%- else %}
|
||||
<option value="1">
|
||||
{%- endif %} First Read
|
||||
</option>
|
||||
{% if args.default_burn_after == 10 %}
|
||||
<option selected value="10">
|
||||
{%- else %}
|
||||
<option value="10">
|
||||
{%- endif %} 10th Read
|
||||
</option>
|
||||
{% if args.default_burn_after == 100 %}
|
||||
<option selected value="100">
|
||||
{%- else %}
|
||||
<option value="100">
|
||||
{%- endif %} 100th Read
|
||||
</option>
|
||||
{% if args.default_burn_after == 1000 %}
|
||||
<option selected value="1000">
|
||||
{%- else %}
|
||||
<option value="1000">
|
||||
{%- endif %} 1000th Read
|
||||
</option>
|
||||
{% if args.default_burn_after == 10000 %}
|
||||
<option selected value="10000">
|
||||
{%- else %}
|
||||
<option value="10000">
|
||||
{%- endif %} 10000th Read
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
{%- endif %}
|
||||
|
||||
{% if args.highlightsyntax %}
|
||||
<div>
|
||||
<label for="syntax_highlight">Syntax <sup> <a href="/guide#syntax">﹖</a></sup></label><br>
|
||||
<select style="width: 100%;" name="syntax_highlight" id="syntax_highlight">
|
||||
<option value="none">None</option>
|
||||
<optgroup label="Client-Rendered">
|
||||
<option value="auto">Automatic</option>
|
||||
</optgroup>
|
||||
<optgroup label="Server-Rendered">
|
||||
<option value="sh">Bash Shell</option>
|
||||
<option value="c">C</option>
|
||||
<option value="cpp">C++</option>
|
||||
<option value="cs">C#</option>
|
||||
<option value="pas">Delphi</option>
|
||||
<option value="erl">Erlang</option>
|
||||
<option value="go">Go</option>
|
||||
<option value="hs">Haskell</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="lua">Lua</option>
|
||||
<option value="lisp">Lisp</option>
|
||||
<option value="java">Java</option>
|
||||
<option value="js">JavaScript</option>
|
||||
<option value="kt">Kotlin</option>
|
||||
<option value="py">Python</option>
|
||||
<option value="php">PHP</option>
|
||||
<option value="r">R</option>
|
||||
<option value="rs">Rust</option>
|
||||
<option value="rb">Ruby</option>
|
||||
<option value="sc">Scala</option>
|
||||
<option value="swift">Swift</option>
|
||||
<!-- no toml support ;-( -->
|
||||
<option value="json">TOML</option>
|
||||
<option value="yaml">YAML</option>
|
||||
<option value="json">JSON</option>
|
||||
<option value="xml">XML</option>
|
||||
</optgroup>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
{%- else %}
|
||||
<input type="hidden" name="syntax_highlight" value="none">
|
||||
{%- endif %}
|
||||
|
||||
{% if args.encryption_client_side || args.encryption_server_side || args.enable_readonly || args.private %}
|
||||
<div>
|
||||
<label for="privacy">Privacy <sup> <a href="/guide#privacy">﹖</a></sup></label><br>
|
||||
<select style="width: 100%;" name="privacy" id="privacy">
|
||||
<optgroup label="Unencrypted (no password)">
|
||||
<option value="public">Public</option>
|
||||
{% if args.private %}
|
||||
<option value="unlisted">Unlisted</option>
|
||||
{%- endif %}
|
||||
</optgroup>
|
||||
{% if args.enable_readonly %}
|
||||
<optgroup label="Unencrypted (protected)">
|
||||
<option value="readonly">Read-only</option>
|
||||
</optgroup>
|
||||
{%- endif %}
|
||||
{% if args.encryption_client_side || args.encryption_server_side %}
|
||||
<optgroup label="Encrypted">
|
||||
{% if args.encryption_server_side %}
|
||||
<option value="private">Private</option>
|
||||
{%- endif %}
|
||||
{% if args.encryption_client_side%}
|
||||
<option value="secret">Secret</option>
|
||||
{%- endif %}
|
||||
</optgroup>
|
||||
{%- endif %}
|
||||
</select>
|
||||
</div>
|
||||
{%- endif %}
|
||||
|
||||
{% if args.encryption_client_side || args.encryption_server_side || args.enable_readonly %}
|
||||
<div>
|
||||
<label for="password_field">Password <sup><a href="/guide#password">﹖</a></sup></label><br>
|
||||
<input style="width: 130px; height: 28px;" type="password" id="password_field" autocomplete="off" />
|
||||
</div>
|
||||
{%- endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<label>Content</label>
|
||||
<textarea style="width: 100%; min-height: 100px; margin-bottom: 2em; font-family: monospace;" id="content-input"
|
||||
autofocus placeholder="Type something here."></textarea>
|
||||
<div>
|
||||
{% if !args.no_file_upload %}
|
||||
<div id="file-select">
|
||||
<label for="file" id="attach-file-button-label"><a role="button" id="attach-file-button">Select or drop file
|
||||
attachment</a></label>
|
||||
<br>
|
||||
<input type="file" id="file" name="file" />
|
||||
</div>
|
||||
{% endif %}
|
||||
<b>
|
||||
<input style="width: 140px; float: right; background-color:
|
||||
#2975D2; color: white;" id="submit-button" type="submit" value="Save" />
|
||||
{% if args.readonly %}
|
||||
{% if status == "incorrect" %}
|
||||
<input style="width: 160px; float: right; background-color: rgba(255, 0, 0, 0.137);" type="password"
|
||||
id="uploader_password" name="uploader_password" placeholder="Incorrect password!" />
|
||||
{% else %}
|
||||
<input style="width: 160px; float: right;" type="password" id="uploader_password" name="uploader_password"
|
||||
placeholder="Uploader Password" />
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</b>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="content" id="content">
|
||||
<input type="hidden" name="encrypt_client" id="encrypt_client">
|
||||
{% if args.encryption_server_side || args.enable_readonly %}
|
||||
<input name="encrypted_random_key" type="hidden" id="encrypted_random_key" autocomplete="off" />
|
||||
{%- endif %}
|
||||
<input type="hidden" name="random_key" id="random_key">
|
||||
<input type="hidden" name="plain_key" id="plain_key">
|
||||
<br>
|
||||
<textarea style="width: 100%; min-height: 100px" name="content" autofocus></textarea>
|
||||
<br>
|
||||
<label>File attachment</label>
|
||||
<br>
|
||||
<input style="width: 100%;" type="file" id="file" name="file">
|
||||
<br>
|
||||
<input style="width: 120px; background-color: limegreen" ; type="submit" value="Save"/>
|
||||
<br>
|
||||
</form>
|
||||
<br>
|
||||
<br>
|
||||
<script>
|
||||
const form = document.getElementById("pasta-form");
|
||||
const submitButton = document.getElementById("submit-button");
|
||||
const passwordField = document.getElementById("password_field");
|
||||
const privacyDropdown = document.getElementById("privacy");
|
||||
const contentInput = document.getElementById("content-input");
|
||||
const content = document.getElementById("content");
|
||||
const attachFileButton = document.getElementById('attach-file-button');
|
||||
const dropContainer = document.getElementById('pasta-form');
|
||||
const hiddenFileButton = document.getElementById('file');
|
||||
const hiddenRandomKeyField = document.getElementById("random_key");
|
||||
const hiddenEncryptedRandomKeyField = document.getElementById("encrypted_random_key");
|
||||
const hiddenPlainKeyField = document.getElementById("plain_key");
|
||||
const hiddenEncryptedClientSide = document.getElementById("encrypt_client");
|
||||
|
||||
const te = new TextEncoder();
|
||||
|
||||
form.onsubmit = async function (event) {
|
||||
event.preventDefault(); // prevent default form submission
|
||||
|
||||
// {% if args.encryption_client_side || args.encryption_server_side || args.enable_readonly %}
|
||||
if (passwordField.value.trim() != "") {
|
||||
// {% if !args.no_file_upload %}
|
||||
if (fileOversized()) return false;
|
||||
// {%- endif %}
|
||||
|
||||
if (privacyDropdown.value == "secret") {
|
||||
let randomKey = Array.from(Array(16), () => Math.floor(Math.random() * 36).toString(36)).join('');
|
||||
hiddenRandomKeyField.value = randomKey;
|
||||
hiddenEncryptedRandomKeyField.value = encryptWithPassword(passwordField.value, randomKey);
|
||||
if (contentInput.value.trim() != "") {
|
||||
content.value = encryptWithPassword(passwordField.value.trim(), contentInput.value);
|
||||
} else {
|
||||
content.value = contentInput.value;
|
||||
}
|
||||
hiddenPlainKeyField.name = "";
|
||||
// {% if !args.no_file_upload %}
|
||||
await encryptFile();
|
||||
// {%- endif %}
|
||||
} else {
|
||||
hiddenPlainKeyField.value = passwordField.value;
|
||||
hiddenEncryptedClientSide.name = "";
|
||||
hiddenEncryptedRandomKeyField.name = "";
|
||||
hiddenRandomKeyField.name = "";
|
||||
content.value = contentInput.value;
|
||||
}
|
||||
} else {
|
||||
if (privacyDropdown.value != "public" && privacyDropdown.value != "unlisted") {
|
||||
passwordField.focus();
|
||||
return false;
|
||||
}
|
||||
hiddenEncryptedClientSide.name = "";
|
||||
content.value = contentInput.value;
|
||||
}
|
||||
// {%- else %}
|
||||
hiddenEncryptedClientSide.name = "";
|
||||
content.value = contentInput.value;
|
||||
// {%- endif %}
|
||||
|
||||
if (contentInput.value.trim() == "" && (hiddenFileButton == undefined || hiddenFileButton.files.length == 0)) {
|
||||
contentInput.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
let showProgress = false;
|
||||
|
||||
submitButton.disabled = true;
|
||||
submitButton.textContent = 'Uploading...';
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/upload', true);
|
||||
|
||||
xhr.upload.onprogress = function (event) {
|
||||
if (showProgress) {
|
||||
const progressPercent = Math.round((event.loaded / event.total) * 100);
|
||||
submitButton.value = `${progressPercent}%`;
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200 || xhr.status === 302) {
|
||||
window.location.href = xhr.responseURL;
|
||||
} else {
|
||||
console.log('Request failed with status:', xhr.status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const formData = new FormData(form);
|
||||
xhr.send(formData);
|
||||
|
||||
showProgressTimeout = setTimeout(() => {
|
||||
showProgress = true;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
function encryptWithPassword(password, plaintext) {
|
||||
const passwordBytes = aesjs.utils.utf8.toBytes(password.padStart(32, "#"));
|
||||
const plaintextBytes = aesjs.utils.utf8.toBytes(plaintext + "!0K");
|
||||
const aesCtr = new aesjs.ModeOfOperation.ctr(passwordBytes);
|
||||
const encryptedBytes = aesCtr.encrypt(plaintextBytes);
|
||||
return aesjs.utils.hex.fromBytes(encryptedBytes);
|
||||
}
|
||||
|
||||
// {% if !args.no_file_upload %}
|
||||
function encryptFileWithPassword(password, bytes) {
|
||||
const passwordBytes = aesjs.utils.utf8.toBytes(password.padStart(32, "#"));
|
||||
const aesCtr = new aesjs.ModeOfOperation.ctr(passwordBytes);
|
||||
const encryptedBytes = aesCtr.encrypt(bytes);
|
||||
return aesjs.utils.hex.fromBytes(encryptedBytes);
|
||||
}
|
||||
|
||||
function fileOversized() {
|
||||
if (hiddenFileButton.files.length > 0) {
|
||||
const fileSize = hiddenFileButton.files.item(0).size;
|
||||
const fileSizeMb = fileSize / 1024 ** 2;
|
||||
|
||||
if (privacyDropdown.value == "secret") {
|
||||
if (fileSizeMb >
|
||||
parseInt("{{ args.max_file_size_encrypted_mb }}")) {
|
||||
attachFileButton.textContent = "Please select a file smaller than {{ args.max_file_size_encrypted_mb }} MB";
|
||||
this.value = "";
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (fileSizeMb >
|
||||
parseInt("{{ args.max_file_size_unencrypted_mb }}")) {
|
||||
attachFileButton.textContent = "Please select a file smaller than {{ args.max_file_size_unencrypted_mb }} MB";
|
||||
this.value = "";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function encryptFile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (hiddenFileButton.files.length > 0) {
|
||||
const file = hiddenFileButton.files[0];
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
const encryptedContents = encryptFileWithPassword(passwordField.value.trim(), new Uint8Array(event.target.result));
|
||||
|
||||
// Replace selected file with its encrypted version
|
||||
const encryptedFile = new File([encryptedContents], file.name, { type: file.type });
|
||||
|
||||
let container = new DataTransfer();
|
||||
container.items.add(encryptedFile);
|
||||
hiddenFileButton.files = container.files;
|
||||
resolve(encryptedFile);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hiddenFileButton.addEventListener('change', function () {
|
||||
attachFileButton.textContent = "Attached: " + this.files[0].name;
|
||||
fileOversized();
|
||||
});
|
||||
|
||||
dropContainer.ondragover = dropContainer.ondragenter = function (evt) {
|
||||
evt.preventDefault();
|
||||
if (hiddenFileButton.files.length == 0) {
|
||||
attachFileButton.textContent = "Drop your file here";
|
||||
} else {
|
||||
attachFileButton.textContent = "Drop your file here to replace " + hiddenFileButton.files[0].name;
|
||||
}
|
||||
};
|
||||
|
||||
dropContainer.ondrop = function (evt) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(evt.dataTransfer.files[0]);
|
||||
hiddenFileButton.files = dataTransfer.files;
|
||||
attachFileButton.textContent = "Attached: " + hiddenFileButton.files[0].name;
|
||||
evt.preventDefault();
|
||||
};
|
||||
// {%- endif %}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
input::file-selector-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#settings {
|
||||
display: grid;
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: repeat(auto-fit, 152px);
|
||||
grid-template-rows: repeat(1, 90px);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
select {
|
||||
height: 3rem;
|
||||
|
||||
}
|
||||
|
||||
/* {% if !args.pure_html %} */
|
||||
#attach-file-button-label {
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
border: #2975D2 2px dotted;
|
||||
border-radius: 6px;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
font-size: smaller;
|
||||
min-width: 235px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* {% endif %} */
|
||||
|
||||
#file {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#file-select {
|
||||
float: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% include "footer.html" %}
|
||||
|
|
|
@ -1,197 +0,0 @@
|
|||
{% include "header.html" %}
|
||||
|
||||
|
||||
{% if pastas.is_empty() %}
|
||||
<br>
|
||||
<p>
|
||||
No uploads yet. 😔 Create one <a href="{{ args.public_path_as_str() }}/">here</a>.
|
||||
</p>
|
||||
<br>
|
||||
{%- else %}
|
||||
<h3>Uploads</h3>
|
||||
<div style="width: 100%; overflow-x: auto;">
|
||||
{% if args.pure_html %}
|
||||
<table border="1" style="width: 100%; min-width: 720px; white-space: nowrap;">
|
||||
{% else %}
|
||||
<table style="width: 100%; min-width: 720px;">
|
||||
{% endif %}
|
||||
<thead>
|
||||
<th style="width: 25%">
|
||||
Key
|
||||
</th>
|
||||
<th style="width: 10%">
|
||||
</th>
|
||||
<th style="width: 15%">
|
||||
Created
|
||||
</th>
|
||||
<th style="width: 15%">
|
||||
Expiration
|
||||
</th>
|
||||
<th style="width: 15%">
|
||||
Contents
|
||||
</th>
|
||||
|
||||
<th style="width: 10%">
|
||||
</th>
|
||||
<th style="width: 10%">
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pasta in pastas %}
|
||||
{% if pasta.pasta_type == "text" && !pasta.private %}
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
href="{{ args.public_path_as_str()}}/upload/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if args.public_path_as_str() != "" %}
|
||||
{% if args.short_path_as_str() == "" %}
|
||||
<a style="margin-right:1rem; cursor: pointer;" class="copy-button" null
|
||||
data-url="{{ args.public_path_as_str()}}/upload/{{pasta.id_as_animals()}}">Copy</a>
|
||||
{% else %}
|
||||
<a style="margin-right:1rem; cursor: pointer;" class="copy-button" data-url="{{ args.short_path_as_str()
|
||||
}}/p/{{pasta.id_as_animals()}}">Copy</a>
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{pasta.created_as_string()}}
|
||||
</td>
|
||||
<td>
|
||||
{{pasta.expiration_as_string()}}
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.content != "" %}
|
||||
<a style="margin-right:1rem"
|
||||
href="{{ args.public_path_as_str()}}/raw/{{pasta.id_as_animals()}}">Text</a>
|
||||
{%- endif %}
|
||||
{% if pasta.file.is_some() %}
|
||||
<a style="margin-right:1rem"
|
||||
href="{{ args.public_path_as_str() }}/file/{{pasta.id_as_animals()}}">
|
||||
{% if pasta.file.as_ref().unwrap().is_image() %}
|
||||
Image
|
||||
{%- else if pasta.file.as_ref().unwrap().is_video() %}
|
||||
Video
|
||||
{%- else %}
|
||||
File
|
||||
{%- endif %}
|
||||
</a>
|
||||
{%- endif %}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{% if pasta.editable %}
|
||||
<a style="margin-right:1rem"
|
||||
href="{{ args.public_path_as_str() }}/edit/{{pasta.id_as_animals()}}">Edit</a>
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ args.public_path_as_str() }}/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<h3>URL Redirects</h3>
|
||||
{% if args.pure_html %}
|
||||
<table border="1" style="width: 100%; min-width: 720px; ">
|
||||
{% else %}
|
||||
<table style="width: 100%; min-width: 720px;">
|
||||
{% endif %}
|
||||
<thead>
|
||||
<th style=" width: 25%">
|
||||
Key
|
||||
</th>
|
||||
<th style="width: 10%">
|
||||
</th>
|
||||
<th style="width: 15%">
|
||||
Created
|
||||
</th>
|
||||
<th style="width: 15%">
|
||||
Expiration
|
||||
</th>
|
||||
<th style="width: 15%">
|
||||
</th>
|
||||
<th style="width: 10%">
|
||||
</th>
|
||||
<th style="width: 10%">
|
||||
</th>
|
||||
</thead>
|
||||
{% for pasta in pastas %}
|
||||
{% if pasta.pasta_type == "url" && !pasta.private %}
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
href="{{ args.public_path_as_str() }}/upload/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if args.short_path_as_str() == "" %}
|
||||
<a style="margin-right:1rem; cursor: pointer;" class="copy-button"
|
||||
data-url="{{ args.public_path_as_str() }}/url/{{pasta.id_as_animals()}}">Copy</a>
|
||||
{% else %}
|
||||
<a style="margin-right:1rem; cursor: pointer;" class="copy-button" data-url="{{ args.short_path_as_str()
|
||||
}}/u/{{pasta.id_as_animals()}}">Copy</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{pasta.created_as_string()}}
|
||||
</td>
|
||||
<td>
|
||||
{{pasta.expiration_as_string()}}
|
||||
</td>
|
||||
<td>
|
||||
<a style="margin-right:1rem"
|
||||
href="{{ args.public_path_as_str() }}/url/{{pasta.id_as_animals()}}">Redirect</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if pasta.editable %}
|
||||
<a style="margin-right:1rem"
|
||||
href="{{ args.public_path_as_str() }}/edit/{{pasta.id_as_animals()}}">Edit</a>
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ args.public_path_as_str() }}/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
{%- endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const copyURLBtns = document.getElementsByClassName("copy-button");
|
||||
|
||||
for (var i = 0; i < copyURLBtns.length; i++) {
|
||||
copyURLBtns.item(i).addEventListener("click", event => {
|
||||
event.srcElement
|
||||
navigator.clipboard.writeText(event.srcElement.getAttribute("data-url"))
|
||||
event.srcElement.innerHTML = "Copied"
|
||||
setTimeout(() => {
|
||||
event.srcElement.innerHTML = "Copy"
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.copy-url-button {
|
||||
font-size: small;
|
||||
padding: 4px;
|
||||
padding-left: 0.8rem;
|
||||
padding-right: 0.8rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% include "footer.html" %}
|
9
templates/pasta.html
Normal file
9
templates/pasta.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
{% include "header.html" %}
|
||||
<a style="margin-right: 0.5rem" href="/raw/{{pasta.id_as_animals()}}">Raw Text Content</a>
|
||||
{% if pasta.file != "no-file" %}
|
||||
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file}}">Attached file '{{pasta.file}}'</a>
|
||||
{%- endif %}
|
||||
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||
<pre><code>{{pasta}}</code></pre>
|
||||
|
||||
{% include "footer.html" %}
|
101
templates/pastalist.html
Normal file
101
templates/pastalist.html
Normal file
|
@ -0,0 +1,101 @@
|
|||
{% include "header.html" %}
|
||||
|
||||
|
||||
{% if pastas.is_empty() %}
|
||||
<br>
|
||||
<p>
|
||||
No pastas yet. 😔 Create one <a href="/">here</a>.
|
||||
</p>
|
||||
<br>
|
||||
{%- else %}
|
||||
<br>
|
||||
<table style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">Pastas</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Key
|
||||
</th>
|
||||
<th>
|
||||
Created
|
||||
</th>
|
||||
<th>
|
||||
Expiration
|
||||
</th>
|
||||
<th>
|
||||
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pasta in pastas %}
|
||||
{% if pasta.pasta_type == "text" %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/pasta/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{pasta.created_as_string()}}
|
||||
</td>
|
||||
<td>
|
||||
{{pasta.expiration_as_string()}}
|
||||
</td>
|
||||
<td>
|
||||
<a style="margin-right:1rem" href="/raw/{{pasta.id_as_animals()}}">Raw</a>
|
||||
{% if pasta.file != "no-file" %}
|
||||
<a style="margin-right:1rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file}}">File</a>
|
||||
{%- endif %}
|
||||
<a href="/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">URL Redirects</th>
|
||||
</tr>
|
||||
<tr >
|
||||
<th>
|
||||
Key
|
||||
</th>
|
||||
<th>
|
||||
Created
|
||||
</th>
|
||||
<th>
|
||||
Expiration
|
||||
</th>
|
||||
<th>
|
||||
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for pasta in pastas %}
|
||||
{% if pasta.pasta_type == "url" %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/url/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{pasta.created_as_string()}}
|
||||
</td>
|
||||
<td>
|
||||
{{pasta.expiration_as_string()}}
|
||||
</td>
|
||||
<td>
|
||||
<a style="margin-right:1rem" href="/raw/{{pasta.id_as_animals()}}">Raw</a>
|
||||
<a href="/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
{%- endif %}
|
||||
{% include "footer.html" %}
|
|
@ -1,29 +0,0 @@
|
|||
{% include "header.html" %}
|
||||
|
||||
<div style="float: left">
|
||||
<a href="{{ args.public_path_as_str() }}/upload/{{pasta.id_as_animals()}}">Back to Upload</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="text-align: center; padding: 3rem;">
|
||||
{% if pasta.pasta_type == "url" %}
|
||||
<a href="{{ args.public_path_as_str() }}/url/{{pasta.id_as_animals()}}">
|
||||
{{qr}}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ args.public_path_as_str() }}/upload/{{pasta.id_as_animals()}}">
|
||||
{{qr}}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.copy-text-button,
|
||||
.copy-url-button {
|
||||
font-size: small;
|
||||
padding: 4px;
|
||||
width: 6rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% include "footer.html" %}
|
|
@ -1,394 +0,0 @@
|
|||
{% include "header.html" %}
|
||||
<div style="float: left">
|
||||
{% if pasta.content != "" %}
|
||||
<button id="copy-text-button" class="small-button" style="margin-right:
|
||||
0.5rem">
|
||||
Copy Text
|
||||
</button>
|
||||
|
||||
{% if args.public_path_as_str() != "" && pasta.pasta_type == "url" %}
|
||||
<button id="copy-redirect-button" class="small-button" style="margin-right:
|
||||
0.5rem">
|
||||
Copy Redirect
|
||||
</button>
|
||||
{%- endif %}
|
||||
<a style="margin-right: 1rem" href="{{ args.public_path_as_str() }}/raw/{{pasta.id_as_animals()}}">Raw Text
|
||||
Content</a>
|
||||
{%- endif %} {% if args.qr && args.public_path_as_str() != "" %}
|
||||
<a style="margin-right: 1rem" href="{{ args.public_path_as_str() }}/qr/{{pasta.id_as_animals()}}">QR</a>
|
||||
{%- endif %} {% if pasta.editable && !pasta.encrypt_client %}
|
||||
<a style="margin-right: 1rem" href="{{ args.public_path_as_str() }}/edit/{{pasta.id_as_animals()}}">Edit</a>
|
||||
{%- endif %}
|
||||
<a style="margin-right: 1rem" href="{{ args.public_path_as_str() }}/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||
</div>
|
||||
<div style="float: right">
|
||||
<a style="margin-right: 0.5rem"
|
||||
href="{{ args.public_path_as_str() }}/upload/{{pasta.id_as_animals()}}"><i>{{pasta.id_as_animals()}}</i></a>
|
||||
{% if args.public_path_as_str() != "" %}
|
||||
<button id="copy-url-button" class="small-button" style="margin-right: 0">
|
||||
Copy URL
|
||||
</button>
|
||||
{%- endif %}
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
{% if pasta.encrypt_client %}
|
||||
<span style="margin-left: auto; margin-right: auto; display: flex;
|
||||
justify-content: center; align-items: center;">
|
||||
<div id="decryption">
|
||||
{% if pasta.encrypt_client %}
|
||||
<label for="password-field" style="margin-bottom: 0.5em;">
|
||||
Please enter your key to decrypt this upload. <sup> <a href="/guide#encryption">﹖</a></sup>
|
||||
</label>
|
||||
<input class="small-button" placeholder="Key" style="margin-right: 0.5rem" type="password" id="password-field"
|
||||
autocomplete="off" />
|
||||
{% if pasta.content != "" %}
|
||||
<button class="small-button" id="decrypt-button" style="margin-right:
|
||||
0.5rem">
|
||||
<b>
|
||||
Decrypt text
|
||||
</b>
|
||||
</button>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{% if pasta.file.is_some() && !pasta.file_embeddable() %}
|
||||
<button class="small-button" id="download-button" style="margin-right:
|
||||
0.5rem">
|
||||
<b>
|
||||
Download {{pasta.file.as_ref().unwrap().name()}}
|
||||
[{{pasta.file.as_ref().unwrap().size}}]
|
||||
</b>
|
||||
</button>
|
||||
{%- endif %}
|
||||
</div>
|
||||
</span>
|
||||
{%- endif %}
|
||||
|
||||
<br>
|
||||
|
||||
{% if pasta.content != "" %}
|
||||
<div class="code-container">
|
||||
<div style="clear: both;">
|
||||
{% if pasta.extension == "auto" || pasta.encrypt_client %}
|
||||
<pre><code id="code">{{pasta.content_escaped()}}</code></pre>
|
||||
{% else if args.highlightsyntax %}
|
||||
<pre><code id="code">{{pasta.content_syntax_highlighted()}}</code></pre>
|
||||
{% else %}
|
||||
<pre><code id="code">{{pasta.content_not_highlighted()}}</code></pre>
|
||||
{%- endif %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endif %}
|
||||
|
||||
{% if pasta.file.is_some() && !pasta.file_embeddable() && !pasta.encrypt_client %}
|
||||
<span style="margin-left: auto; margin-right: auto; display: flex;
|
||||
justify-content: center; align-items: center;">
|
||||
<p style="font-size: small;">{{pasta.file.as_ref().unwrap().name()}}
|
||||
[{{pasta.file.as_ref().unwrap().size}}]</p>
|
||||
<a href="{{ args.public_path_as_str()}}/file/{{pasta.id_as_animals()}}" id="download-link">
|
||||
<button class="download-button" autofocus>
|
||||
Download
|
||||
</button>
|
||||
</a>
|
||||
</span>
|
||||
{%- endif %}
|
||||
|
||||
|
||||
{% if pasta.file.is_some() && pasta.file.as_ref().unwrap().is_image() &&
|
||||
pasta.file_embeddable() && !pasta.encrypt_client %}
|
||||
<img id="embed" src="{{ args.public_path_as_str()}}/file/{{pasta.id_as_animals()}}" height="300" />
|
||||
<span style="margin-left: auto; margin-right: auto; display: flex;
|
||||
justify-content: center; align-items: center;">
|
||||
<p style="font-size: small;">{{pasta.file.as_ref().unwrap().name()}}
|
||||
[{{pasta.file.as_ref().unwrap().size}}]</p>
|
||||
<a href="{{ args.public_path_as_str() }}/file/{{pasta.id_as_animals()}}" id="download-link" download>
|
||||
<button class="download-button" autofocus>
|
||||
Download
|
||||
</button>
|
||||
</a>
|
||||
</span>
|
||||
{%- endif %}
|
||||
|
||||
|
||||
{% if pasta.file.is_some() && pasta.file.as_ref().unwrap().is_video() &&
|
||||
pasta.file_embeddable() && !pasta.encrypt_client %}
|
||||
<video id="embed" controls src="{{ args.public_path_as_str()}}/file/{{pasta.id_as_animals()}}" height="300"></video>
|
||||
<span style="margin-left: auto; margin-right: auto; display: flex;
|
||||
justify-content: center; align-items: center;">
|
||||
<p style="font-size: small;">{{pasta.file.as_ref().unwrap().name()}}
|
||||
[{{pasta.file.as_ref().unwrap().size}}]</p>
|
||||
<a href="{{ args.public_path_as_str() }}/file/{{pasta.id_as_animals()}}" download id="download-link">
|
||||
<button class="download-button">
|
||||
Download
|
||||
</button>
|
||||
</a>
|
||||
</span>
|
||||
{%- endif %}
|
||||
|
||||
<div>
|
||||
{% if args.show_read_stats %} {% if pasta.read_count == 1 %}
|
||||
<p style="font-size: small">Read {{pasta.read_count}} time, last
|
||||
{{pasta.last_read_time_ago_as_string()}}</p>
|
||||
{%- else %}
|
||||
<p style="font-size: small">Read {{pasta.read_count}} times, last
|
||||
{{pasta.last_read_time_ago_as_string()}}</p>
|
||||
{%- endif %} {%- endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<script type="text/javascript" src="{{ args.public_path_as_str() }}/static/highlight/highlight.min.js"></script>
|
||||
<link rel="stylesheet" href="{{ args.public_path_as_str()}}/static/highlight/highlight.min.css">
|
||||
|
||||
<script>
|
||||
const copyURLBtn = document.getElementById("copy-url-button")
|
||||
const copyTextBtn = document.getElementById("copy-text-button")
|
||||
const copyRedirectBtn = document.getElementById("copy-redirect-button")
|
||||
var content = `{{ pasta.content_escaped() }}`
|
||||
const contentElement = document.getElementById("code");
|
||||
const url = (`{{ args.short_path_as_str()}}` === "") ? `{{ args.public_path_as_str() }}/upload/{{pasta.id_as_animals()}}` : `{{ args.short_path_as_str()}}/p/{{pasta.id_as_animals()}}`
|
||||
const redirect_url = (`{{ args.short_path_as_str()}}` === "") ? `{{ args.public_path_as_str() }}/url/{{pasta.id_as_animals()}}` : `{{ args.short_path_as_str()}}/u/{{pasta.id_as_animals()}}`
|
||||
|
||||
const te = new TextEncoder();
|
||||
|
||||
// {% if pasta.extension == "auto" && !pasta.encrypt_client %}
|
||||
onload = (event) => {
|
||||
contentElement.innerHTML = content;
|
||||
hljs.highlightAll();
|
||||
contentElement.innerHTML =
|
||||
wrapStringInCodeLines(contentElement.innerHTML);
|
||||
};
|
||||
// {% endif %}
|
||||
|
||||
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function wrapStringInCodeLines(str) {
|
||||
const lines = str.split(/\r?\n/); // split the string into an array of lines
|
||||
const wrappedLines = lines.map((line) => `<code-line>${line}</code-line>`); // wrap each line in a "code-line" tag
|
||||
return wrappedLines.join("\n"); // join the wrapped lines back into a single string with line breaks
|
||||
}
|
||||
|
||||
const decodeEntity = (inputStr) => {
|
||||
var textarea = document.createElement("textarea");
|
||||
textarea.innerHTML = inputStr;
|
||||
return textarea.value;
|
||||
}
|
||||
|
||||
if (copyURLBtn) {
|
||||
copyURLBtn.addEventListener("click", () => {
|
||||
navigator.clipboard.writeText(url)
|
||||
copyURLBtn.innerHTML = "Copied"
|
||||
setTimeout(() => {
|
||||
copyURLBtn.innerHTML = "Copy URL"
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
// it will be undefined when the element does not exist on non-url pastas
|
||||
if (copyRedirectBtn) {
|
||||
copyRedirectBtn.addEventListener("click", () => {
|
||||
navigator.clipboard.writeText(redirect_url)
|
||||
copyRedirectBtn.innerHTML = "Copied"
|
||||
setTimeout(() => {
|
||||
copyRedirectBtn.innerHTML = "Copy Redirect"
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
if (copyTextBtn) {
|
||||
copyTextBtn.addEventListener("click", () => {
|
||||
const decodeContent = decodeEntity(content)
|
||||
navigator.clipboard.writeText(decodeContent)
|
||||
copyTextBtn.innerHTML = "Copied"
|
||||
setTimeout(() => {
|
||||
copyTextBtn.innerHTML = "Copy Text"
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
// {% if pasta.encrypt_client %}
|
||||
|
||||
const decryptDiv = document.getElementById('decryption');
|
||||
const decryptButton = document.getElementById('decrypt-button');
|
||||
const passwordField = document.getElementById("password-field");
|
||||
const downloadButton = document.getElementById('download-button');
|
||||
|
||||
// {% if pasta.file.is_some() %}
|
||||
// Set up event listener for download link click
|
||||
downloadButton.addEventListener('click', async (event) => {
|
||||
event.preventDefault(); // prevent default click behavior
|
||||
|
||||
if (passwordField.value.trim() == "") {
|
||||
passwordField.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fetch encrypted file from server
|
||||
const formData = new FormData();
|
||||
|
||||
// {% if pasta.encrypted_key.is_some() %}
|
||||
let key = decryptWithPassword(passwordField.value.trim(), "{{ pasta.encrypted_key.as_ref().unwrap() }}");
|
||||
// {%- endif %}
|
||||
formData.append('password', key);
|
||||
|
||||
const response = await fetch('/secure_file/{{ pasta.id_as_animals() }}', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
// {% if pasta.file.is_some() %}
|
||||
const encryptedFile = await response.text();
|
||||
|
||||
// Decrypt file contents
|
||||
const decryptedContents = decryptFileWithPassword(passwordField.value.trim(), encryptedFile);
|
||||
if (!decryptedContents) {
|
||||
throw new Error('Failed to decrypt file');
|
||||
}
|
||||
|
||||
// Create blob from decrypted file contents
|
||||
const decryptedBlob = new Blob([decryptedContents], { type: 'application/octet-stream' });
|
||||
|
||||
// Create data URI for decrypted file
|
||||
// const dataUri = `data:application/octet-stream;${encodeURIComponent(decryptedContents)}`;
|
||||
|
||||
// Create temporary anchor element
|
||||
const tempAnchorEl = document.createElement('a');
|
||||
// tempAnchorEl.href = dataUri;
|
||||
tempAnchorEl.href = URL.createObjectURL(decryptedBlob);
|
||||
tempAnchorEl.download = '{{pasta.file.as_ref().unwrap().name()}}';
|
||||
|
||||
// Programmatically click anchor element to trigger download
|
||||
tempAnchorEl.click();
|
||||
|
||||
// {%- endif %}
|
||||
});
|
||||
// {% endif %}
|
||||
|
||||
decryptButton.addEventListener("click", () => {
|
||||
password = passwordField.value;
|
||||
|
||||
content = contentDecrypted = escapeHtml(decryptWithPassword(password, content));
|
||||
if (contentDecrypted) {
|
||||
contentElement.innerHTML = contentDecrypted;
|
||||
// {% if pasta.extension == "auto" %}
|
||||
hljs.highlightAll();
|
||||
// {% endif %}
|
||||
contentElement.innerHTML = wrapStringInCodeLines(contentElement.innerHTML);
|
||||
// decryptDiv.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
function decryptWithPassword(password, encryptedHex) {
|
||||
const passwordBytes = aesjs.utils.utf8.toBytes(password.padStart(32, "#"));
|
||||
const encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex);
|
||||
const aesCtr = new aesjs.ModeOfOperation.ctr(passwordBytes);
|
||||
const decryptedBytes = aesCtr.decrypt(encryptedBytes);
|
||||
const res = aesjs.utils.utf8.fromBytes(decryptedBytes);
|
||||
|
||||
if (res.endsWith("!0K")) {
|
||||
return res.substring(0, res.length - "!0K".length);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function decryptFileWithPassword(password, encryptedHex) {
|
||||
const passwordBytes = aesjs.utils.utf8.toBytes(password.padStart(32, "#"));
|
||||
const encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex);
|
||||
const aesCtr = new aesjs.ModeOfOperation.ctr(passwordBytes);
|
||||
const decryptedBytes = aesCtr.decrypt(encryptedBytes);
|
||||
return decryptedBytes;
|
||||
}
|
||||
// {% endif %}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
code-line {
|
||||
counter-increment: line;
|
||||
text-align: right;
|
||||
float: left;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
code-line::before {
|
||||
content: counter(line);
|
||||
display: inline-block;
|
||||
padding-left: auto;
|
||||
margin-left: auto;
|
||||
text-align: left;
|
||||
width: 1.8rem;
|
||||
border-right: 1px solid lightgrey;
|
||||
color: grey;
|
||||
margin-right: 0.4rem;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#code {
|
||||
min-height: 2rem;
|
||||
}
|
||||
|
||||
#embed {
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 6px;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-height: 480px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
margin-left: 1rem;
|
||||
font-size: small;
|
||||
padding: 4px;
|
||||
padding-left: 0.8rem;
|
||||
padding-right: 0.8rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.code-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.small-button {
|
||||
font-size: small;
|
||||
padding: 4px;
|
||||
padding-left: 0.8rem;
|
||||
padding-right: 0.8rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% if !args.pure_html %}
|
||||
<style>
|
||||
#decryption {
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
width: fit-content;
|
||||
}
|
||||
</style>
|
||||
{% endif %}
|
||||
|
||||
{% include "footer.html" %}
|
Loading…
Add table
Reference in a new issue