diff --git a/.envrc b/.envrc deleted file mode 100644 index 3550a30..0000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 8fc887f..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,4 +0,0 @@ -# These are supported funding model platforms - -github: szabodanika -ko_fi: dani_sz diff --git a/.github/index.png b/.github/index.png deleted file mode 100644 index a557ed2..0000000 Binary files a/.github/index.png and /dev/null differ diff --git a/.github/logo.png b/.github/logo.png deleted file mode 100644 index 03e5a7d..0000000 Binary files a/.github/logo.png and /dev/null differ diff --git a/.github/workflows/build_nix.yml b/.github/workflows/build_nix.yml deleted file mode 100644 index 2f684e1..0000000 --- a/.github/workflows/build_nix.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: "Build legacy Nix package on Ubuntu" - -on: - push: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: cachix/install-nix-action@v12 - - name: Building package - run: nix-build . -A defaultPackage.x86_64-linux diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index 9c50ecd..0000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,192 +0,0 @@ -name: 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: 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.5 - 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 }} \ No newline at end of file diff --git a/.github/workflows/gh-release.yml b/.github/workflows/gh-release.yml deleted file mode 100644 index 66c88c8..0000000 --- a/.github/workflows/gh-release.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: GitHub Release - -on: - push: - tags: - - v[0-9]+.[0-9]+.[0-9]+* - -jobs: - release: - name: Publish to Github Releases - 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: 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.5 - 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 }} \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a930d6b..0000000 --- a/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ -result -.direnv/ - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -pasta_data/* diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index bab4773..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,2444 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "actix-codec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-sink", - "log", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "actix-files" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d832782fac6ca7369a70c9ee9a20554623c5e51c76e190ad151780ebea1cf689" -dependencies = [ - "actix-http", - "actix-service", - "actix-utils", - "actix-web", - "askama_escape 0.10.3", - "bitflags", - "bytes", - "derive_more", - "futures-core", - "http-range", - "log", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", -] - -[[package]] -name = "actix-http" -version = "3.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c83abf9903e1f0ad9973cc4f7b9767fd5a03a583f51a5b7a339e07987cd2724" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "ahash", - "base64", - "bitflags", - "brotli", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "futures-core", - "h2", - "http", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand", - "sha1", - "smallvec", - "tracing", - "zstd", -] - -[[package]] -name = "actix-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-multipart" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9edfb0e7663d7fe18c8d5b668c9c1bcf79176b1dcc9d4da9592503209a6bfb0" -dependencies = [ - "actix-utils", - "actix-web", - "bytes", - "derive_more", - "futures-core", - "httparse", - "local-waker", - "log", - "mime", - "twoway", -] - -[[package]] -name = "actix-router" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" -dependencies = [ - "bytestring", - "http", - "regex", - "serde", - "tracing", -] - -[[package]] -name = "actix-rt" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-server" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da34f8e659ea1b077bb4637948b815cd3768ad5a188fdcd74ff4d84240cd824" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "futures-util", - "mio", - "num_cpus", - "socket2", - "tokio", - "tracing", -] - -[[package]] -name = "actix-service" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" -dependencies = [ - "futures-core", - "paste", - "pin-project-lite", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - -[[package]] -name = "actix-web" -version = "4.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48f7b6534e06c7bfc72ee91db7917d4af6afe23e7d223b51e68fffbb21e96b9" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-utils", - "actix-web-codegen", - "ahash", - "bytes", - "bytestring", - "cfg-if", - "cookie", - "derive_more", - "encoding_rs", - "futures-core", - "futures-util", - "http", - "itoa", - "language-tags", - "log", - "mime", - "once_cell", - "pin-project-lite", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "socket2", - "time 0.3.16", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa9362663c8643d67b2d5eafba49e4cb2c8a053a29ed00a0bea121f17c76b13" -dependencies = [ - "actix-router", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "actix-web-httpauth" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c25a48b4684f90520183cd1a688e5f4f7e9905835fa75d02c0fe4f60fcdbe6" -dependencies = [ - "actix-service", - "actix-utils", - "actix-web", - "base64", - "futures-core", - "futures-util", - "pin-project-lite", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - -[[package]] -name = "aho-corasick" -version = "0.7.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "askama" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d298738b6e47e1034e560e5afe63aa488fea34e25ec11b855a76f0d7b8e73134" -dependencies = [ - "askama_derive", - "askama_escape 0.10.3", - "askama_shared 0.11.2", -] - -[[package]] -name = "askama-filters" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca4dc6a52fe9a1f3b62dd9c01c02102c73bff65c274d3c6bff1ccc850cfa83" -dependencies = [ - "askama_shared 0.9.1", - "chrono", - "regex", -] - -[[package]] -name = "askama_derive" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2925c4c290382f9d2fa3d1c1b6a63fa1427099721ecca4749b154cc9c25522" -dependencies = [ - "askama_shared 0.11.2", - "proc-macro2", - "syn", -] - -[[package]] -name = "askama_escape" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a577aeba5fec1aafb9f195d98cfcc38a78b588e4ebf9b15f62ca1c7aa33795a" - -[[package]] -name = "askama_escape" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" - -[[package]] -name = "askama_shared" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee517f4e33c27b129928e71d8a044d54c513e72e0b72ec5c4f5f1823e9de353" -dependencies = [ - "askama_escape 0.3.0", - "humansize", - "num-traits", - "serde", - "toml", -] - -[[package]] -name = "askama_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6083ccb191711e9c2b80b22ee24a8381a18524444914c746d4239e21d1afaf" -dependencies = [ - "askama_escape 0.10.3", - "humansize", - "nom", - "num-traits", - "percent-encoding", - "proc-macro2", - "quote", - "serde", - "syn", - "toml", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bit_field" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitvec" -version = "0.19.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" -dependencies = [ - "generic-array", -] - -[[package]] -name = "brotli" -version = "3.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bumpalo" -version = "3.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" - -[[package]] -name = "bytemuck" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" - -[[package]] -name = "bytesize" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70" -dependencies = [ - "serde", -] - -[[package]] -name = "bytestring" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b6a75fd3048808ef06af5cd79712be8111960adaf89d90250974b38fc3928a" -dependencies = [ - "bytes", -] - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" -dependencies = [ - "jobserver", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-integer", - "num-traits", - "time 0.1.44", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "clap" -version = "3.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "clap_lex", - "indexmap", - "once_cell", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_derive" -version = "3.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" -dependencies = [ - "percent-encoding", - "time 0.3.16", - "version_check", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "cpufeatures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cxx" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", -] - -[[package]] -name = "digest" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "either" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" - -[[package]] -name = "encoding_rs" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "env_logger" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "exr" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb5f255b5980bb0c8cf676b675d1a99be40f316881444f44e0462eaf5df5ded" -dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide 0.6.2", - "smallvec", - "threadpool", -] - -[[package]] -name = "flate2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" -dependencies = [ - "crc32fast", - "miniz_oxide 0.5.4", -] - -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "pin-project", - "spin", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - -[[package]] -name = "futures" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" - -[[package]] -name = "futures-executor" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" - -[[package]] -name = "futures-macro" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" - -[[package]] -name = "futures-task" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" - -[[package]] -name = "futures-util" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "gif" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "h2" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "half" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6a9459c9c30b177b925162351f97e7d967c7ea8bab3b8352805327daf45554" -dependencies = [ - "crunchy", -] - -[[package]] -name = "harsh" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6fce2283849822530a18d7d8eeb1719ac65a27cfb6649c0dc8dfd2d2cc5edfb" - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "html-escape" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e7479fa1ef38eb49fb6a42c426be515df2d063f06cb8efd3e50af073dbc26c" -dependencies = [ - "utf8-width", -] - -[[package]] -name = "http" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-range" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "humansize" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "iana-time-zone" -version = "0.1.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" -dependencies = [ - "cxx", - "cxx-build", -] - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "image" -version = "0.24.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8e4fb07cf672b1642304e731ef8a6a4c7891d67bb4fd4f5ce58cd6ed86803c" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "exr", - "gif", - "jpeg-decoder", - "num-rational", - "num-traits", - "png", - "scoped_threadpool", - "tiff", -] - -[[package]] -name = "indexmap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "itoa" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" - -[[package]] -name = "jobserver" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" -dependencies = [ - "libc", -] - -[[package]] -name = "jpeg-decoder" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b" -dependencies = [ - "rayon", -] - -[[package]] -name = "js-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "karton" -version = "2.0.1" -dependencies = [ - "actix-files", - "actix-multipart", - "actix-web", - "actix-web-httpauth", - "askama", - "askama-filters", - "bytesize", - "chrono", - "clap", - "env_logger", - "futures", - "harsh", - "lazy_static", - "linkify", - "log", - "mime_guess", - "qrcode-generator", - "rand", - "rust-embed", - "sanitize-filename", - "serde", - "serde_json", - "syntect", -] - -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - -[[package]] -name = "lexical-core" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" -dependencies = [ - "arrayvec", - "bitflags", - "cfg-if", - "ryu", - "static_assertions", -] - -[[package]] -name = "libc" -version = "0.2.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" - -[[package]] -name = "line-wrap" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", -] - -[[package]] -name = "link-cplusplus" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" -dependencies = [ - "cc", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "linkify" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d9967eb7d0bc31c39c6f52e8fce42991c0cd1f7a2078326f0b7a399a584c8d" -dependencies = [ - "memchr", -] - -[[package]] -name = "local-channel" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" -dependencies = [ - "futures-core", - "futures-sink", - "futures-util", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" - -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "mime_guess" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "miniz_oxide" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" -dependencies = [ - "adler", -] - -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", -] - -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom", -] - -[[package]] -name = "nom" -version = "6.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" -dependencies = [ - "bitvec", - "funty", - "lexical-core", - "memchr", - "version_check", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "once_cell" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" - -[[package]] -name = "onig" -version = "6.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" -dependencies = [ - "bitflags", - "libc", - "once_cell", - "onig_sys", -] - -[[package]] -name = "onig_sys" -version = "69.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" -dependencies = [ - "cc", - "pkg-config", -] - -[[package]] -name = "os_str_bytes" -version = "6.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "paste" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" - -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - -[[package]] -name = "pin-project" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" - -[[package]] -name = "plist" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" -dependencies = [ - "base64", - "indexmap", - "line-wrap", - "serde", - "time 0.3.16", - "xml-rs", -] - -[[package]] -name = "png" -version = "0.17.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" -dependencies = [ - "bitflags", - "crc32fast", - "flate2", - "miniz_oxide 0.5.4", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "qrcode-generator" -version = "4.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501c33c9127afb867693646b38817bf63a9a756d4b66aefbc5c0d7e5e8c3125a" -dependencies = [ - "html-escape", - "image", - "qrcodegen", -] - -[[package]] -name = "qrcodegen" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rayon" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" - -[[package]] -name = "rust-embed" -version = "6.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "6.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "7.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054" -dependencies = [ - "sha2", - "walkdir", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "sanitize-filename" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf18934a12018228c5b55a6dae9df5d0641e3566b3630cb46cc55564068e7c2f" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "scoped_threadpool" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "scratch" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" - -[[package]] -name = "semver" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" - -[[package]] -name = "serde" -version = "1.0.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - -[[package]] -name = "socket2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "spin" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" -dependencies = [ - "lock_api", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syntect" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c454c27d9d7d9a84c7803aaa3c50cd088d2906fe3c6e42da3209aa623576a8" -dependencies = [ - "bincode", - "bitflags", - "flate2", - "fnv", - "lazy_static", - "once_cell", - "onig", - "plist", - "regex-syntax", - "serde", - "serde_derive", - "serde_json", - "thiserror", - "walkdir", - "yaml-rust", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - -[[package]] -name = "thiserror" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - -[[package]] -name = "tiff" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259662e32d1e219321eb309d5f9d898b779769d81b76e762c07c8e5d38fcb65" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" -dependencies = [ - "itoa", - "libc", - "num_threads", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - -[[package]] -name = "time-macros" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b" -dependencies = [ - "time-core", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "1.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "winapi", -] - -[[package]] -name = "tokio-util" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "log", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "twoway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" -dependencies = [ - "memchr", - "unchecked-index", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "unchecked-index" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - -[[package]] -name = "unicode-ident" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "url" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf8-width" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" - -[[package]] -name = "weezl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" - -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" - -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.1+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" -dependencies = [ - "cc", - "libc", -] diff --git a/Cargo.toml b/Cargo.toml index 6432d52..4907afb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,28 +1,19 @@ [package] -name = "karton" -version = "2.0.1" -edition = "2021" -authors = ["Jade ", "Daniel Szabo "] -license = "BSD-3-Clause" -description = "Simple, performant, configurable, entirely self-contained Pastebin and URL shortener." -readme = "README.md" -homepage = "https://gitlab.com/obsidianical/microbin" -repository = "https://gitlab.com/obsidianical/microbin" -keywords = ["pastebin", "karton", "microbin", "actix", "selfhosted"] -categories = ["pastebins"] +name="microbin" +version="0.2.0" +edition="2021" [dependencies] -actix-web = "4" -actix-files = "0.6.0" -serde = { version = "1.0", features = ["derive"] } +actix-web="4" +actix-files="0.6.0" +serde={ version = "1.0", features = ["derive"] } serde_json = "1.0.80" -bytesize = { version = "1.1", features = ["serde"] } -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", "env"] } +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" sanitize-filename = "0.3.0" @@ -30,12 +21,4 @@ log = "0.4" env_logger = "0.9.0" actix-web-httpauth = "0.6.0" lazy_static = "1.4.0" -syntect = "5.0" -qrcode-generator = "4.1.6" -rust-embed = "6.4.2" -mime_guess = "2.0.4" -harsh = "0.2" - -[profile.release] -lto = true -strip = true +rutie = "0.8.4" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 4fb8ae3..9e743d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,31 @@ -FROM docker.io/rust:latest as build +# latest rust will be used to build the binary +FROM rust:latest as builder -WORKDIR /app +# 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 \ - DEBIAN_FRONTEND=noninteractive \ - apt-get update &&\ - apt-get -y install ca-certificates tzdata &&\ - 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 docker.io/bitnami/minideb:latest - -# microbin will be in /app -WORKDIR /app - -# copy time zone info -COPY --from=build \ - /usr/share/zoneinfo \ - /usr/share/zoneinfo - -COPY --from=build \ - /etc/ssl/certs/ca-certificates.crt \ - /etc/ssl/certs/ca-certificates.crt - -# copy built executable -COPY --from=build \ - /app/target/release/karton \ - /usr/bin/karton - -# Expose webport used for the webserver to the docker runtime -EXPOSE 8080 - -ENTRYPOINT ["karton"] +# run the binary +CMD ["microbin"] \ No newline at end of file diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..9c4501d --- /dev/null +++ b/README.MD @@ -0,0 +1,66 @@ +# MicroBin + +![Screenshot](git/index.png) + +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 + diff --git a/README.md b/README.md deleted file mode 100644 index b5f6a08..0000000 --- a/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Karton - -A small, rusty pastebin with URL shortener functionality. - -The github repository is a mirror of [this gitlab repository](https://gitlab.com/obsidianical/microbin). - -This is a fork of [MicroBin](https://github.com/szabodanika/microbin). - -## Features - -- Animal names (by default) or custom namefiles instead of just hashes (though hashes are an option too!) -- File and image uploads -- raw text serving -- URL shortening -- QR codes -- Listing and removing pastas (though currently everyone can do that) -- Expiration times -- Editable pastas -- Syntax highlighting -- Styling via [water.css](https://github.com/kognise/water.css) -- Customizable endpoints - -## Installation guide - -Karton is available on [Docker hub](https://hub.docker.com/r/schrottkatze/karton), [crates.io](https://crates.io/crates/karton) and using the nix flake. - -The only "officially supported" (I will actively debug and search for the problem) method is the last one using nix flakes. - -### Installation via the nix flake - -Add the repository to your inputs. - -```nix - karton.url = "git+https://gitlab.com/obsidianical/microbin.git"; -``` - -```nix -# microbin.nix -{ inputs, config, pkgs, ... }: -{ - environment.systemPackages = [ inputs.karton.defaultPackage."x86_64-linux" ]; - systemd.services.karton = { - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - environment = { - # set environment variables to configure karton - KARTON_HASH_IDS = ""; - KARTON_EDITABLE = ""; - KARTON_PRIVATE = ""; - KARTON_HIGHLIGHTSYNTAX = ""; - # adjust this to your domain - KARTON_PUBLIC_PATH = "https://example.org"; - KARTON_QR = ""; - # configure endpoints to be shorter - KARTON_URL_EP = "u"; - KARTON_RAW_EP = "r"; - KARTON_PASTA_EP = "p"; - }; - script = "${inputs.karton.defaultPackage."x86_64-linux"}/bin/karton"; - # register a simple systemd service - serviceConfig = { - Type = "simple"; - RootDirectory="/"; - WorkingDirectory = "/karton"; - }; - }; -} -``` - -## Contact - -This fork of MicroBin was created by [Schrottkatze](https://schrottkatze.de). - -Join [the matrix room](https://matrix.to/#/#s10e-microbin:matrix.org) to chat! - -Contact me via e-mail at [contact@schrottkatze.de](mailto:contact@schrottkatze.de). diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 559543e..0000000 --- a/TODO.md +++ /dev/null @@ -1,54 +0,0 @@ -# TODO lists - -these are just rough guides tho - -## v2.1 - -- [ ] customizable endpoints - - [ ] create - - [ ] edit - - [ ] info - - [ ] get pastas - - [ ] remove -- [ ] improve remove endpoint - - [ ] disable it -- [ ] client library - - [ ] request .well-known data - - [ ] support most endpoints -- [ ] karton cli - -## v3.0 - -- [ ] internal rewrite & docs -- [ ] design new frontend -- [ ] switch to yew - - [ ] using client lib -- [ ] theme and general config files - - [ ] unified theme format - - [ ] no env configs anymore if possible -- [ ] proper dbs - - [ ] sqlite - - [ ] postgres -- [ ] apis/endpoints - - [ ] IDs, name IDs AND user/pastaname - - [ ] root (and admin) user for root level pastas -- [ ] status/instance health admin dashboard and ap - - [ ] storage - - [ ] db status - - [ ] how up to date - - [ ] stats (users etc) - - [ ] errors - - [ ] loading speeds, performance monitor? - - [ ] memory use -- [ ] auth - - [ ] general auth - - [ ] oidc - - [ ] permssion system & api keys - - [ ] only allow some other users to open pasta - - [ ] access control and editing - - [ ] password protected pastas too -- [ ] features for pastas - - [ ] pw protection - - [ ] better editor - - [ ] markdown pastas - - [ ] optional, opt-in commenting diff --git a/default.nix b/default.nix deleted file mode 100644 index 39bacff..0000000 --- a/default.nix +++ /dev/null @@ -1,7 +0,0 @@ -(import ( - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz"; - sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; } -) { - src = ./.; -}).defaultNix diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 9e09da8..0000000 --- a/flake.lock +++ /dev/null @@ -1,77 +0,0 @@ -{ - "nodes": { - "naersk": { - "inputs": { - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1671096816, - "narHash": "sha256-ezQCsNgmpUHdZANDCILm3RvtO1xH8uujk/+EqNvzIOg=", - "owner": "nix-community", - "repo": "naersk", - "rev": "d998160d6a076cfe8f9741e56aeec7e267e3e114", - "type": "github" - }, - "original": { - "owner": "nix-community", - "ref": "master", - "repo": "naersk", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1677852945, - "narHash": "sha256-liiVJjkBTuBTAkRW3hrI8MbPD2ImYzwUpa7kvteiKhM=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "f5ffd5787786dde3a8bf648c7a1b5f78c4e01abb", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "type": "indirect" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1677852945, - "narHash": "sha256-liiVJjkBTuBTAkRW3hrI8MbPD2ImYzwUpa7kvteiKhM=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "f5ffd5787786dde3a8bf648c7a1b5f78c4e01abb", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "naersk": "naersk", - "nixpkgs": "nixpkgs_2", - "utils": "utils" - } - }, - "utils": { - "locked": { - "lastModified": 1676283394, - "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index c5fcff0..0000000 --- a/flake.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - inputs = { - naersk.url = "github:nix-community/naersk/master"; - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - utils.url = "github:numtide/flake-utils"; - }; - - outputs = { self, nixpkgs, utils, naersk }: - utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { inherit system; }; - naersk-lib = pkgs.callPackage naersk { }; - in - { - defaultPackage = naersk-lib.buildPackage ./.; - devShell = with pkgs; mkShell { - buildInputs = [ cargo rustc rustfmt pre-commit rustPackages.clippy cargo-watch podman ]; - RUST_SRC_PATH = rustPlatform.rustLibSrc; - }; - }); -} diff --git a/git/index.png b/git/index.png new file mode 100644 index 0000000..350f814 Binary files /dev/null and b/git/index.png differ diff --git a/plugins/BasicSyntaxHighlighter.rb b/plugins/BasicSyntaxHighlighter.rb new file mode 100644 index 0000000..db561b9 --- /dev/null +++ b/plugins/BasicSyntaxHighlighter.rb @@ -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}$$", ""); + content.gsub!("$$/#{color}$$", ""); + }; + + return content + + end + + end +end \ No newline at end of file diff --git a/plugins/MBPlugin.rb b/plugins/MBPlugin.rb new file mode 100644 index 0000000..b463c01 --- /dev/null +++ b/plugins/MBPlugin.rb @@ -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 \ No newline at end of file diff --git a/plugins/helloWorld.rb b/plugins/helloWorld.rb new file mode 100644 index 0000000..b94cd8b --- /dev/null +++ b/plugins/helloWorld.rb @@ -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 diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 77db547..0000000 --- a/shell.nix +++ /dev/null @@ -1,7 +0,0 @@ -(import ( - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz"; - sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; } -) { - src = ./.; -}).shellNix diff --git a/src/animalnumbers.rs b/src/animalnumbers.rs new file mode 100644 index 0000000..8af5ac9 --- /dev/null +++ b/src/animalnumbers.rs @@ -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 +} diff --git a/src/args.rs b/src/args.rs deleted file mode 100644 index 1920658..0000000 --- a/src/args.rs +++ /dev/null @@ -1,166 +0,0 @@ -use clap::Parser; -use lazy_static::lazy_static; -use std::convert::Infallible; -use std::fmt; -use std::net::IpAddr; -use std::path::PathBuf; -use std::str::FromStr; - -lazy_static! { - pub static ref ARGS: Args = Args::parse(); -} - -#[derive(Parser, Debug, Clone)] -#[clap(author, version, about, long_about = None)] -pub struct Args { - /// The username for basic HTTP auth. - /// If unset, HTTP authentication stays disabled. - /// - /// WARNING: people opening pastas will have to authenticate too. - #[clap(long, env = "KARTON_AUTH_USERNAME")] - pub auth_username: Option, - - /// Set a password for HTTP authentication. - /// If unset, HTTP authentication will not require a password. - /// If `auth_username` is unset, this option will not have any effect. - #[clap(long, env = "KARTON_AUTH_PASSWORD")] - pub auth_password: Option, - - /// Enable the option to make pastas editable. - #[clap(long, env = "KARTON_EDITABLE")] - pub editable: bool, - - /// The text displayed in the browser navigation bar. - #[clap(long, env = "KARTON_TITLE", default_value = " Karton")] - pub title: String, - - /// The web interfaces' footer text. - #[clap(long, env = "KARTON_FOOTER_TEXT")] - pub footer_text: Option, - - /// Hide the footer of the web interface. - #[clap(long, env = "KARTON_HIDE_FOOTER")] - pub hide_footer: bool, - - /// Hide the header of the web interface. - #[clap(long, env = "KARTON_HIDE_HEADER")] - pub hide_header: bool, - - /// Hide the logo in the header. - #[clap(long, env = "KARTON_HIDE_LOGO")] - pub hide_logo: bool, - - /// Disable the listing page. - #[clap(long, env = "KARTON_NO_LISTING")] - pub no_listing: bool, - - /// Enable syntax highlighting in pastas. - #[clap(long, env = "KARTON_HIGHLIGHTSYNTAX")] - pub highlightsyntax: bool, - - /// The port to which to bind the server. - #[clap(short, long, env = "KARTON_PORT", default_value_t = 8080)] - pub port: u16, - - /// The IP adress to bind the server to. - #[clap(short, long, env="KARTON_BIND", default_value_t = IpAddr::from([0, 0, 0, 0]))] - pub bind: IpAddr, - - /// Enable the option to create private pastas. - #[clap(long, env = "KARTON_PRIVATE")] - pub private: bool, - - /// Disables most css, apart form some inline styles. - #[clap(long, env = "KARTON_PURE_HTML")] - pub pure_html: bool, - - /// The servers public path, making it possible to run Karton behind a reverse proxy subpath. - #[clap(long, env="KARTON_PUBLIC_PATH", default_value_t = PublicUrl(String::from("")))] - pub public_path: PublicUrl, - - /// Enable creation of QR codes of pastas. Requires `public_path` to be set. - #[clap(long, env = "KARTON_QR")] - pub qr: bool, - - - /// Disable adding/removing/editing pastas. - #[clap(long, env = "KARTON_READONLY")] - pub readonly: bool, - - /// The amount of worker threads that the server is allowed to have. - #[clap(short, long, env = "KARTON_THREADS", default_value_t = 1)] - pub threads: u8, - - /// Sets a time value for the garbage collector. Pastas that aren't accessed for the given - /// amount of days will be deleted. Set to 0 to disable garbage collection. - #[clap(short, long, env = "KARTON_GC_DAYS", default_value_t = 90)] - pub gc_days: u16, - - /// Enable the option to delete after a given amount of reads. - #[clap(long, env = "KARTON_ENABLE_BURN_AFTER")] - pub enable_burn_after: bool, - - /// The default amount of reads for the self-delete mechanism. - #[clap(short, long, env = "KARTON_DEFAULT_BURN_AFTER", default_value_t = 0)] - pub default_burn_after: u16, - - /// Changes the UIs maximum width from 720 pixels to 1080. - #[clap(long, env = "KARTON_WIDE")] - pub wide: bool, - - /// Disable "Never" expiry setting. - #[clap(long, env = "KARTON_NO_ETERNAL_PASTA")] - pub no_eternal_pasta: bool, - - /// Set the default expiry time value. - #[clap(long, env = "KARTON_DEFAULT_EXPIRY", default_value = "24hour")] - pub default_expiry: String, - - /// Disable file uploading. - #[clap(short, long, env = "KARTON_NO_FILE_UPLOAD")] - pub no_file_upload: bool, - - // TODO: replace with simple path. - /// Replace built-in CSS file with a CSS file provided by the linked URL. - #[clap(long, env = "KARTON_CUSTOM_CSS")] - pub custom_css: Option, - - /// Replace built-in animal names file with custom names file for pasta links. - /// The file must be newline seperated. - #[clap(long, env = "KARTON_CUSTOM_NAMES")] - pub custom_names: Option, - - /// Enable the use of Hash IDs for shorter URLs instead of animal names. - #[clap(long, env = "KARTON_HASH_IDS")] - pub hash_ids: bool, - - /// Endpoint for /url/ - #[clap(long, env = "KARTON_URL_EP", default_value = "url" )] - pub url_endpoint: String, - - /// Endpoint for /pasta/ - #[clap(long, env = "KARTON_PASTA_EP", default_value = "pasta" )] - pub pasta_endpoint: String, - - /// Endpoint for /raw/ - #[clap(long, env = "KARTON_RAW_EP", default_value = "raw" )] - pub raw_endpoint: String, -} - -#[derive(Debug, Clone)] -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 { - let uri = s.strip_suffix('/').unwrap_or(s).to_owned(); - Ok(PublicUrl(uri)) - } -} diff --git a/src/util/dbio.rs b/src/dbio.rs similarity index 67% rename from src/util/dbio.rs rename to src/dbio.rs index 7c6a5e8..ef167cc 100644 --- a/src/util/dbio.rs +++ b/src/dbio.rs @@ -4,7 +4,7 @@ use std::io::{BufReader, BufWriter}; use crate::Pasta; -static DATABASE_PATH: &str = "pasta_data/database.json"; +static DATABASE_PATH: &'static str = "pasta_data/database.json"; pub fn save_to_file(pasta_data: &Vec) { let mut file = File::create(DATABASE_PATH); @@ -14,11 +14,11 @@ pub fn save_to_file(pasta_data: &Vec) { serde_json::to_writer(writer, &pasta_data).expect("Failed to create JSON writer"); } Err(_) => { - log::info!("Database file {DATABASE_PATH} not found!"); + log::info!("Database file {} not found!", DATABASE_PATH); file = File::create(DATABASE_PATH); match file { Ok(_) => { - log::info!("Database file {DATABASE_PATH} created."); + log::info!("Database file {} created.", DATABASE_PATH); save_to_file(pasta_data); } Err(err) => { @@ -27,7 +27,7 @@ pub fn save_to_file(pasta_data: &Vec) { &DATABASE_PATH, &err ); - panic!("Failed to create database file {DATABASE_PATH}: {err}!") + panic!("Failed to create database file {}: {}!", DATABASE_PATH, err) } } } @@ -39,17 +39,14 @@ pub fn load_from_file() -> io::Result> { match file { Ok(_) => { let reader = BufReader::new(file.unwrap()); - let data: Vec = match serde_json::from_reader(reader) { - Ok(t) => t, - _ => Vec::new(), - }; + let data: Vec = serde_json::from_reader(reader).unwrap(); Ok(data) } Err(_) => { - log::info!("Database file {DATABASE_PATH} not found!"); + log::info!("Database file {} not found!", DATABASE_PATH); save_to_file(&Vec::::new()); - log::info!("Database file {DATABASE_PATH} created."); + log::info!("Database file {} created.", DATABASE_PATH); load_from_file() } } diff --git a/src/endpoints/create.rs b/src/endpoints/create.rs deleted file mode 100644 index 917e959..0000000 --- a/src/endpoints/create.rs +++ /dev/null @@ -1,214 +0,0 @@ -use crate::dbio::save_to_file; -use crate::pasta::PastaFile; -use crate::util::hashids::to_hashids; -use crate::util::misc::is_valid_url; -use crate::util::pasta_id_converter::CONVERTER; -use crate::{AppState, Pasta, ARGS}; -use actix_multipart::Multipart; -use actix_web::http::StatusCode; -use actix_web::web::{BytesMut, BufMut}; -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}; - -use super::errors::ErrorTemplate; - -#[derive(Template)] -#[template(path = "index.html")] -struct IndexTemplate<'a> { - args: &'a ARGS, -} - -#[get("/")] -pub async fn index() -> impl Responder { - HttpResponse::Ok() - .content_type("text/html") - .body(IndexTemplate { args: &ARGS }.render().unwrap()) -} - -/// Pasta creation endpoint. -pub async fn create( - data: web::Data, - mut payload: Multipart, -) -> Result { - if ARGS.readonly { - return Ok(HttpResponse::Found() - .append_header(("Location", format!("{}/", ARGS.public_path))) - .finish()); - } - - let mut pastas = data.pastas.lock().await; - - 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::() as u64, - content: String::from("No Text Content"), - file: None, - extension: String::from(""), - private: false, - editable: false, - created: timenow, - read_count: 0, - burn_after_reads: 0, - last_read: timenow, - pasta_type: String::from(""), - expiration: 0, - }; - - while let Some(mut field) = payload.try_next().await? { - match field.name() { - "editable" => { - new_pasta.editable = true; - } - "private" => { - new_pasta.private = true; - } - "expiration" => { - while let Some(chunk) = field.try_next().await? { - new_pasta.expiration = match std::str::from_utf8(&chunk).unwrap() { - // TODO: customizable times - "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.no_eternal_pasta { - timenow + 60 * 60 * 24 * 7 - } else { - 0 - } - } - _ => { - log::error!("{}", "Unexpected expiration time!"); - timenow + 60 * 60 * 24 * 7 - } - }; - } - } - "burn_after" => { - while let Some(chunk) = field.try_next().await? { - new_pasta.burn_after_reads = match std::str::from_utf8(&chunk).unwrap() { - // TODO: also make customizable - // maybe options in config files, with defaults - // 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 - } - }; - } - } - "content" => { - let mut content = BytesMut::new(); - while let Some(chunk) = field.try_next().await? { - content.put(chunk); - } - if !content.is_empty() { - new_pasta.content = match String::from_utf8(content.to_vec()) { - Ok(v) => v, - Err(_) => return Ok(HttpResponse::BadRequest() - .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::BAD_REQUEST, - args: &ARGS - }.render().unwrap())), - }; - - new_pasta.pasta_type = if is_valid_url(new_pasta.content.as_str()) { - String::from("url") - } else { - String::from("text") - }; - } - } - "syntax-highlight" => { - while let Some(chunk) = field.try_next().await? { - new_pasta.extension = std::str::from_utf8(&chunk).unwrap().to_string(); - } - } - "file" => { - if ARGS.no_file_upload { - continue; - } - - let path = field.content_disposition().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!( - "./pasta_data/public/{}", - &new_pasta.id_as_animals() - )) - .unwrap(); - - let filepath = format!( - "./pasta_data/public/{}/{}", - &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(); - 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); - } - } - } - - let id = new_pasta.id; - - pastas.push(new_pasta); - - save_to_file(&pastas); - - let slug = if ARGS.hash_ids { - to_hashids(id) - } else { - CONVERTER.to_names(id) - }; - Ok(HttpResponse::Found() - .append_header(("Location", format!("{}/{}/{}", ARGS.public_path, ARGS.pasta_endpoint, slug))) - .finish()) -} diff --git a/src/endpoints/edit.rs b/src/endpoints/edit.rs deleted file mode 100644 index bfa4dba..0000000 --- a/src/endpoints/edit.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::args::Args; -use crate::dbio::save_to_file; -use crate::endpoints::errors::ErrorTemplate; -use crate::util::hashids::to_u64 as hashid_to_u64; -use crate::util::misc::remove_expired; -use crate::util::pasta_id_converter::CONVERTER; -use crate::{AppState, Pasta, ARGS}; -use actix_multipart::Multipart; -use actix_web::http::StatusCode; -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, -} - -#[get("/edit/{id}")] -pub async fn get_edit(data: web::Data, id: web::Path) -> HttpResponse { - let mut pastas = data.pastas.lock().await; - - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - CONVERTER.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))) - .finish(); - } - return HttpResponse::Ok() - .content_type("text/html") - .body(EditTemplate { pasta, args: &ARGS }.render().unwrap()); - } - } - - HttpResponse::NotFound() - .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) -} - -#[post("/edit/{id}")] -pub async fn post_edit( - data: web::Data, - id: web::Path, - mut payload: Multipart, -) -> Result { - if ARGS.readonly { - return Ok(HttpResponse::Found() - .append_header(("Location", format!("{}/", ARGS.public_path))) - .finish()); - } - - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) - }; - - let mut pastas = data.pastas.lock().await; - - remove_expired(&mut pastas); - - let mut new_content = String::from(""); - - while let Some(mut field) = payload.try_next().await? { - if field.name() == "content" { - while let Some(chunk) = field.try_next().await? { - new_content = std::str::from_utf8(&chunk).unwrap().to_string(); - } - } - } - - for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { - if pasta.editable { - pastas[i].content.replace_range(.., &new_content); - save_to_file(&pastas); - - return Ok(HttpResponse::Found() - .append_header(( - "Location", - format!("{}/pasta/{}", ARGS.public_path, pastas[i].id_as_animals()), - )) - .finish()); - } else { - break; - } - } - } - - Ok(HttpResponse::NotFound() - .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap())) -} diff --git a/src/endpoints/errors.rs b/src/endpoints/errors.rs deleted file mode 100644 index 744ede5..0000000 --- a/src/endpoints/errors.rs +++ /dev/null @@ -1,20 +0,0 @@ -use actix_web::{Error, HttpResponse, http::StatusCode}; -use askama::Template; - -use crate::args::{Args, ARGS}; - -#[derive(Template)] -#[template(path = "error.html")] -pub struct ErrorTemplate<'a> { - pub status_code: StatusCode, - pub args: &'a Args, -} - -pub async fn not_found() -> Result { - Ok(HttpResponse::NotFound() - .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap())) -} diff --git a/src/endpoints/info.rs b/src/endpoints/info.rs deleted file mode 100644 index 173ac05..0000000 --- a/src/endpoints/info.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::args::{Args, ARGS}; -use crate::pasta::Pasta; -use crate::AppState; -use actix_web::{get, web, HttpResponse}; -use askama::Template; - -#[derive(Template)] -#[template(path = "info.html")] -struct Info<'a> { - args: &'a Args, - pastas: &'a Vec, - status: &'a str, - version_string: &'a str, - message: &'a str, -} - -/// Endpoint to get information about the instance. -#[get("/info")] -pub async fn info(data: web::Data) -> HttpResponse { - // get access to the pasta collection - let pastas = data.pastas.lock().await; - - // TODO: status report more sophisticated - // maybe: - // - detect weird/invalid configurations? - // - detect server storage issues - // - detect performance problems? - let mut status = "OK"; - let mut message = ""; - - if ARGS.public_path.to_string() == "" { - status = "WARNING"; - message = "Warning: No public URL set with --public-path parameter. QR code and URL Copying functions have been disabled" - } - - HttpResponse::Ok().content_type("text/html").body( - Info { - args: &ARGS, - pastas: &pastas, - status, - version_string: env!("CARGO_PKG_VERSION"), - message - } - .render() - .unwrap(), - ) -} diff --git a/src/endpoints/pasta.rs b/src/endpoints/pasta.rs deleted file mode 100644 index 39ba5a6..0000000 --- a/src/endpoints/pasta.rs +++ /dev/null @@ -1,213 +0,0 @@ -use crate::args::{Args, ARGS}; -use crate::dbio::save_to_file; -use crate::endpoints::errors::ErrorTemplate; -use crate::pasta::Pasta; -use crate::util::hashids::to_u64 as hashid_to_u64; -use crate::util::misc::remove_expired; -use crate::AppState; -use crate::util::pasta_id_converter::CONVERTER; - -use actix_web::http::StatusCode; -use actix_web::{web, HttpResponse}; -use askama::Template; -use std::time::{SystemTime, UNIX_EPOCH}; - -#[derive(Template)] -#[template(path = "pasta.html", escape = "none")] -struct PastaTemplate<'a> { - pasta: &'a Pasta, - args: &'a Args, -} - -/// Endpoint to view a pasta. -pub async fn getpasta(data: web::Data, id: web::Path) -> HttpResponse { - // get access to the pasta collection - let mut pastas = data.pastas.lock().await; - - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - CONVERTER.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 - save_to_file(&pastas); - - // serve pasta in template - let response = HttpResponse::Ok().content_type("text/html").body( - PastaTemplate { - pasta: &pastas[index], - args: &ARGS, - } - .render() - .unwrap(), - ); - - // 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; - - return response; - } - - // otherwise - // send pasta not found error - HttpResponse::NotFound() - .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) -} - -/// Endpoint for redirection. -pub async fn redirecturl(data: web::Data, id: web::Path) -> HttpResponse { - // get access to the pasta collection - let mut pastas = data.pastas.lock().await; - - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - CONVERTER.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 - save_to_file(&pastas); - - // 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; - - return response; - // send error if we're trying to open a non-url pasta as a redirect - } else { - HttpResponse::NotFound() - .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()); - } - } - - // otherwise - // send pasta not found error - HttpResponse::NotFound() - .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) -} - -/// Endpoint to request pasta as raw file. -pub async fn getrawpasta(data: web::Data, id: web::Path) -> String { - // get access to the pasta collection - let mut pastas = data.pastas.lock().await; - - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - CONVERTER.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; - - // 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 - save_to_file(&pastas); - - // send raw content of pasta - return pastas[index].content.to_owned(); - } - - // otherwise - // send pasta not found error as raw text - String::from("Pasta not found! :-(") -} diff --git a/src/endpoints/pastalist.rs b/src/endpoints/pastalist.rs deleted file mode 100644 index 0ff15e4..0000000 --- a/src/endpoints/pastalist.rs +++ /dev/null @@ -1,37 +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 = "pastalist.html")] -struct PastaListTemplate<'a> { - pastas: &'a Vec, - args: &'a Args, -} - -/// The endpoint to view all currently registered pastas. -#[get("/pastalist")] -pub async fn list(data: web::Data) -> HttpResponse { - if ARGS.no_listing { - return HttpResponse::Found() - .append_header(("Location", format!("{}/", ARGS.public_path))) - .finish(); - } - - let mut pastas = data.pastas.lock().await; - - remove_expired(&mut pastas); - - HttpResponse::Ok().content_type("text/html").body( - PastaListTemplate { - pastas: &pastas, - args: &ARGS, - } - .render() - .unwrap(), - ) -} diff --git a/src/endpoints/qr.rs b/src/endpoints/qr.rs deleted file mode 100644 index 73dcb9b..0000000 --- a/src/endpoints/qr.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::args::{Args, ARGS}; -use crate::endpoints::errors::ErrorTemplate; -use crate::pasta::Pasta; -use crate::util::hashids::to_u64 as hashid_to_u64; -use crate::util::misc::{self, remove_expired}; -use crate::AppState; -use crate::util::pasta_id_converter::CONVERTER; -use actix_web::http::StatusCode; -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, -} - -/// Endpoint to open a QR code to a pasta. -#[get("/qr/{id}")] -pub async fn getqr(data: web::Data, id: web::Path) -> HttpResponse { - // get access to the pasta collection - let mut pastas = data.pastas.lock().await; - - let u64_id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - CONVERTER.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 /pasta 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, &id).as_str()), - _ => misc::string_to_qr_svg(format!("{}/pasta/{}", &ARGS.public_path, &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::NotFound() - .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) -} diff --git a/src/endpoints/remove.rs b/src/endpoints/remove.rs deleted file mode 100644 index 1dbe997..0000000 --- a/src/endpoints/remove.rs +++ /dev/null @@ -1,68 +0,0 @@ -use actix_web::http::StatusCode; -use actix_web::{get, web, HttpResponse}; - -use crate::args::ARGS; -use crate::endpoints::errors::ErrorTemplate; -use crate::pasta::PastaFile; -use crate::util::hashids::to_u64 as hashid_to_u64; -use crate::util::misc::remove_expired; -use crate::AppState; -use crate::util::pasta_id_converter::CONVERTER; -use askama::Template; -use std::fs; - -/// Endpoint to remove a pasta. -#[get("/remove/{id}")] -pub async fn remove(data: web::Data, id: web::Path) -> HttpResponse { - if ARGS.readonly { - return HttpResponse::Found() - .append_header(("Location", format!("{}/", ARGS.public_path))) - .finish(); - } - - let mut pastas = data.pastas.lock().await; - - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) - }; - - for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { - // remove the file itself - if let Some(PastaFile { name, .. }) = &pasta.file { - if fs::remove_file(format!( - "./pasta_data/public/{}/{}", - pasta.id_as_animals(), - name - )) - .is_err() - { - log::error!("Failed to delete file {}!", name) - } - - // and remove the containing directory - if fs::remove_dir(format!("./pasta_data/public/{}/", pasta.id_as_animals())) - .is_err() - { - log::error!("Failed to delete directory {}!", name) - } - } - // remove it from in-memory pasta list - pastas.remove(i); - return HttpResponse::Found() - .append_header(("Location", format!("{}/pastalist", ARGS.public_path))) - .finish(); - } - } - - remove_expired(&mut pastas); - - HttpResponse::NotFound() - .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) -} diff --git a/src/endpoints/static_resources.rs b/src/endpoints/static_resources.rs deleted file mode 100644 index bb738f4..0000000 --- a/src/endpoints/static_resources.rs +++ /dev/null @@ -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) -> impl Responder { - handle_embedded_file(path.as_str()) -} diff --git a/src/main.rs b/src/main.rs index fd4f412..7053d04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,51 +1,353 @@ extern crate core; -use crate::args::ARGS; -use crate::endpoints::{ - create, edit, errors, info, pasta as pasta_endpoint, pastalist, qr, remove, static_resources, -}; -use crate::pasta::Pasta; -use crate::util::dbio; -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 futures::lock::Mutex; -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 pasta_id_converter; - pub mod auth; - pub mod dbio; - pub mod hashids; - pub mod misc; - pub mod syntaxhighlighter; +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 create; - pub mod edit; - pub mod errors; - pub mod info; - pub mod pasta; - pub mod pastalist; - pub mod qr; - pub mod remove; - pub mod static_resources; +struct AppState { + pastas: Mutex>, } -pub struct AppState { - pub pastas: Mutex>, +#[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, + + #[clap(long)] + auth_password: Option, +} + +async fn auth_validator( + req: ServiceRequest, + credentials: BasicAuth, +) -> Result { + // 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, + 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 { + Ok(HttpResponse::Found() + .content_type("text/html") + .body(ErrorTemplate { args: &ARGS }.render().unwrap())) +} + +async fn create(data: web::Data, mut payload: Multipart) -> Result { + 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::() 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, id: web::Path) -> 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, id: web::Path) -> 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, id: web::Path) -> 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, id: web::Path) -> 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) -> 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!( @@ -60,16 +362,15 @@ 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("./pasta_data/public") { + match std::fs::create_dir_all("./pasta_data") { Ok(dir) => dir, Err(error) => { - log::error!("Couldn't create data directory ./pasta_data/public/: {error:?}"); - panic!("Couldn't create data directory ./pasta_data/public/: {error:?}"); + log::error!("Couldn't create data directory ./pasta_data: {:?}", error); + panic!("Couldn't create data directory ./pasta_data: {:?}", error); } }; @@ -81,40 +382,53 @@ async fn main() -> std::io::Result<()> { App::new() .app_data(data.clone()) .wrap(middleware::NormalizePath::trim()) - .service(create::index) - .service(info::info) - .route( - &format!("/{}/{{id}}", ARGS.pasta_endpoint), - web::get().to(pasta_endpoint::getpasta) - ) - .route( - &format!("/{}/{{id}}", ARGS.raw_endpoint), - web::get().to(pasta_endpoint::getrawpasta) - ) - .route( - &format!("/{}/{{id}}", ARGS.url_endpoint), - web::get().to(pasta_endpoint::redirecturl) - ) - //.service(pasta_endpoint::getpasta) - //.service(pasta_endpoint::getrawpasta) - //.service(pasta_endpoint::redirecturl) - .service(edit::get_edit) - .service(edit::post_edit) - .service(static_resources::static_resources) - .service(qr::getqr) - .service(actix_files::Files::new("/file", "./pasta_data/public/")) - .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(pastalist::list) + .service(remove) + .service(list) .wrap(Condition::new( - ARGS.auth_username.is_some(), - 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) { + // 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() +} diff --git a/src/pasta.rs b/src/pasta.rs index 38a215a..4f67823 100644 --- a/src/pasta.rs +++ b/src/pasta.rs @@ -1,74 +1,29 @@ -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::hashids::to_hashids; -use crate::util::pasta_id_converter::CONVERTER; -use crate::util::syntaxhighlighter::html_highlight; +use chrono::{DateTime, Datelike, NaiveDateTime, Timelike, Utc}; +use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, PartialEq, Eq)] -pub struct PastaFile { - pub name: String, - pub size: ByteSize, -} - -impl PastaFile { - pub fn from_unsanitized(path: &str) -> Result { - 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 - } - - /// Check if file is an image for embedding - pub fn is_image(&self) -> bool { - let guess = mime_guess::from_path(&self.name).first_or_text_plain(); - guess.type_() == "image" - } -} +use crate::to_animal_names; #[derive(Serialize, Deserialize)] pub struct Pasta { pub id: u64, pub content: String, - pub file: Option, - pub extension: String, - pub private: bool, - pub editable: bool, + pub file: String, pub created: i64, pub expiration: i64, - pub last_read: i64, - pub read_count: u64, - pub burn_after_reads: u64, - // what types can there be? - // `url`, `text`, pub pasta_type: String, } impl Pasta { pub fn id_as_animals(&self) -> String { - if ARGS.hash_ids { - to_hashids(self.id) - } else { - CONVERTER.to_names(self.id) - } + to_animal_names(self.id) } pub fn created_as_string(&self) -> String { - let date = Local.timestamp(self.created, 0); + let date = DateTime::::from_utc(NaiveDateTime::from_timestamp(self.created, 0), Utc); format!( - "{:02}-{:02} {:02}:{:02}", + "{:02}-{:02} {}:{}", date.month(), date.day(), date.hour(), @@ -80,9 +35,10 @@ impl Pasta { if self.expiration == 0 { String::from("Never") } else { - let date = Local.timestamp(self.expiration, 0); + let date = + DateTime::::from_utc(NaiveDateTime::from_timestamp(self.expiration, 0), Utc); format!( - "{:02}-{:02} {:02}:{:02}", + "{:02}-{:02} {}:{}", date.month(), date.day(), date.hour(), @@ -90,73 +46,6 @@ impl Pasta { ) } } - - 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} days ago"); - }; - - // 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} hours ago"); - }; - - // 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} minutes ago"); - }; - - // 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} seconds ago"); - }; - - // 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 { - self.content - .replace('`', "\\`") - .replace('$', "\\$") - .replace('/', "\\/") - } } impl fmt::Display for Pasta { diff --git a/src/plugins.rs b/src/plugins.rs new file mode 100644 index 0000000..03e8950 --- /dev/null +++ b/src/plugins.rs @@ -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 = init(); +} + +fn init() -> Vec { + 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) { + 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::() { + 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 { + let paths = fs::read_dir("./plugins").expect("Failed to access ./plugins library."); + + let mut plugin_paths: Vec = 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) -> Vec { + let mut plugin_codes: Vec = 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) { + 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) -> Vec { + let mut plugin_ids: Vec = Vec::new(); + for plugin_path in plugin_paths { + plugin_ids.push(plugin_path.replace("./plugins/", "").replace(".rb", "")) + } + plugin_ids +} diff --git a/src/util/auth.rs b/src/util/auth.rs deleted file mode 100644 index 4d554d1..0000000 --- a/src/util/auth.rs +++ /dev/null @@ -1,29 +0,0 @@ -use actix_web::dev::ServiceRequest; -use actix_web::{error, Error}; -use actix_web_httpauth::extractors::basic::BasicAuth; - -use crate::args::ARGS; - -pub async fn auth_validator( - req: ServiceRequest, - credentials: BasicAuth, -) -> Result { - // 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.")) - } -} diff --git a/src/util/hashids.rs b/src/util/hashids.rs deleted file mode 100644 index 0e53f31..0000000 --- a/src/util/hashids.rs +++ /dev/null @@ -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 { - 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) -} diff --git a/src/util/misc.rs b/src/util/misc.rs deleted file mode 100644 index 49d8bf8..0000000 --- a/src/util/misc.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::time::{SystemTime, UNIX_EPOCH}; - -use crate::args::ARGS; -use linkify::{LinkFinder, LinkKind}; -use qrcode_generator::QrCodeEcc; -use std::fs; - -use crate::{dbio, Pasta}; - -pub fn remove_expired(pastas: &mut Vec) { - // 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 the file itself - if let Some(file) = &p.file { - if fs::remove_file(format!( - "./pasta_data/public/{}/{}", - 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!("./pasta_data/public/{}/", p.id_as_animals())).is_err() { - log::error!("Failed to delete directory {}!", file.name()) - } - } - false - } - }); - - dbio::save_to_file(pastas); -} - -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() -} diff --git a/src/util/pasta_id_converter.rs b/src/util/pasta_id_converter.rs deleted file mode 100644 index 3d45d68..0000000 --- a/src/util/pasta_id_converter.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::fs; - -use lazy_static::lazy_static; - -use crate::args::ARGS; - -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", -]; - -lazy_static!{ - pub static ref CONVERTER: PastaIdConverter = PastaIdConverter::new(); -} - -/// Convert pasta IDs to names and vice versa -pub struct PastaIdConverter { - names: Vec -} - -impl PastaIdConverter { - pub fn new() -> Self { - let names; - if let Some(names_path) = &ARGS.custom_names { - let names_data = fs::read_to_string(names_path) - .expect("path for the names file should contain a names file"); - names = names_data - .split('\n') - .map(ToOwned::to_owned) - .collect::>(); - } else { - names = ANIMAL_NAMES - .iter() - .copied() - .map(ToOwned::to_owned) - .collect(); - } - - Self { names } - } - - pub fn to_names(&self, mut number: u64) -> String { - let mut result: Vec<&str> = Vec::new(); - - if number == 0 { - return self.names[0].parse().unwrap(); - } - - let mut power = 6; - - loop { - let digit = number / self.names.len().pow(power) as u64; - if !(result.is_empty() && digit == 0) { - result.push(&self.names[digit as usize]); - } - number -= digit * self.names.len().pow(power) as u64; - if power > 0 { - power -= 1; - } else if power == 0 || number == 0 { - break; - } - } - - result.join("-") - } - - pub fn to_u64(&self, pasta_id: &str) -> Result { - let mut result: u64 = 0; - - let names: Vec<&str> = pasta_id.split('-').collect(); - - let mut pow = names.len(); - for name in names { - pow -= 1; - let name_index = self.names.iter().position(|r| r == name); - match name_index { - None => return Err("Failed to convert animal name to u64!"), - Some(_) => { - result += (name_index.unwrap() * self.names.len().pow(pow as u32)) as u64 - } - } - } - - Ok(result) - } -} - -impl Default for PastaIdConverter { - fn default() -> Self { - Self::new() - } -} - - diff --git a/src/util/syntaxhighlighter.rs b/src/util/syntaxhighlighter.rs deleted file mode 100644 index bda6474..0000000 --- a/src/util/syntaxhighlighter.rs +++ /dev/null @@ -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!("{line}\n"); - } - - // 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 -} diff --git a/static/water.css b/static/water.css new file mode 100644 index 0000000..bc7ec46 --- /dev/null +++ b/static/water.css @@ -0,0 +1 @@ +:root{--background-body:#fff;--background:#efefef;--background-alt:#f7f7f7;--selection:#9e9e9e;--text-main:#363636;--text-bright:#000;--text-muted:#70777f;--links:#0076d1;--focus:rgba(0,150,191,0.67);--border:#dbdbdb;--code:#000;--animation-duration:0.1s;--button-base:#d0cfcf;--button-hover:#9b9b9b;--scrollbar-thumb:#aaa;--scrollbar-thumb-hover:var(--button-hover);--form-placeholder:#949494;--form-text:#1d1d1d;--variable:#39a33c;--highlight:#ff0;--select-arrow:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23161f27'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E")}@media (prefers-color-scheme:dark){:root{--background-body:#202b38;--background:#161f27;--background-alt:#1a242f;--selection:#1c76c5;--text-main:#dbdbdb;--text-bright:#fff;--text-muted:#a9b1ba;--links:#41adff;--focus:rgba(0,150,191,0.67);--border:#526980;--code:#ffbe85;--animation-duration:0.1s;--button-base:#0c151c;--button-hover:#040a0f;--scrollbar-thumb:var(--button-hover);--scrollbar-thumb-hover:#000;--form-placeholder:#a9a9a9;--form-text:#fff;--variable:#d941e2;--highlight:#efdb43;--select-arrow:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23efefef'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E")}}html{scrollbar-color:#aaa #fff;scrollbar-color:var(--scrollbar-thumb) var(--background-body);scrollbar-width:thin}@media (prefers-color-scheme:dark){html{scrollbar-color:#040a0f #202b38;scrollbar-color:var(--scrollbar-thumb) var(--background-body)}}body{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Segoe UI Emoji,Apple Color Emoji,Noto Color Emoji,sans-serif;line-height:1.4;max-width:800px;margin:20px auto;padding:0 10px;word-wrap:break-word;color:#363636;color:var(--text-main);background:#fff;background:var(--background-body);text-rendering:optimizeLegibility}@media (prefers-color-scheme:dark){body{background:#202b38;background:var(--background-body);color:#dbdbdb;color:var(--text-main)}}button{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}@media (prefers-color-scheme:dark){button{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}}input{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}@media (prefers-color-scheme:dark){input{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}}textarea{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}@media (prefers-color-scheme:dark){textarea{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}}h1{font-size:2.2em;margin-top:0}h1,h2,h3,h4,h5,h6{margin-bottom:12px;margin-top:24px}h1{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h1{color:#fff;color:var(--text-bright)}}h2{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h2{color:#fff;color:var(--text-bright)}}h3{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h3{color:#fff;color:var(--text-bright)}}h4{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h4{color:#fff;color:var(--text-bright)}}h5{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h5{color:#fff;color:var(--text-bright)}}h6{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h6{color:#fff;color:var(--text-bright)}}strong{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){strong{color:#fff;color:var(--text-bright)}}b,h1,h2,h3,h4,h5,h6,strong,th{font-weight:600}q:after,q:before{content:none}blockquote{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus);margin:1.5em 0;padding:.5em 1em;font-style:italic}@media (prefers-color-scheme:dark){blockquote{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus)}}q{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus);margin:1.5em 0;padding:.5em 1em;font-style:italic}@media (prefers-color-scheme:dark){q{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus)}}blockquote>footer{font-style:normal;border:0}address,blockquote cite{font-style:normal}a[href^=mailto\:]:before{content:"📧 "}a[href^=tel\:]:before{content:"📞 "}a[href^=sms\:]:before{content:"💬 "}mark{background-color:#ff0;background-color:var(--highlight);border-radius:2px;padding:0 2px;color:#000}@media (prefers-color-scheme:dark){mark{background-color:#efdb43;background-color:var(--highlight)}}a>code,a>strong{color:inherit}button,input[type=button],input[type=checkbox],input[type=radio],input[type=range],input[type=reset],input[type=submit],select{cursor:pointer}input,select{display:block}[type=checkbox],[type=radio]{display:initial}input{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){input{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}button{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){button{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}textarea{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){textarea{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}select{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){select{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}button{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){button{background-color:#0c151c;background-color:var(--button-base)}}input[type=submit]{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){input[type=submit]{background-color:#0c151c;background-color:var(--button-base)}}input[type=reset]{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){input[type=reset]{background-color:#0c151c;background-color:var(--button-base)}}input[type=button]{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){input[type=button]{background-color:#0c151c;background-color:var(--button-base)}}button:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){button:hover{background:#040a0f;background:var(--button-hover)}}input[type=submit]:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){input[type=submit]:hover{background:#040a0f;background:var(--button-hover)}}input[type=reset]:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){input[type=reset]:hover{background:#040a0f;background:var(--button-hover)}}input[type=button]:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){input[type=button]:hover{background:#040a0f;background:var(--button-hover)}}input[type=color]{min-height:2rem;padding:8px;cursor:pointer}input[type=checkbox],input[type=radio]{height:1em;width:1em}input[type=radio]{border-radius:100%}input{vertical-align:top}label{vertical-align:middle;margin-bottom:4px;display:inline-block}button,input:not([type=checkbox]):not([type=radio]),input[type=range],select,textarea{-webkit-appearance:none}textarea{display:block;margin-right:0;box-sizing:border-box;resize:vertical}textarea:not([cols]){width:100%}textarea:not([rows]){min-height:40px;height:140px}select{background:#efefef url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23161f27'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E") calc(100% - 12px) 50%/12px no-repeat;background:var(--background) var(--select-arrow) calc(100% - 12px) 50%/12px no-repeat;padding-right:35px}@media (prefers-color-scheme:dark){select{background:#161f27 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23efefef'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E") calc(100% - 12px) 50%/12px no-repeat;background:var(--background) var(--select-arrow) calc(100% - 12px) 50%/12px no-repeat}}select::-ms-expand{display:none}select[multiple]{padding-right:10px;background-image:none;overflow-y:auto}input:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){input:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}select:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){select:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}button:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){button:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}textarea:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){textarea:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}button:active,input[type=button]:active,input[type=checkbox]:active,input[type=radio]:active,input[type=range]:active,input[type=reset]:active,input[type=submit]:active{transform:translateY(2px)}button:disabled,input:disabled,select:disabled,textarea:disabled{cursor:not-allowed;opacity:.5}::-moz-placeholder{color:#949494;color:var(--form-placeholder)}:-ms-input-placeholder{color:#949494;color:var(--form-placeholder)}::-ms-input-placeholder{color:#949494;color:var(--form-placeholder)}::placeholder{color:#949494;color:var(--form-placeholder)}@media (prefers-color-scheme:dark){::-moz-placeholder{color:#a9a9a9;color:var(--form-placeholder)}:-ms-input-placeholder{color:#a9a9a9;color:var(--form-placeholder)}::-ms-input-placeholder{color:#a9a9a9;color:var(--form-placeholder)}::placeholder{color:#a9a9a9;color:var(--form-placeholder)}}fieldset{border:1px solid rgba(0,150,191,.67);border:1px solid var(--focus);border-radius:6px;margin:0 0 12px;padding:10px}@media (prefers-color-scheme:dark){fieldset{border:1px solid rgba(0,150,191,.67);border:1px solid var(--focus)}}legend{font-size:.9em;font-weight:600}input[type=range]{margin:10px 0;padding:10px 0;background:transparent}input[type=range]:focus{outline:none}input[type=range]::-webkit-slider-runnable-track{width:100%;height:9.5px;-webkit-transition:.2s;transition:.2s;background:#efefef;background:var(--background);border-radius:3px}@media (prefers-color-scheme:dark){input[type=range]::-webkit-slider-runnable-track{background:#161f27;background:var(--background)}}input[type=range]::-webkit-slider-thumb{box-shadow:0 1px 1px #000,0 0 1px #0d0d0d;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border);-webkit-appearance:none;margin-top:-7px}@media (prefers-color-scheme:dark){input[type=range]::-webkit-slider-thumb{background:#526980;background:var(--border)}}input[type=range]:focus::-webkit-slider-runnable-track{background:#efefef;background:var(--background)}@media (prefers-color-scheme:dark){input[type=range]:focus::-webkit-slider-runnable-track{background:#161f27;background:var(--background)}}input[type=range]::-moz-range-track{width:100%;height:9.5px;-moz-transition:.2s;transition:.2s;background:#efefef;background:var(--background);border-radius:3px}@media (prefers-color-scheme:dark){input[type=range]::-moz-range-track{background:#161f27;background:var(--background)}}input[type=range]::-moz-range-thumb{box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border)}@media (prefers-color-scheme:dark){input[type=range]::-moz-range-thumb{background:#526980;background:var(--border)}}input[type=range]::-ms-track{width:100%;height:9.5px;background:transparent;border-color:transparent;border-width:16px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#efefef;background:var(--background);border:.2px solid #010101;border-radius:3px;box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d}@media (prefers-color-scheme:dark){input[type=range]::-ms-fill-lower{background:#161f27;background:var(--background)}}input[type=range]::-ms-fill-upper{background:#efefef;background:var(--background);border:.2px solid #010101;border-radius:3px;box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d}@media (prefers-color-scheme:dark){input[type=range]::-ms-fill-upper{background:#161f27;background:var(--background)}}input[type=range]::-ms-thumb{box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;border:1px solid #000;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border)}@media (prefers-color-scheme:dark){input[type=range]::-ms-thumb{background:#526980;background:var(--border)}}input[type=range]:focus::-ms-fill-lower{background:#efefef;background:var(--background)}@media (prefers-color-scheme:dark){input[type=range]:focus::-ms-fill-lower{background:#161f27;background:var(--background)}}input[type=range]:focus::-ms-fill-upper{background:#efefef;background:var(--background)}@media (prefers-color-scheme:dark){input[type=range]:focus::-ms-fill-upper{background:#161f27;background:var(--background)}}a{text-decoration:none;color:#0076d1;color:var(--links)}@media (prefers-color-scheme:dark){a{color:#41adff;color:var(--links)}}a:hover{text-decoration:underline}code{background:#efefef;background:var(--background);color:#000;color:var(--code);padding:2.5px 5px;border-radius:6px;font-size:1em}@media (prefers-color-scheme:dark){code{color:#ffbe85;color:var(--code);background:#161f27;background:var(--background)}}samp{background:#efefef;background:var(--background);color:#000;color:var(--code);padding:2.5px 5px;border-radius:6px;font-size:1em}@media (prefers-color-scheme:dark){samp{color:#ffbe85;color:var(--code);background:#161f27;background:var(--background)}}time{background:#efefef;background:var(--background);color:#000;color:var(--code);padding:2.5px 5px;border-radius:6px;font-size:1em}@media (prefers-color-scheme:dark){time{color:#ffbe85;color:var(--code);background:#161f27;background:var(--background)}}pre>code{padding:10px;display:block;overflow-x:auto}var{color:#39a33c;color:var(--variable);font-style:normal;font-family:monospace}@media (prefers-color-scheme:dark){var{color:#d941e2;color:var(--variable)}}kbd{background:#efefef;background:var(--background);border:1px solid #dbdbdb;border:1px solid var(--border);border-radius:2px;color:#363636;color:var(--text-main);padding:2px 4px}@media (prefers-color-scheme:dark){kbd{color:#dbdbdb;color:var(--text-main);border:1px solid #526980;border:1px solid var(--border);background:#161f27;background:var(--background)}}img,video{max-width:100%;height:auto}hr{border:none;border-top:1px solid #dbdbdb;border-top:1px solid var(--border)}@media (prefers-color-scheme:dark){hr{border-top:1px solid #526980;border-top:1px solid var(--border)}}table{border-collapse:collapse;margin-bottom:10px;width:100%;table-layout:fixed}table caption,td,th{text-align:left}td,th{padding:6px;vertical-align:top;word-wrap:break-word}thead{border-bottom:1px solid #dbdbdb;border-bottom:1px solid var(--border)}@media (prefers-color-scheme:dark){thead{border-bottom:1px solid #526980;border-bottom:1px solid var(--border)}}tfoot{border-top:1px solid #dbdbdb;border-top:1px solid var(--border)}@media (prefers-color-scheme:dark){tfoot{border-top:1px solid #526980;border-top:1px solid var(--border)}}tbody tr:nth-child(2n){background-color:#efefef;background-color:var(--background)}@media (prefers-color-scheme:dark){tbody tr:nth-child(2n){background-color:#161f27;background-color:var(--background)}}tbody tr:nth-child(2n) button{background-color:#f7f7f7;background-color:var(--background-alt)}@media (prefers-color-scheme:dark){tbody tr:nth-child(2n) button{background-color:#1a242f;background-color:var(--background-alt)}}tbody tr:nth-child(2n) button:hover{background-color:#fff;background-color:var(--background-body)}@media (prefers-color-scheme:dark){tbody tr:nth-child(2n) button:hover{background-color:#202b38;background-color:var(--background-body)}}::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#efefef;background:var(--background);border-radius:6px}@media (prefers-color-scheme:dark){::-webkit-scrollbar-track{background:#161f27;background:var(--background)}}::-webkit-scrollbar-thumb{background:#aaa;background:var(--scrollbar-thumb);border-radius:6px}@media (prefers-color-scheme:dark){::-webkit-scrollbar-thumb{background:#040a0f;background:var(--scrollbar-thumb)}}::-webkit-scrollbar-thumb:hover{background:#9b9b9b;background:var(--scrollbar-thumb-hover)}@media (prefers-color-scheme:dark){::-webkit-scrollbar-thumb:hover{background:#000;background:var(--scrollbar-thumb-hover)}}::-moz-selection{background-color:#9e9e9e;background-color:var(--selection);color:#000;color:var(--text-bright)}::selection{background-color:#9e9e9e;background-color:var(--selection);color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){::-moz-selection{color:#fff;color:var(--text-bright)}::selection{color:#fff;color:var(--text-bright)}}@media (prefers-color-scheme:dark){::-moz-selection{background-color:#1c76c5;background-color:var(--selection)}::selection{background-color:#1c76c5;background-color:var(--selection)}}details{display:flex;flex-direction:column;align-items:flex-start;background-color:#f7f7f7;background-color:var(--background-alt);padding:10px 10px 0;margin:1em 0;border-radius:6px;overflow:hidden}@media (prefers-color-scheme:dark){details{background-color:#1a242f;background-color:var(--background-alt)}}details[open]{padding:10px}details>:last-child{margin-bottom:0}details[open] summary{margin-bottom:10px}summary{display:list-item;background-color:#efefef;background-color:var(--background);padding:10px;margin:-10px -10px 0;cursor:pointer;outline:none}@media (prefers-color-scheme:dark){summary{background-color:#161f27;background-color:var(--background)}}summary:focus,summary:hover{text-decoration:underline}details>:not(summary){margin-top:0}summary::-webkit-details-marker{color:#363636;color:var(--text-main)}@media (prefers-color-scheme:dark){summary::-webkit-details-marker{color:#dbdbdb;color:var(--text-main)}}dialog{background-color:#f7f7f7;background-color:var(--background-alt);color:#363636;color:var(--text-main);border-radius:6px;border:#dbdbdb;border-color:var(--border);padding:10px 30px}@media (prefers-color-scheme:dark){dialog{border-color:#526980;border-color:var(--border);color:#dbdbdb;color:var(--text-main);background-color:#1a242f;background-color:var(--background-alt)}}dialog>header:first-child{background-color:#efefef;background-color:var(--background);border-radius:6px 6px 0 0;margin:-10px -30px 10px;padding:10px;text-align:center}@media (prefers-color-scheme:dark){dialog>header:first-child{background-color:#161f27;background-color:var(--background)}}dialog::-webkit-backdrop{background:rgba(0,0,0,.61);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}dialog::backdrop{background:rgba(0,0,0,.61);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}footer{border-top:1px solid #dbdbdb;border-top:1px solid var(--border);padding-top:10px;color:#70777f;color:var(--text-muted)}@media (prefers-color-scheme:dark){footer{color:#a9b1ba;color:var(--text-muted);border-top:1px solid #526980;border-top:1px solid var(--border)}}body>footer{margin-top:40px}@media print{body,button,code,details,input,pre,summary,textarea{background-color:#fff}button,input,textarea{border:1px solid #000}body,button,code,footer,h1,h2,h3,h4,h5,h6,input,pre,strong,summary,textarea{color:#000}summary::marker{color:#000}summary::-webkit-details-marker{color:#000}tbody tr:nth-child(2n){background-color:#f2f2f2}a{color:#00f;text-decoration:underline}} diff --git a/templates/assets/favicon.svg b/templates/assets/favicon.svg deleted file mode 100644 index fb63341..0000000 --- a/templates/assets/favicon.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/templates/assets/logo.png b/templates/assets/logo.png deleted file mode 100644 index 37cbc86..0000000 Binary files a/templates/assets/logo.png and /dev/null differ diff --git a/templates/assets/logo.svg b/templates/assets/logo.svg deleted file mode 100644 index ca349e6..0000000 --- a/templates/assets/logo.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/templates/assets/water.css b/templates/assets/water.css deleted file mode 100644 index 422b511..0000000 --- a/templates/assets/water.css +++ /dev/null @@ -1,626 +0,0 @@ -/* - * This is (basically) water.css. - * - * repo: https://github.com/kognise/water.css - * - * The license: - * - * The MIT License (MIT) - * - * Copyright © 2019 Kognise - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the “Software”), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -:root { - --background-body:#4a4a55; - --background:#383844; - --background-alt:#242438; - --selection:#23bf7c; - --text-main:#dfdfef; - --text-bright:#f7f7ff; - --text-muted:#878797; - --links:#28db8f; - --focus:#299465df; - --border:#676773; - --code:var(--text-main); - --animation-duration:0.1s; - --button-base:#299465; - --button-hover:#23bf7c; - --scrollbar-thumb:var(--button-hover); - --scrollbar-thumb-hover:#000; - --form-placeholder:#a9a9a9; - --form-text:#fff; - --variable:#d941e2; - --highlight:#efdb43; - --select-arrow:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23efefef'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E") -} -html { - scrollbar-color:#040a0f #202b38; - scrollbar-color:var(--scrollbar-thumb) var(--background-body); - scrollbar-width:thin -} -body { - font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Segoe UI Emoji,Apple Color Emoji,Noto Color Emoji,sans-serif; - line-height:1.4; - max-width:800px; - margin:20px auto; - padding:0 10px; - word-wrap:break-word; - color:#dbdbdb; - color:var(--text-main); - background:#202b38; - background:var(--background-body); - text-rendering:optimizeLegibility -} -button, -input, -textarea { - transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease; - transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease -} -h1 { - font-size:2.2em; - margin-top:0 -} -h1, -h2, -h3, -h4, -h5, -h6 { - margin-bottom:12px; - margin-top:24px -} -h1, -h2, -h3, -h4, -h5, -h6, -strong { - color:#fff; - color:var(--text-bright) -} -b, -h1, -h2, -h3, -h4, -h5, -h6, -strong, -th { - font-weight:600 -} -q:after, -q:before { - content:none -} -blockquote, -q { - border-left:4px solid rgba(0,150,191,.67); - border-left:4px solid var(--focus); - margin:1.5em 0; - padding:.5em 1em; - font-style:italic -} -blockquote>footer { - font-style:normal; - border:0 -} -address, -blockquote cite { - font-style:normal -} -a[href^=mailto\:]:before { - content:"📧 " -} -a[href^=tel\:]:before { - content:"📞 " -} -a[href^=sms\:]:before { - content:"💬 " -} -mark { - background-color:#efdb43; - background-color:var(--highlight); - border-radius:2px; - padding:0 2px; - color:#000 -} -a>code, -a>strong { - color:inherit -} -button, -input[type=button], -input[type=checkbox], -input[type=radio], -input[type=range], -input[type=reset], -input[type=submit], -select { - cursor:pointer -} -input, -select { - display:block -} -[type=checkbox], -[type=radio] { - display:initial -} -button, -input, -select, -textarea { - color:#fff; - color:var(--form-text); - background-color:#161f27; - background-color:var(--background); - font-family:inherit; - font-size:inherit; - margin-right:6px; - margin-bottom:6px; - padding:10px; - border:none; - border-radius:6px; - outline:none -} -button, -input[type=button], -input[type=reset], -input[type=submit] { - background-color:#0c151c; - background-color:var(--button-base); - padding-right:30px; - padding-left:30px -} -button:hover, -input[type=button]:hover, -input[type=reset]:hover, -input[type=submit]:hover { - background:#040a0f; - background:var(--button-hover) -} -input[type=color] { - min-height:2rem; - padding:8px; - cursor:pointer -} -input[type=checkbox], -input[type=radio] { - height:1em; - width:1em -} -input[type=radio] { - border-radius:100% -} -input { - vertical-align:top -} -label { - vertical-align:middle; - margin-bottom:4px; - display:inline-block -} -button, -input:not([type=checkbox]):not([type=radio]), -input[type=range], -select, -textarea { - -webkit-appearance:none -} -textarea { - display:block; - margin-right:0; - box-sizing:border-box; - resize:vertical -} -textarea:not([cols]) { - width:100% -} -textarea:not([rows]) { - min-height:40px; - height:140px -} -select { - background:#161f27 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23efefef'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E") calc(100% - 12px) 50%/12px no-repeat; - background:var(--background) var(--select-arrow) calc(100% - 12px) 50%/12px no-repeat; - padding-right:35px -} -select::-ms-expand { - display:none -} -select[multiple] { - padding-right:10px; - background-image:none; - overflow-y:auto -} -button:focus, -input:focus, -select:focus, -textarea:focus { - box-shadow:0 0 0 2px rgba(0,150,191,.67); - box-shadow:0 0 0 2px var(--focus) -} -button:active, -input[type=button]:active, -input[type=checkbox]:active, -input[type=radio]:active, -input[type=range]:active, -input[type=reset]:active, -input[type=submit]:active { - transform:translateY(2px) -} -button:disabled, -input:disabled, -select:disabled, -textarea:disabled { - cursor:not-allowed; - opacity:.5 -} -::-moz-placeholder { - color:#a9a9a9; - color:var(--form-placeholder) -} -:-ms-input-placeholder { - color:#a9a9a9; - color:var(--form-placeholder) -} -::-ms-input-placeholder { - color:#a9a9a9; - color:var(--form-placeholder) -} -::placeholder { - color:#a9a9a9; - color:var(--form-placeholder) -} -fieldset { - border:1px solid rgba(0,150,191,.67); - border:1px solid var(--focus); - border-radius:6px; - margin:0 0 12px; - padding:10px -} -legend { - font-size:.9em; - font-weight:600 -} -input[type=range] { - margin:10px 0; - padding:10px 0; - background:transparent -} -input[type=range]:focus { - outline:none -} -input[type=range]::-webkit-slider-runnable-track { - width:100%; - height:9.5px; - -webkit-transition:.2s; - transition:.2s; - background:#161f27; - background:var(--background); - border-radius:3px -} -input[type=range]::-webkit-slider-thumb { - box-shadow:0 1px 1px #000,0 0 1px #0d0d0d; - height:20px; - width:20px; - border-radius:50%; - background:#526980; - background:var(--border); - -webkit-appearance:none; - margin-top:-7px -} -input[type=range]:focus::-webkit-slider-runnable-track { - background:#161f27; - background:var(--background) -} -input[type=range]::-moz-range-track { - width:100%; - height:9.5px; - -moz-transition:.2s; - transition:.2s; - background:#161f27; - background:var(--background); - border-radius:3px -} -input[type=range]::-moz-range-thumb { - box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d; - height:20px; - width:20px; - border-radius:50%; - background:#526980; - background:var(--border) -} -input[type=range]::-ms-track { - width:100%; - height:9.5px; - background:transparent; - border-color:transparent; - border-width:16px 0; - color:transparent -} -input[type=range]::-ms-fill-lower, -input[type=range]::-ms-fill-upper { - background:#161f27; - background:var(--background); - border:.2px solid #010101; - border-radius:3px; - box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d -} -input[type=range]::-ms-thumb { - box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d; - border:1px solid #000; - height:20px; - width:20px; - border-radius:50%; - background:#526980; - background:var(--border) -} -input[type=range]:focus::-ms-fill-lower, -input[type=range]:focus::-ms-fill-upper { - background:#161f27; - background:var(--background) -} -a { - text-decoration:none; - color:#41adff; - color:var(--links) -} -a:hover { - text-decoration:underline -} -code, -samp, -time { - background:#161f27; - background:var(--background); - color:#ffbe85; - color:var(--code); - padding:2.5px 5px; - border-radius:6px; - font-size:1em -} -pre>code { - padding:10px; - display:block; - overflow-x:auto -} -var { - color:#d941e2; - color:var(--variable); - font-style:normal; - font-family:monospace -} -kbd { - background:#161f27; - background:var(--background); - border:1px solid #526980; - border:1px solid var(--border); - border-radius:2px; - color:#dbdbdb; - color:var(--text-main); - padding:2px 4px -} -img, -video { - max-width:100%; - height:auto -} -hr { - border:none; - border-top:1px solid #526980; - border-top:1px solid var(--border) -} -table { - border-collapse:collapse; - margin-bottom:10px; - width:100%; - table-layout:fixed -} -table caption, -td, -th { - text-align:left -} -td, -th { - padding:6px; - vertical-align:top; - word-wrap:break-word -} -thead { - border-bottom:1px solid #526980; - border-bottom:1px solid var(--border) -} -tfoot { - border-top:1px solid #526980; - border-top:1px solid var(--border) -} -tbody tr:nth-child(2n) { - background-color:#161f27; - background-color:var(--background) -} -tbody tr:nth-child(2n) button { - background-color:#1a242f; - background-color:var(--background-alt) -} -tbody tr:nth-child(2n) button:hover { - background-color:#202b38; - background-color:var(--background-body) -} -::-webkit-scrollbar { - height:10px; - width:10px -} -::-webkit-scrollbar-track { - background:#161f27; - background:var(--background); - border-radius:6px -} -::-webkit-scrollbar-thumb { - background:#040a0f; - background:var(--scrollbar-thumb); - border-radius:6px -} -::-webkit-scrollbar-thumb:hover { - background:#000; - background:var(--scrollbar-thumb-hover) -} -::-moz-selection { - background-color:#1c76c5; - background-color:var(--selection); - color:#fff; - color:var(--text-bright) -} -::selection { - background-color:#1c76c5; - background-color:var(--selection); - color:#fff; - color:var(--text-bright) -} -details { - display:flex; - flex-direction:column; - align-items:flex-start; - background-color:#1a242f; - background-color:var(--background-alt); - padding:10px 10px 0; - margin:1em 0; - border-radius:6px; - overflow:hidden -} -details[open] { - padding:10px -} -details>:last-child { - margin-bottom:0 -} -details[open] summary { - margin-bottom:10px -} -summary { - display:list-item; - background-color:#161f27; - background-color:var(--background); - padding:10px; - margin:-10px -10px 0; - cursor:pointer; - outline:none -} -summary:focus, -summary:hover { - text-decoration:underline -} -details>:not(summary) { - margin-top:0 -} -summary::-webkit-details-marker { - color:#dbdbdb; - color:var(--text-main) -} -dialog { - background-color:#1a242f; - background-color:var(--background-alt); - color:#dbdbdb; - color:var(--text-main); - border-radius:6px; - border:#526980; - border-color:var(--border); - padding:10px 30px -} -dialog>header:first-child { - background-color:#161f27; - background-color:var(--background); - border-radius:6px 6px 0 0; - margin:-10px -30px 10px; - padding:10px; - text-align:center -} -dialog::-webkit-backdrop { - background:rgba(0,0,0,.61); - -webkit-backdrop-filter:blur(4px); - backdrop-filter:blur(4px) -} -dialog::backdrop { - background:rgba(0,0,0,.61); - -webkit-backdrop-filter:blur(4px); - backdrop-filter:blur(4px) -} -footer { - border-top:1px solid #526980; - border-top:1px solid var(--border); - padding-top:10px; - color:#a9b1ba; - color:var(--text-muted) -} -body>footer { - margin-top:40px -} -@media print { - body, - button, - code, - details, - input, - pre, - summary, - textarea { - background-color:#fff - } - button, - input, - textarea { - border:1px solid #000 - } - body, - button, - code, - footer, - h1, - h2, - h3, - h4, - h5, - h6, - input, - pre, - strong, - summary, - textarea { - color:#000 - } - summary::marker { - color:#000 - } - summary::-webkit-details-marker { - color:#000 - } - tbody tr:nth-child(2n) { - background-color:#f2f2f2 - } - a { - color:#00f; - text-decoration:underline - } -} - diff --git a/templates/edit.html b/templates/edit.html deleted file mode 100644 index fe47b34..0000000 --- a/templates/edit.html +++ /dev/null @@ -1,20 +0,0 @@ -{% include "header.html" %} -
-

- Editing pasta '{{ pasta.id_as_animals() }}' -

- -
- -
- - {% if args.readonly %} - - {%- else %} - - {%- endif %} - - -
-
-{% include "footer.html" %} diff --git a/templates/error.html b/templates/error.html index a0c007c..05f55d5 100644 --- a/templates/error.html +++ b/templates/error.html @@ -1,10 +1,10 @@ {% include "header.html" %}
-

{{ status_code.as_u16() }}

-{{ status_code.canonical_reason().unwrap_or("Unknown error") }} +

404

+Not Found

- Go Home + Go Home

{% include "footer.html" %} diff --git a/templates/footer.html b/templates/footer.html index dcac7b4..0b59f3d 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -1,18 +1,13 @@ + {% if !args.hide_footer %}

- {% if args.footer_text.as_ref().is_none() %} - Karton by Schrottkatze, based on MicroBin by Dániel Szabó and the FOSS Community. + MicroBin by Daniel Szabo. Fork me on GitHub! Let's keep the Web compact, accessible and humane! - {%- else %} - {{ args.footer_text.as_ref().unwrap() }} - {%- endif %}

{%- endif %} - - - + \ No newline at end of file diff --git a/templates/header.html b/templates/header.html index 7154bdf..7c0b7bc 100644 --- a/templates/header.html +++ b/templates/header.html @@ -1,65 +1,35 @@ - - - {{ args.title }} - + MicroBin - {% if !args.pure_html %} - {% if args.custom_css.as_ref().is_none() %} - - {%- else %} - + {%- endif %} - {%- endif %} - - -{% if args.wide %} - + - {%- else %} - - {%- endif %} -
+
- {% if !args.hide_header %} +{% if !args.hide_header %} - + + μ MicroBin + - {% if !args.hide_logo %} - - {%- endif %} - {{ args.title }} - +New Pasta - New - +Pasta List - List +GitHub + +
- Info - -
- - {%- endif %} +{%- endif %} diff --git a/templates/index.html b/templates/index.html index cdae99f..848d274 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,252 +1,27 @@ {% include "header.html" %} - -
+ +
+
+
-
-
-
- -
- {% if args.enable_burn_after %} -
-
- -
- {%- endif %} - - {% if args.highlightsyntax %} -
-
- -
- {%- else %} - - {%- endif %} - -
- {% if args.editable || args.private %} - - {%- endif %} - {% if args.editable %} -
- - -
- {%- endif %} - {% if args.private %} -
- - -
- {%- endif %} -
-
- -
- {% if !args.no_file_upload %} - - {% endif %} - {% if args.readonly %} - - - - - {%- else %} - - - - - {%- endif %} -
- +
+ +
+ +
+ +
+ +
- - - - - {% include "footer.html" %} diff --git a/templates/info.html b/templates/info.html deleted file mode 100644 index 6373c87..0000000 --- a/templates/info.html +++ /dev/null @@ -1,42 +0,0 @@ -{% include "header.html" %} - -

Welcome to MicroBin

-
- - -
-

Info

- - - - - - - - - - - - - -
Version{{version_string}}
Status{{status}}
Pastas{{pastas.len()}}
-
-
- -{% if message != "" %} -

Messages

-

{{message}}

-{%- endif %} - -
- -{% include "footer.html" %} diff --git a/templates/pasta.html b/templates/pasta.html index fa5b885..4701c2a 100644 --- a/templates/pasta.html +++ b/templates/pasta.html @@ -1,149 +1,9 @@ {% include "header.html" %} -
- {% if pasta.content != "No Text Content" %} - - {% if args.public_path.to_string() != "" && pasta.pasta_type == "url" %} - - {%- endif %} - Raw Text - Content - {%- endif %} - {% if args.qr && args.public_path.to_string() != "" %} - QR - {%- endif %} - {% if pasta.editable %} - Edit - {%- endif %} - Remove -
-
- {{pasta.id_as_animals()}} - {% if args.public_path.to_string() != "" %} - - {%- endif %} -
-{% if pasta.file.is_some() %} -
-
-{% if pasta.file.as_ref().unwrap().is_image() %} - -
+Raw Text Content +{% if pasta.file != "no-file" %} +Attached file '{{pasta.file}}' {%- endif %} - - Download attached file: '{{pasta.file.as_ref().unwrap().name()}}' [{{pasta.file.as_ref().unwrap().size}}] - -{%- endif %} -
-
-{% if pasta.content != "No Text Content" %} -
-
- {% if args.highlightsyntax %} -
{{pasta.content_syntax_highlighted()}}
- {%- else %} -
{{pasta.content_not_highlighted()}}
- {%- endif %} -
-
-{%- endif %} -
- {% if pasta.read_count == 1 %} -

Read {{pasta.read_count}} time, last {{pasta.last_read_time_ago_as_string()}}

- {%- else %} -

Read {{pasta.read_count}} times, last {{pasta.last_read_time_ago_as_string()}}

- {%- endif %} - -
- -
- - - - - +Remove +
{{pasta}}
{% include "footer.html" %} diff --git a/templates/pastalist.html b/templates/pastalist.html index ec231f2..f380763 100644 --- a/templates/pastalist.html +++ b/templates/pastalist.html @@ -4,117 +4,98 @@ {% if pastas.is_empty() %}

- No pastas yet. 😔 Create one here. + No pastas yet. 😔 Create one here.


{%- else %} -

Pastas

-{% if args.pure_html %} - - {% else %} -
- {% endif %} - - - - - - - - {% for pasta in pastas %} - {% if pasta.pasta_type == "text" && !pasta.private %} - - - - - - - {%- endif %} - {% endfor %} - -
- Key - - Created - - Expiration - -
- {{pasta.id_as_animals()}} - - {{pasta.created_as_string()}} - - {{pasta.expiration_as_string()}} - - Raw - {% if pasta.file.is_some() %} - File - {%- endif %} - {% if pasta.editable %} - Edit - {%- endif %} - Remove -
-
-

URL Redirects

- {% if args.pure_html %} - - {% else %} -
- {% endif %} - - - - - - - {% for pasta in pastas %} - {% if pasta.pasta_type == "url" && !pasta.private %} - - - - - - - {%- endif %} - {% endfor %} - -
- Key - - Created - - Expiration - -
- {{pasta.id_as_animals()}} - - {{pasta.created_as_string()}} - - {{pasta.expiration_as_string()}} - - Open - Copy - {% if pasta.editable %} - Edit - {%- endif %} - Remove -
-
+
+ + + + + + + + + + + + + + {% for pasta in pastas %} + {% if pasta.pasta_type == "text" %} + + + + + + + {%- endif %} + {% endfor %} + +
Pastas
+ Key + + Created + + Expiration + + +
+ {{pasta.id_as_animals()}} + + {{pasta.created_as_string()}} + + {{pasta.expiration_as_string()}} + + Raw + {% if pasta.file != "no-file" %} + File {%- endif %} + Remove +
+
+ + + + + + + + + + + + + {% for pasta in pastas %} + {% if pasta.pasta_type == "url" %} + + + + + + + {%- endif %} + {% endfor %} + +
URL Redirects
+ Key + + Created + + Expiration + - - - {% include "footer.html" %} +
+ {{pasta.id_as_animals()}} + + {{pasta.created_as_string()}} + + {{pasta.expiration_as_string()}} + + Raw + Remove +
+
+{%- endif %} +{% include "footer.html" %} diff --git a/templates/qr.html b/templates/qr.html deleted file mode 100644 index 5645c07..0000000 --- a/templates/qr.html +++ /dev/null @@ -1,29 +0,0 @@ -{% include "header.html" %} - - - - -
- {% if pasta.pasta_type == "url" %} - - {{qr}} - - {% else %} - - {{qr}} - - {% endif %} -
- - - -{% include "footer.html" %} \ No newline at end of file