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 index 9c50ecd..5051c6b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,165 +1,23 @@ -name: Release +name: Docker Image on: push: + branches: + - "master" tags: - - v[0-9]+.[0-9]+.[0-9]+* + - "v*" + pull_request: + branches: + - "master" 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/') + docker_image: + name: Build & push docker image to DockerHub runs-on: ubuntu-latest - needs: release steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Docker meta id: meta uses: docker/metadata-action@v4 @@ -169,24 +27,24 @@ jobs: 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 + + - name: Setup QEMU + uses: docker/setup-qemu-action@v2 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push - uses: docker/build-push-action@v2 + + - name: Build & push + uses: docker/build-push-action@v3 with: - build-args: | - REPO=${{ github.repository }} - VER=${{ github.ref_name }} - platforms: | - linux/amd64 - linux/arm64 + 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 + labels: ${{ steps.meta.outputs.labels }} 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..4b0821e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "karton" -version = "2.0.1" +name = "microbin" +version = "1.1.0" edition = "2021" -authors = ["Jade ", "Daniel Szabo "] +authors = ["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"] +homepage = "https://github.com/szabodanika/microbin" +repository = "https://github.com/szabodanika/microbin" +keywords = ["pastebin", "pastabin", "microbin", "actix", "selfhosted"] categories = ["pastebins"] [dependencies] @@ -31,10 +31,6 @@ 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 diff --git a/Dockerfile b/Dockerfile index 4fb8ae3..21e31e9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/rust:latest as build +FROM rust:latest as build WORKDIR /app @@ -8,11 +8,10 @@ RUN \ DEBIAN_FRONTEND=noninteractive \ apt-get update &&\ apt-get -y install ca-certificates tzdata &&\ - CARGO_NET_GIT_FETCH_WITH_CLI=true \ cargo build --release # https://hub.docker.com/r/bitnami/minideb -FROM docker.io/bitnami/minideb:latest +FROM bitnami/minideb:latest # microbin will be in /app WORKDIR /app @@ -26,12 +25,9 @@ COPY --from=build \ /etc/ssl/certs/ca-certificates.crt \ /etc/ssl/certs/ca-certificates.crt -# copy built executable +# copy built exacutable COPY --from=build \ - /app/target/release/karton \ - /usr/bin/karton + /app/target/release/microbin \ + /usr/bin/microbin -# Expose webport used for the webserver to the docker runtime -EXPOSE 8080 - -ENTRYPOINT ["karton"] +ENTRYPOINT ["microbin"] \ No newline at end of file diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..2fb6af4 --- /dev/null +++ b/README.MD @@ -0,0 +1,331 @@ + +![Screenshot](git/index.png) + +# MicroBin + + +![Build](https://github.com/szabodanika/microbin/actions/workflows/rust.yml/badge.svg) +![crates.io](https://img.shields.io/crates/v/microbin.svg) + + +MicroBin is a super tiny, feature rich, configurable, self-contained and self-hosted paste bin web application. It is very easy to set up and use, and will only require a few megabytes of memory and disk storage. It takes only a couple minutes to set it up, why not give it a try now? + +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/szabodanika/microbin) + +Or install from Cargo: + +`cargo install microbin` + +And run with your custom configuration: + +`microbin --port 8080 --highlightsyntax --editable` + +### Features +- Is very small +- Animal names instead of random numbers for pasta identifiers (64 animals) +- File uploads (eg. server.com/file/pig-dog-cat) +- Raw pasta text (eg. server.com/raw/pig-dog-cat) +- URL shortening and redirection +- Very simple database (JSON + files) for portability, easy backups and integration +- Listing and manually removing pastas (/pastalist) +- Private and public pastas +- Editable and final pastas +- Never expiring pastas +- Automatically expiring pastas +- Syntax highlighting +- Entirely self-contained executable, MicroBin is a single file! +- Automatic dark mode (follows system preferences) +- Very little CSS and absolutely no JS (see [water.css](https://github.com/kognise/water.css)) +- Most of the above can be toggled on and off! + +## 1 Usage + +### What is a "pasta" anyway? + +In microbin, a pasta can be: +- A text that you want to paste from one machine to another, eg. some code, +- A file that you want to share, eg. a video that is too large for Discord, a zip with a code project in it or an image, +- A URL redirect. + +### When is MicroBin useful? + +You can use MicroBin +- As a URL shortener/redirect service, +- To send long texts to other people, +- To send large files to other people, +- To serve content on the web, eg. configuration files for testing, images, or any other file content using the Raw functionality, +- To move files between your desktop and a server you access from the console, +- As a "postbox" service where people can upload their files or texts, but they cannot see or remove what others sent you - just disable the pastalist page +- To take notes! Simply create an editable pasta. + +...and many other things, why not get creative? + +### Creating a Pasta + +Navigate to the root of your server, for example https://microbin.myserver.com/. This should show you a form where you will at the very least see an expiration selector, a file attachment input, a content text field and a green save button. Depending on your configuration there miight also be a syntax highlight selector, an editable checkbox and a private ceckbox. + +Use the expiration dropdown to choose how long you want your pasta to exist. When the selected time has expired, it will be removed from the server. The content can be any text, including plain text, code, html, even a URL. A URL is a special case, because when you open the pasta again, it will redirect you to that URL instead of showing it as a text. Entering content is optional, and so is the file attachment. If you want, you can even submit a pasta completely empty. + +You will be redirected to the URL of the pasta, which will end with a few animal names. If you remember those animals, you can simply type them in on another machine and open your pasta elsewhere. + +If you have editable pastas enabled and you check the editable checkbox, then later on there will be an option to change the text content of your pasta. Selecting the private checkbox will simply prevent your pasta to show up on the pasta list page, if that is enabled. + +If you have syntax higlighting enabled, then select your language from the dropdown, or leave it as none if you just want to upload plain with no highlighting. + +### Listing Pastas + +If you have pasta listing enabled, then there is a pasta list option in the navigation bar, which will list all the pastas on the server in two groups: regular pastas and URL redirects (pastas containing nothing but a URL). If you have private pastas enabled, they will not show up here at all. + +From the pasta list page, you will be able to view individual pastas by clicking on their animal identifiers on the lest, view their raw contrent by clicking on the Raw button, remove them, and if you have editable pastas enabled, then open them in edit view. + +### Use MicroBin from the console 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). + +## 2 Installation + +### From Cargo + +Install from Cargo: + +`cargo install microbin` + +Remember, MicroBin will create your database and file storage wherever you execute it. I recommend that you create a folder for it first and execute it there: + +`mkdir ~/microbin/` + +`cd ~/microbin/` + +`microbin --highlightsyntax --editable` + +### From AUR (for any Arch-based distro) +Install `microbin` package from AUR and start/enable microbin systemd service. Systemd will start server on 127.0.0.1:8080 with almost all features enabled, but this can be changed in `/etc/microbin.conf`. + +### Building MicroBin + +Simply clone the repository, build it with `cargo build --release` and run the `microbin` executable in the created `target/release/` directory. It will start listening on 0.0.0.0:8080. You can change the port or bind address with CL arguments `-p (--port)` or `-b (--bind)` respectively . For other arguments see [the Wiki](https://github.com/szabodanika/microbin/wiki). + +``` +git clone https://github.com/szabodanika/microbin.git +cd microbin +cargo build --release +./target/release/microbin -p 80 +``` + +### Building Docker Image + +MicroBin includes a Dockerfile. To build the image, follow these steps: + +``` +git clone https://github.com/szabodanika/microbin.git +cd microbin +docker build -t microbin-docker . +``` + +Then, for `docker compose` you can repurpose the following example in your compose file: + +``` +services: + paste: + image: microbin-docker + restart: always + ports: + - "80:8080" + volumes: + - ./microbin-data:/usr/local/bin/pasta_data +``` +To pass command line arguments you must edit the Dockerfile and change the CMD line. In this example we add the syntax highlighting option and enable private pastas: + +``` +CMD ["microbin", "--highlightsyntax", "--private"] +``` +You then need to rebuild the image and recreate your container. + + +**Note:** If you are getting the following error about domain name resolution: + +``` +warning: spurious network error (2 tries remaining): failed to resolve address for github.com: Temporary failure in name resolution; class=Net (12) +warning: spurious network error (1 tries remaining): failed to resolve address for github.com: Temporary failure in name resolution; class=Net (12) +``` + +You might need to run `docker build` with the `--network` option: + +``` +docker build --network host -t microbin-docker . +``` + +### MicroBin as a service + +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. If you installed MicroBin from cargo, your executable will be in your cargo directory, e.g. `/Users/daniel/.cargo/bin/microbin`. + +``` +[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 +``` + +Here is my `microbin.service` for example, with some optional arguments: + + +``` +[Unit] +Description=MicroBin +After=network.target + +[Service] +Type=simple +Restart=always +User=ubuntu +RootDirectory=/ + +# This is the directory where I want to run microbin. It will store all the pastas here. +WorkingDirectory=/home/ubuntu/server/microbin + +# This is the location of my executable - I also have 2 optional features enabled +ExecStart=/home/ubuntu/server/microbin/target/release/microbin --editable --highlightsyntax + +# I keep my installation in the home directory, so I need to add this +ProtectHome=off + +[Install] +WantedBy=multi-user.target +``` + +Then start the service with `systemctl start microbin` and enable it on boot with `systemctl enable microbin`. To update your MicroBin, simply update or clone the repository again, build it again, and then restart the service with `systemctl restart microbin`. An update will never affect your existing pastas, unless there is a breaking change in the data model (in which case MicroBin just won't be able to import your DB), which will always be mentioned explicitly. + +### NGINX configuration + +``` +server { + # I have HTTPS enabled using certbot - you can use HTTP of course if you want! + listen 443 ssl; # managed by Certbot + + server_name microbin.myserver.com; + + location / { + # Make sure to change the port if you are not running MicroBin at 8080! + proxy_pass http://127.0.0.1:8080$request_uri; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # Limit content size - I have 1GB because my MicroBin server is private, no one else will use it. + client_max_body_size 1024M; + + ssl_certificate /etc/letsencrypt/live/microbin.myserver.com/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/microbin.myserver.com/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot +} + +``` + +## 3 Command Line Arguments + +There is an ever expanding list of customisations built into MicroBin so you can use it the way you want. Instead of a configuration file, we simply use arguments that you pass to the executable, making the workflow even simpler. Read the following options and if you cannot find what you need, you can always open an issue at our GitHub repository and request a new feature! + +### --auth-username [AUTH_USERNAME] + +Require username for HTTP Basic Authentication when visiting the service. If `--auth-username` is set but `--auth-password ` is not, just leave the password field empty when logging in. You can also just go to https://username:password@yourserver.net or https://username@yourserver.net if password is not set instead of typing into the password + +### --auth-password [AUTH_PASSWORD] + +Require password for HTTP Basic Authentication when visiting the service. Will not have any affect unless `--auth-username` is also set. If `--auth-username` is set but `--auth-password ` is not, just leave the password field empty when logging in. You can also just go to https://username:password@yourserver.net or https://username@yourserver.net if password is not set instead of typing into the password prompt. + +### --editable + +Enables editable pastas. You will still be able to make finalised pastas but there will be an extra checkbox to make your new pasta editable from the pasta list or the pasta view page. + +### --footer_text [TEXT] + +Replaces the default footer text with your own. If you want to hide the footer, use --hide-footer instead. + +### -h, --help + +Show all commands in the terminal. + +### --hide-footer + +Hides the footer on every page. + +### --hide-header + +Hides the navigation bar on every page. + +### --hide-logo + +Hides the MicroBin logo from the navigation bar on every page. + +### --no-listing + +Disables the /pastalist endpoint, essentially making all pastas private. + +### --highlightsyntax + +Enables syntax highlighting support. When creating a new pasta, a new dropdown selector will be added where you can select your pasta's syntax, or just leave it empty for no highlighting. + +### -p, --port [PORT] + +Default value: 8080 + +Sets the port for the server will be listening on. + + +### -b, --bind [ADDRESS] + +Default value: 0.0.0.0 + +Sets the bind address for the server will be listening on. Both ipv4 and ipv6 are supported. + +### --private + +Enables private pastas. Adds a new checkbox to make your pasta private, which then won't show up on the pastalist page. With the URL to your pasta, it will still be accessible. + +### --pure-html + +Disables main CSS styling, just uses a few in-line stylings for the layout. With this option you will lose dark-mode support. + +### --readonly + +Disables adding/editing/removing pastas entirely. + +### --title [TITLE] + +Replaces "MicroBin" with your title of choice in the navigation bar. + +### -t, --threads [THREADS] + +Default value: 1 + +Number of workers MicroBin is allowed to have. Increase this to the number of CPU cores you have if you want to go beast mode, but for personal use one worker is enough. + +### -V, --version + +Displays your MicroBin's version information. + +### --wide + +Changes the maximum width of the UI from 720 pixels to 1080 pixels. 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/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f86af39 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 1.1.* | :white_check_mark: | +| < 1.1.0 | :x: | + +## Reporting a Vulnerability + +Security vulnerabilities can be reported directly to +the developer/maintainer at d@szab.eu. + +Sensitive information may be GPG encrypted with my public key available at +https://szab.eu/assets/files/daniel-szabo-pub.asc. 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..b1e2384 Binary files /dev/null and b/git/index.png differ diff --git a/render.yaml b/render.yaml new file mode 100644 index 0000000..3c3e430 --- /dev/null +++ b/render.yaml @@ -0,0 +1,9 @@ +services: + - type: web + name: microbin + plan: free + numInstances: 1 + env: rust + repo: https://github.com/szabodanika/microbin.git + buildCommand: cargo build --release + startCommand: ./target/release/microbin --editable --highlightsyntax 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/args.rs b/src/args.rs index 1920658..577fa0f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,10 +1,6 @@ +use std::net::IpAddr; 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(); @@ -13,154 +9,54 @@ lazy_static! { #[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")] + #[clap(long, env="MICROBIN_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")] + #[clap(long, env="MICROBIN_AUTH_PASSWORD")] pub auth_password: Option, - /// Enable the option to make pastas editable. - #[clap(long, env = "KARTON_EDITABLE")] + #[clap(long, env="MICROBIN_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")] + #[clap(long, env="MICROBIN_FOOTER_TEXT")] pub footer_text: Option, - /// Hide the footer of the web interface. - #[clap(long, env = "KARTON_HIDE_FOOTER")] + #[clap(long, env="MICROBIN_HIDE_FOOTER")] pub hide_footer: bool, - /// Hide the header of the web interface. - #[clap(long, env = "KARTON_HIDE_HEADER")] + #[clap(long, env="MICROBIN_HIDE_HEADER")] pub hide_header: bool, - /// Hide the logo in the header. - #[clap(long, env = "KARTON_HIDE_LOGO")] + #[clap(long, env="MICROBIN_HIDE_LOGO")] pub hide_logo: bool, - /// Disable the listing page. - #[clap(long, env = "KARTON_NO_LISTING")] + #[clap(long, env="MICROBIN_NO_LISTING")] pub no_listing: bool, - /// Enable syntax highlighting in pastas. - #[clap(long, env = "KARTON_HIGHLIGHTSYNTAX")] + #[clap(long, env="MICROBIN_HIGHLIGHTSYNTAX")] pub highlightsyntax: bool, - /// The port to which to bind the server. - #[clap(short, long, env = "KARTON_PORT", default_value_t = 8080)] + #[clap(short, long, env="MICROBIN_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]))] + #[clap(short, long, env="MICROBIN_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")] + #[clap(long, env="MICROBIN_PRIVATE")] pub private: bool, - /// Disables most css, apart form some inline styles. - #[clap(long, env = "KARTON_PURE_HTML")] + #[clap(long, env="MICROBIN_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")] + #[clap(long, env="MICROBIN_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)] + #[clap(long, env="MICROBIN_TITLE")] + pub title: Option, + + #[clap(short, long, env="MICROBIN_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")] + #[clap(long, env="MICROBIN_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)) - } -} +} \ No newline at end of file diff --git a/src/endpoints/create.rs b/src/endpoints/create.rs index 917e959..8c8b1a3 100644 --- a/src/endpoints/create.rs +++ b/src/endpoints/create.rs @@ -1,12 +1,9 @@ use crate::dbio::save_to_file; use crate::pasta::PastaFile; -use crate::util::hashids::to_hashids; +use crate::util::animalnumbers::to_animal_names; 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; @@ -16,8 +13,6 @@ 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> { @@ -31,18 +26,17 @@ pub async fn index() -> impl Responder { .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))) + .append_header(("Location", "/")) .finish()); } - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(n) => n.as_secs(), @@ -60,9 +54,6 @@ pub async fn create( private: false, editable: false, created: timenow, - read_count: 0, - burn_after_reads: 0, - last_read: timenow, pasta_type: String::from(""), expiration: 0, }; @@ -70,87 +61,51 @@ pub async fn create( while let Some(mut field) = payload.try_next().await? { match field.name() { "editable" => { + // while let Some(_chunk) = field.try_next().await? {} new_pasta.editable = true; + continue; } "private" => { + // while let Some(_chunk) = field.try_next().await? {} new_pasta.private = true; + continue; } "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 - } - } + "never" => 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 } }; } + + continue; } "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.content = std::str::from_utf8(&chunk).unwrap().to_string(); new_pasta.pasta_type = if is_valid_url(new_pasta.content.as_str()) { String::from("url") } else { String::from("text") }; } + continue; } "syntax-highlight" => { while let Some(chunk) = field.try_next().await? { new_pasta.extension = std::str::from_utf8(&chunk).unwrap().to_string(); } + continue; } "file" => { - if ARGS.no_file_upload { - continue; - } - let path = field.content_disposition().get_filename(); let path = match path { @@ -159,7 +114,7 @@ pub async fn create( None => continue, }; - let mut file = match PastaFile::from_unsanitized(path) { + let mut file = match PastaFile::from_unsanitized(&path) { Ok(f) => f, Err(e) => { warn!("Unsafe file name: {e:?}"); @@ -167,11 +122,8 @@ pub async fn create( } }; - std::fs::create_dir_all(format!( - "./pasta_data/public/{}", - &new_pasta.id_as_animals() - )) - .unwrap(); + std::fs::create_dir_all(format!("./pasta_data/public/{}", &new_pasta.id_as_animals())) + .unwrap(); let filepath = format!( "./pasta_data/public/{}/{}", @@ -191,9 +143,7 @@ pub async fn create( new_pasta.file = Some(file); new_pasta.pasta_type = String::from("text"); } - field => { - log::error!("Unexpected multipart field: {}", field); - } + _ => {} } } @@ -203,12 +153,7 @@ pub async fn create( 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))) + .append_header(("Location", format!("/pasta/{}", to_animal_names(id)))) .finish()) } diff --git a/src/endpoints/edit.rs b/src/endpoints/edit.rs index bfa4dba..f928324 100644 --- a/src/endpoints/edit.rs +++ b/src/endpoints/edit.rs @@ -1,12 +1,10 @@ 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::animalnumbers::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; @@ -20,13 +18,9 @@ struct EditTemplate<'a> { #[get("/edit/{id}")] pub async fn get_edit(data: web::Data, id: web::Path) -> HttpResponse { - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) - }; + let id = to_u64(&*id.into_inner()).unwrap_or(0); remove_expired(&mut pastas); @@ -34,21 +28,23 @@ pub async fn get_edit(data: web::Data, id: web::Path) -> HttpR if pasta.id == id { if !pasta.editable { return HttpResponse::Found() - .append_header(("Location", format!("{}/", ARGS.public_path))) + .append_header(("Location", "/")) .finish(); } - return HttpResponse::Ok() - .content_type("text/html") - .body(EditTemplate { pasta, args: &ARGS }.render().unwrap()); + return HttpResponse::Ok().content_type("text/html").body( + EditTemplate { + pasta: &pasta, + args: &ARGS, + } + .render() + .unwrap(), + ); } } - HttpResponse::NotFound() + HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) + .body(ErrorTemplate { args: &ARGS }.render().unwrap()) } #[post("/edit/{id}")] @@ -59,41 +55,37 @@ pub async fn post_edit( ) -> Result { if ARGS.readonly { return Ok(HttpResponse::Found() - .append_header(("Location", format!("{}/", ARGS.public_path))) + .append_header(("Location", "/")) .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 id = to_u64(&*id.into_inner()).unwrap_or(0); - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); 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(); + match 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); + 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()), - )) + .append_header(("Location", format!("/pasta/{}", pastas[i].id_as_animals()))) .finish()); } else { break; @@ -101,10 +93,7 @@ pub async fn post_edit( } } - Ok(HttpResponse::NotFound() + Ok(HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap())) + .body(ErrorTemplate { args: &ARGS }.render().unwrap())) } diff --git a/src/endpoints/errors.rs b/src/endpoints/errors.rs index 744ede5..04626b4 100644 --- a/src/endpoints/errors.rs +++ b/src/endpoints/errors.rs @@ -1,4 +1,4 @@ -use actix_web::{Error, HttpResponse, http::StatusCode}; +use actix_web::{Error, HttpResponse}; use askama::Template; use crate::args::{Args, ARGS}; @@ -6,15 +6,11 @@ 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() + Ok(HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap())) + .body(ErrorTemplate { args: &ARGS }.render().unwrap())) } diff --git a/src/endpoints/help.rs b/src/endpoints/help.rs new file mode 100644 index 0000000..2ca8f38 --- /dev/null +++ b/src/endpoints/help.rs @@ -0,0 +1,23 @@ +use crate::args::{Args, ARGS}; +use actix_web::{get, HttpResponse}; +use askama::Template; +use std::marker::PhantomData; + +#[derive(Template)] +#[template(path = "help.html")] +struct Help<'a> { + args: &'a Args, + _marker: PhantomData<&'a ()>, +} + +#[get("/help")] +pub async fn help() -> HttpResponse { + HttpResponse::Ok().content_type("text/html").body( + Help { + args: &ARGS, + _marker: Default::default(), + } + .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 index 39ba5a6..26ac11d 100644 --- a/src/endpoints/pasta.rs +++ b/src/endpoints/pasta.rs @@ -1,16 +1,12 @@ +use actix_web::{get, web, HttpResponse}; +use askama::Template; + 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::animalnumbers::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")] @@ -19,195 +15,74 @@ struct PastaTemplate<'a> { args: &'a Args, } -/// Endpoint to view a pasta. +#[get("/pasta/{id}")] 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 mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) - }; + let id = to_u64(&*id.into_inner()).unwrap_or(0); + + println!("{}", id); - // 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() { + for pasta in pastas.iter() { 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 + return HttpResponse::Ok().content_type("text/html").body( + PastaTemplate { + pasta: &pasta, + args: &ARGS, } - } 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()); + .render() + .unwrap(), + ); } } - // otherwise - // send pasta not found error - HttpResponse::NotFound() + HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) + .body(ErrorTemplate { 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; +#[get("/url/{id}")] +pub async fn redirecturl(data: web::Data, id: web::Path) -> HttpResponse { + let mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) - }; + let id = 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() { + for pasta in pastas.iter() { if pasta.id == id { - index = i; - found = true; - break; + if pasta.pasta_type == "url" { + return HttpResponse::Found() + .append_header(("Location", String::from(&pasta.content))) + .finish(); + } else { + return HttpResponse::Ok() + .content_type("text/html") + .body(ErrorTemplate { args: &ARGS }.render().unwrap()); + } } } - if found { - // increment read count - pastas[index].read_count += 1; + HttpResponse::Ok() + .content_type("text/html") + .body(ErrorTemplate { 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; +#[get("/raw/{id}")] +pub async fn getrawpasta(data: web::Data, id: web::Path) -> String { + let mut pastas = data.pastas.lock().unwrap(); - // update last read time - pastas[index].last_read = timenow; + let id = to_u64(&*id.into_inner()).unwrap_or(0); - // save the updated read count - save_to_file(&pastas); + remove_expired(&mut pastas); - // send raw content of pasta - return pastas[index].content.to_owned(); + for pasta in pastas.iter() { + if pasta.id == id { + return pasta.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 index 0ff15e4..3e3dec4 100644 --- a/src/endpoints/pastalist.rs +++ b/src/endpoints/pastalist.rs @@ -13,16 +13,15 @@ struct PastaListTemplate<'a> { 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))) + .append_header(("Location", "/")) .finish(); } - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); remove_expired(&mut pastas); 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 index 1dbe997..7b95553 100644 --- a/src/endpoints/remove.rs +++ b/src/endpoints/remove.rs @@ -1,68 +1,52 @@ -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::animalnumbers::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))) + .append_header(("Location", "/")) .finish(); } - let mut pastas = data.pastas.lock().await; + let mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - CONVERTER.to_u64(&id.into_inner()).unwrap_or(0) - }; + let id = 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() + 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() - { + 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))) + .append_header(("Location", "/pastalist")) .finish(); } } remove_expired(&mut pastas); - HttpResponse::NotFound() + HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { - status_code: StatusCode::NOT_FOUND, - args: &ARGS - }.render().unwrap()) + .body(ErrorTemplate { args: &ARGS }.render().unwrap()) } diff --git a/src/endpoints/static_resources.rs b/src/endpoints/static_resources.rs index bb738f4..9a5318e 100644 --- a/src/endpoints/static_resources.rs +++ b/src/endpoints/static_resources.rs @@ -1,21 +1,23 @@ -use actix_web::{web, HttpResponse, Responder}; -use mime_guess::from_path; -use rust_embed::RustEmbed; +use actix_web::{get, web, HttpResponse}; +use askama::Template; +use std::marker::PhantomData; -#[derive(RustEmbed)] -#[folder = "templates/assets/"] -struct Asset; +#[derive(Template)] +#[template(path = "water.css", escape = "none")] +struct WaterCSS<'a> { + _marker: PhantomData<&'a ()>, +} -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"), +#[get("/static/{resource}")] +pub async fn static_resources(resource_id: web::Path) -> HttpResponse { + match resource_id.into_inner().as_str() { + "water.css" => HttpResponse::Ok().content_type("text/css").body( + WaterCSS { + _marker: Default::default(), + } + .render() + .unwrap(), + ), + _ => HttpResponse::NotFound().content_type("text/html").finish(), } } - -#[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..099a2a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ extern crate core; use crate::args::ARGS; use crate::endpoints::{ - create, edit, errors, info, pasta as pasta_endpoint, pastalist, qr, remove, static_resources, + create, edit, errors, help, pasta as pasta_endpoint, pastalist, remove, static_resources, }; use crate::pasta::Pasta; use crate::util::dbio; @@ -11,19 +11,18 @@ 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; pub mod args; pub mod pasta; pub mod util { - pub mod pasta_id_converter; + pub mod animalnumbers; pub mod auth; pub mod dbio; - pub mod hashids; pub mod misc; pub mod syntaxhighlighter; } @@ -32,10 +31,9 @@ pub mod endpoints { pub mod create; pub mod edit; pub mod errors; - pub mod info; + pub mod help; pub mod pasta; pub mod pastalist; - pub mod qr; pub mod remove; pub mod static_resources; } @@ -68,8 +66,8 @@ async fn main() -> std::io::Result<()> { match fs::create_dir_all("./pasta_data/public") { 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/public/: {:?}", error); + panic!("Couldn't create data directory ./pasta_data/public/: {:?}", error); } }; @@ -82,26 +80,13 @@ async fn main() -> std::io::Result<()> { .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(help::help) + .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)) diff --git a/src/pasta.rs b/src/pasta.rs index 38a215a..3a54070 100644 --- a/src/pasta.rs +++ b/src/pasta.rs @@ -3,11 +3,8 @@ 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::animalnumbers::to_animal_names; use crate::util::syntaxhighlighter::html_highlight; #[derive(Serialize, Deserialize, PartialEq, Eq)] @@ -30,12 +27,6 @@ impl PastaFile { 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" - } } #[derive(Serialize, Deserialize)] @@ -48,21 +39,12 @@ pub struct Pasta { pub editable: bool, 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 { @@ -91,58 +73,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) } @@ -150,13 +80,6 @@ impl Pasta { 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/util/animalnumbers.rs b/src/util/animalnumbers.rs new file mode 100644 index 0000000..29e5094 --- /dev/null +++ b/src/util/animalnumbers.rs @@ -0,0 +1,53 @@ +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(); + } + + 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) -> Result { + 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; + let animal_index = ANIMAL_NAMES.iter().position(|&r| r == animals[i]); + match animal_index { + None => return Err("Failed to convert animal name to u64!"), + Some(_) => { + result += (animal_index.unwrap() * ANIMAL_NAMES.len().pow(pow as u32)) as u64 + } + } + } + + Ok(result) +} diff --git a/src/util/dbio.rs b/src/util/dbio.rs index 7c6a5e8..12dff06 100644 --- a/src/util/dbio.rs +++ b/src/util/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) } } } @@ -46,10 +46,10 @@ pub fn load_from_file() -> io::Result> { 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/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 index 49d8bf8..d1f4693 100644 --- a/src/util/misc.rs +++ b/src/util/misc.rs @@ -1,8 +1,6 @@ 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}; @@ -18,16 +16,8 @@ pub fn remove_expired(pastas: &mut Vec) { } 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) - { + // expiration is `never` or not reached + if p.expiration == 0 || p.expiration > timenow { // keep true } else { @@ -55,10 +45,6 @@ pub fn remove_expired(pastas: &mut Vec) { 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(); 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 index bda6474..4b3f2a2 100644 --- a/src/util/syntaxhighlighter.rs +++ b/src/util/syntaxhighlighter.rs @@ -11,7 +11,7 @@ pub fn html_highlight(text: &str, extension: &str) -> String { let syntax = ps .find_syntax_by_extension(extension) - .or_else(|| Option::from(ps.find_syntax_plain_text())) + .or(Option::from(ps.find_syntax_plain_text())) .unwrap(); let mut h = HighlightLines::new(syntax, &ts.themes["InspiredGitHub"]); @@ -25,7 +25,7 @@ pub fn html_highlight(text: &str, extension: &str) -> String { let mut highlighted_content2: String = String::from(""); for line in highlighted_content.lines() { - highlighted_content2 += &*format!("{line}\n"); + highlighted_content2 += &*format!("{}\n", line); } // Rewrite colours to ones that are compatible with water.css and both light/dark modes @@ -33,5 +33,5 @@ pub fn html_highlight(text: &str, extension: &str) -> String { highlighted_content2 = highlighted_content2.replace("style=\"color:#183691;\"", "style=\"color:blue;\""); - highlighted_content2 + return highlighted_content2; } 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/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..d705401 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -1,18 +1,17 @@ + {% 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. - Let's keep the Web compact, accessible and humane! + MicroBin by Dániel Szabó. Fork me on GitHub! + Let's keep the Web compact, accessible and humane! {%- else %} - {{ args.footer_text.as_ref().unwrap() }} + {{ 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..37d2de0 100644 --- a/templates/header.html +++ b/templates/header.html @@ -1,65 +1,60 @@ - - {{ args.title }} + {% if args.title.as_ref().is_none() %} + MicroBin + {%- else %} + {{ args.title.as_ref().unwrap() }} + {%- endif %} - {% 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_logo %} + μ + {%- endif %} + + {% if args.title.as_ref().is_none() %} + MicroBin {%- else %} + {{ args.title.as_ref().unwrap() }} + {%- endif %} + - - {%- endif %} -
+New Pasta - {% if !args.hide_header %} +Pasta List - +Help - {% if !args.hide_logo %} - - {%- endif %} - {{ args.title }} - +
- New - - - List - - Info - -
- - {%- endif %} +{%- endif %} diff --git a/templates/help.html b/templates/help.html new file mode 100644 index 0000000..abef0ce --- /dev/null +++ b/templates/help.html @@ -0,0 +1,160 @@ +{% include "header.html" %} + + +

Welcome to MicroBin!

+

This page contains help regarding the installation, configuration and use of MicroBin. If you have questions that are not answered here, head to our GitHub repository.

+

1 Usage

+

What is a "pasta" anyway?

+

In microbin, a pasta can be:

+
    +
  • A text that you want to paste from one machine to another, eg. some code
  • +
  • A file that you want to share, eg. a video that is too large for Discord, a zip with a code project in it or an image
  • +
  • A URL redirect
  • +
+

When is MicroBin useful?

+

You can use MicroBin

+
    +
  • As a URL shortener/redirect service,
  • +
  • To send long texts to other people,
  • +
  • To send large files to other people,
  • +
  • To serve content on the web, eg. configuration files for testing, images, or any other file content using the Raw functionality,
  • +
  • To move files between your desktop and a server you access from the console,
  • +
  • As a "postbox" service where people can upload their files or texts, but they cannot see or remove what others sent you - just disable the pastalist page
  • +
  • To take notes! Simply create an editable pasta.
  • +
+

...and many other things, why not get creative?

+

Creating a Pasta

+

Navigate to the root of your server, for example https://microbin.myserver.com/. This should show you a form where you will at the very least see an expiration selector, a file attachment input, a content text field and a green save button. Depending on your configuration there miight also be a syntax highlight selector, an editable checkbox and a private ceckbox.

+

Use the expiration dropdown to choose how long you want your pasta to exist. When the selected time has expired, it will be removed from the server. The content can be any text, including plain text, code, html, even a URL. A URL is a special case, because when you open the pasta again, it will redirect you to that URL instead of showing it as a text. Entering content is optional, and so is the file attachment. If you want, you can even submit a pasta completely empty.

+

You will be redirected to the URL of the pasta, which will end with a few animal names. If you remember those animals, you can simply type them in on another machine and open your pasta elsewhere.

+

If you have editable pastas enabled and you check the editable checkbox, then later on there will be an option to change the text content of your pasta. Selecting the private checkbox will simply prevent your pasta to show up on the pasta list page, if that is enabled.

+

If you have syntax higlighting enabled, then select your language from the dropdown, or leave it as none if you just want to upload plain with no highlighting.

+

Listing Pastas

+

If you have pasta listing enabled, then there is a pasta list option in the navigation bar, which will list all the pastas on the server in two groups: regular pastas and URL redirects (pastas containing nothing but a URL). If you have private pastas enabled, they will not show up here at all.

+

From the pasta list page, you will be able to view individual pastas by clicking on their animal identifiers on the lest, view their raw contrent by clicking on the Raw button, remove them, and if you have editable pastas enabled, then open them in edit view.

+

Use MicroBin from the console 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).

+

2 Installation

+

From Cargo

+

Install from Cargo:

+

cargo install microbin

+

Remember, MicroBin will create your database and file storage wherever you execute it. I recommend that you create a folder for it first and execute it there:

+

mkdir ~/microbin/

+

cd ~/microbin/

+

microbin --port 8080 --highlightsyntax --editable

+

Building MicroBin

+

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.

+
git clone https://github.com/szabodanika/microbin.git
+cd microbin
+cargo build --release
+./target/release/microbin -p 80
+

MicroBin as a service

+

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. If you installed MicroBin from Cargo, your executable will be in your system's Cargo directory, e.g. /Users/daniel/.cargo/bin/microbin.

+
[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
+

Here is my microbin.service for example, with some optional arguments:

+
[Unit]
+Description=MicroBin
+After=network.target
+
+[Service]
+Type=simple
+Restart=always
+User=ubuntu
+RootDirectory=/
+
+# This is the directory where I want to run microbin. It will store all the pastas here.
+WorkingDirectory=/home/ubuntu/server/microbin
+
+# This is the location of my executable - I also have 2 optional features enabled
+ExecStart=/home/ubuntu/server/microbin/target/release/microbin --editable --linenumbers --highlightsyntax
+
+# I keep my installation in the home directory, so I need to add this
+ProtectHome=off
+
+[Install]
+WantedBy=multi-user.target
+

Then start the service with systemctl start microbin and enable it on boot with systemctl enable microbin. To update your MicroBin, simply update or clone the repository again, build it again, and then restart the service with systemctl restart microbin. An update will never affect your existing pastas, unless there is a breaking change in the data model (in which case MicroBin just won't be able to import your DB), which will always be mentioned explicitly.

+

NGINX configuration

+
server {
+    # I have HTTPS enabled using certbot - you can use HTTP of course if you want!
+  listen 443 ssl; # managed by Certbot
+
+    server_name    microbin.myserver.com;
+
+    location / {
+            # Make sure to change the port if you are not running MicroBin at 8080!
+        proxy_pass            http://127.0.0.1:8080$request_uri;
+        proxy_set_header    Host $host;
+        proxy_set_header    X-Forwarded-Proto $scheme;
+        proxy_set_header    X-Real-IP $remote_addr;
+        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
+    }
+
+    # Limit content size - I have 1GB because my MicroBin server is private, no one else will use it.
+    client_max_body_size 1024M;
+
+  ssl_certificate /etc/letsencrypt/live/microbin.myserver.com/fullchain.pem; # managed by Certbot
+  ssl_certificate_key /etc/letsencrypt/live/microbin.myserver.com/privkey.pem; # managed by Certbot
+  include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+}
+

3 Command Line Arguments

+

There is an ever expanding list of customisations built into MicroBin so you can use it the way you want. Instead of a configuration file, we simply use arguments that you pass to the executable, making the workflow even simpler. Read the following options and if you cannot find what you need, you can always open an issue at our GitHub repository and request a new feature!

+

--auth-username [AUTH_USERNAME]

+

Require username for HTTP Basic Authentication when visiting the service. If --auth-username is set but --auth-password is not, just leave the password field empty when logging in. You can also just go to https://username:password@yourserver.net or https://username@yourserver.net if password is not set instead of typing into the password

+

--auth-password [AUTH_PASSWORD]

+

Require password for HTTP Basic Authentication when visiting the service. Will not have any affect unless --auth-username is also set. If --auth-username is set but --auth-password is not, just leave the password field empty when logging in. You can also just go to https://username:password@yourserver.net or https://username@yourserver.net if password is not set instead of typing into the password prompt.

+

--editable

+

Enables editable pastas. You will still be able to make finalised pastas but there will be an extra checkbox to make your new pasta editable from the pasta list or the pasta view page.

+ +

Replaces the default footer text with your own. If you want to hide the footer, use --hide-footer instead.

+

-h, --help

+

Show all commands in the terminal.

+ +

Hides the footer on every page.

+

--hide-header

+

Hides the navigation bar on every page.

+ +

Hides the MicroBin logo from the navigation bar on every page.

+

--no-listing

+

Disables the /pastalist endpoint, essentially making all pastas private.

+

--highlightsyntax

+

Enables syntax highlighting support. When creating a new pasta, a new dropdown selector will be added where you can select your pasta's syntax, or just leave it empty for no highlighting.

+

-p, --port [PORT]

+

Default value: 8080

+

Sets the port for the server will be listening on.

+

--private

+

Enables private pastas. Adds a new checkbox to make your pasta private, which then won't show up on the pastalist page. With the URL to your pasta, it will still be accessible.

+

--pure-html

+

Disables main CSS styling, just uses a few in-line stylings for the layout. With this option you will lose dark-mode support.

+

--readonly

+

Disables adding/editing/removing pastas entirely.

+

--title [TITLE]

+

Replaces "MicroBin" with your title of choice in the navigation bar.

+

-t, --threads [THREADS]

+

Default value: 1

+

Number of workers MicroBin is allowed to have. Increase this to the number of CPU cores you have if you want to go beast mode, but for personal use one worker is enough.

+

-V, --version

+

Displays your MicroBin's version information.

+

--wide

+

Changes the maximum width of the UI from 720 pixels to 1080 pixels.

+ +{% include "footer.html" %} diff --git a/templates/index.html b/templates/index.html index cdae99f..e1c4ef7 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,115 +1,26 @@ {% include "header.html" %} - -
+
-
+

- {% if args.enable_burn_after %} -
-
- -
- {%- endif %} - {% if args.highlightsyntax %}
-
+
- -
- {%- endif %} - {% if args.private %} -
- - -
- {%- endif %} + +
+
- -
- {% if !args.no_file_upload %} -
- -
- +
+ +
+
+ {% if args.editable %} +
+ + +
+ {%- endif %} + {% if args.private %} +
+ +
- {% endif %} - {% if args.readonly %} - - - - - {%- else %} - - - - {%- 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..879c948 100644 --- a/templates/pasta.html +++ b/templates/pasta.html @@ -1,149 +1,53 @@ {% 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 + Raw Text Content + {% if pasta.file.is_some() %} + + Attached file'{{pasta.file.as_ref().unwrap().name()}}' [{{pasta.file.as_ref().unwrap().size}}] + {%- endif %} {% if pasta.editable %} - Edit + Edit {%- endif %} - Remove + Remove
- {{pasta.id_as_animals()}} - {% if args.public_path.to_string() != "" %} - - {%- endif %} + {{pasta.id_as_animals()}}
-{% if pasta.file.is_some() %}
-
-{% if pasta.file.as_ref().unwrap().is_image() %} - -
-{%- 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()}}

+
+ {% if args.highlightsyntax %} +
{{pasta.content_syntax_highlighted()}}
{%- else %} -

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

+
{{pasta.content_not_highlighted()}}
{%- endif %} -
- -
- - - - {% include "footer.html" %} diff --git a/templates/pastalist.html b/templates/pastalist.html index ec231f2..4de2d2b 100644 --- a/templates/pastalist.html +++ b/templates/pastalist.html @@ -4,117 +4,112 @@ {% 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 %} - - - - - - +
- 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 -
+{% else %} +
+ {% endif %} + + + + + + + + + + + + + {% for pasta in pastas %} + {% if pasta.pasta_type == "text" && !pasta.private %} + + + + + -
Pastas
+ Key + + Created + + Expiration + + +
+ {{pasta.id_as_animals()}} + + {{pasta.created_as_string()}} + + {{pasta.expiration_as_string()}} + + Raw + {% if pasta.file.is_some() %} + File {%- endif %} - {% endfor %} -
-
-

URL Redirects

- {% if args.pure_html %} - - {% else %} -
- {% endif %} - - - - - - - {% for pasta in pastas %} - {% if pasta.pasta_type == "url" && !pasta.private %} - - - - - - + {% if pasta.editable %} + Edit {%- 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 -
-
- {%- endif %} + Remove + + + {%- endif %} + {% endfor %} + + +
+{% if args.pure_html %} + +{% else %} +
+{% endif %} + + + + + + + + + + + + {% for pasta in pastas %} + {% if pasta.pasta_type == "url" && !pasta.private %} + + + + + + + {%- endif %} + {% endfor %} + +
URL Redirects
+ Key + + Created + + Expiration + - - - {% include "footer.html" %} +
+ {{pasta.id_as_animals()}} + + {{pasta.created_as_string()}} + + {{pasta.expiration_as_string()}} + + Raw + {% if pasta.editable %} + Edit + {%- endif %} + 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 diff --git a/templates/water.css b/templates/water.css new file mode 100644 index 0000000..bc7ec46 --- /dev/null +++ b/templates/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}}