Merge branch 'upreb'

This commit is contained in:
Daniel 2023-05-17 08:11:57 -04:00
commit e042821848
19 changed files with 2264 additions and 334 deletions

View file

@ -1,8 +1,8 @@
# Directories
/.git/
/.github/
/target/
/upload/
/shuttle/
# Files
.gitignore

1
.github/FUNDING.yml vendored
View file

@ -1,2 +1,3 @@
github: orhun
patreon: orhunp
custom: ["https://www.buymeacoffee.com/orhun"]

15
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,15 @@
version: 2
updates:
# Maintain dependencies for Cargo
- package-ecosystem: cargo
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
# Maintain dependencies for GitHub Actions
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10

View file

@ -7,10 +7,10 @@ on:
jobs:
audit:
name: Audit
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout the repository
uses: actions/checkout@master
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:

View file

@ -8,13 +8,13 @@ on:
jobs:
publish-github:
name: Publish on GitHub
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
TARGET: [x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl]
steps:
- name: Checkout the repository
uses: actions/checkout@master
uses: actions/checkout@v3
- name: Set the release version
run: echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV
- name: Install musl-tools
@ -57,10 +57,10 @@ jobs:
publish-crates-io:
name: Publish on crates.io
needs: publish-github
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout the repository
uses: actions/checkout@master
uses: actions/checkout@v3
- name: Publish
uses: actions-rs/cargo@v1
with:

View file

@ -8,12 +8,12 @@ on:
branches:
- master
schedule:
- cron: '0 0 * * 0'
- cron: "0 0 * * 0"
jobs:
check:
name: Check
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
@ -22,7 +22,7 @@ jobs:
profile: minimal
override: true
- name: Checkout the repository
uses: actions/checkout@master
uses: actions/checkout@v3
- name: Check the project files
uses: actions-rs/cargo@v1
with:
@ -31,7 +31,7 @@ jobs:
test:
name: Test suite
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
@ -39,7 +39,7 @@ jobs:
toolchain: stable
override: true
- name: Checkout the repository
uses: actions/checkout@master
uses: actions/checkout@v3
- name: Setup cargo-tarpaulin
run: |
curl -s https://api.github.com/repos/xd009642/tarpaulin/releases/latest | \
@ -49,7 +49,7 @@ jobs:
- name: Run tests
run: cargo tarpaulin --out Xml --verbose -- --test-threads 1
- name: Upload reports to codecov
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v3
with:
name: code-coverage-report
file: cobertura.xml
@ -60,7 +60,7 @@ jobs:
fixtures:
name: Test fixtures
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
@ -69,7 +69,7 @@ jobs:
profile: minimal
override: true
- name: Checkout the repository
uses: actions/checkout@master
uses: actions/checkout@v3
- name: Build the project
uses: actions-rs/cargo@v1
with:
@ -84,7 +84,7 @@ jobs:
clippy:
name: Lints
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
@ -94,7 +94,7 @@ jobs:
components: clippy
override: true
- name: Checkout the repository
uses: actions/checkout@master
uses: actions/checkout@v3
- name: Check the lints
uses: actions-rs/cargo@v1
with:
@ -103,7 +103,7 @@ jobs:
rustfmt:
name: Formatting
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
@ -113,7 +113,7 @@ jobs:
components: rustfmt
override: true
- name: Checkout the repository
uses: actions/checkout@master
uses: actions/checkout@v3
- name: Check the formatting
uses: actions-rs/cargo@v1
with:
@ -122,10 +122,10 @@ jobs:
lychee:
name: Links
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout the repository
uses: actions/checkout@master
uses: actions/checkout@v3
- name: Check the links
uses: lycheeverse/lychee-action@v1
with:

View file

@ -15,14 +15,14 @@ on:
jobs:
docker:
name: Docker Build and Push
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
uses: docker/metadata-action@v4
with:
images: |
orhunp/rustypaste
@ -37,10 +37,10 @@ jobs:
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: Cache Docker layers
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
@ -49,14 +49,14 @@ jobs:
- name: Login to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: orhunp
password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@ -64,16 +64,24 @@ jobs:
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
uses: docker/build-push-action@v4
with:
context: ./
file: ./Dockerfile
platforms: linux/amd64
builder: ${{ steps.buildx.outputs.name }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
sbom: true
provenance: true
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
- name: Scan the image
uses: anchore/sbom-action@v0
with:
image: ghcr.io/${{ github.repository_owner }}/rustypaste/rustypaste
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

33
.github/workflows/shuttle.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: Deploy on Shuttle
on:
push:
tags:
- "v*.*.*"
jobs:
deploy:
name: Deploy
runs-on: ubuntu-22.04
steps:
- name: Checkout the repository
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Install cargo-binstall
uses: taiki-e/install-action@cargo-binstall
- name: Install cargo-shuttle
run: cargo binstall -y cargo-shuttle
- name: Prepare for deployment
shell: bash
run: sed -i 's|default = \["rustls"\]|default = \["rustls", "shuttle"\]|g' Cargo.toml
- name: Login
run: cargo shuttle login --api-key ${{ secrets.SHUTTLE_TOKEN }}
- name: Restart
run: cargo shuttle project restart
- name: Deploy
run: cargo shuttle deploy --allow-dirty --no-test

View file

@ -1,9 +1,58 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.9.0] - 2023-05-17
The public instance is now available at [https://rustypaste.shuttleapp.rs](https://rustypaste.shuttleapp.rs) 🚀
Read the blog post about `rustypaste` and Shuttle deployments: [https://blog.orhun.dev/blazingly-fast-file-sharing](https://blog.orhun.dev/blazingly-fast-file-sharing)
### Added
- Deploy on Shuttle.rs
- Support setting a default expiry time
You can now specify a expiry time for uploaded files. For example, if you want all the files to expire after one hour:
```toml
[paste]
default_expiry = "1h"
```
- Support overriding the server URL
If you are using `rustypaste` with a redirect or reverse proxy, it is now possible to set a different URL for the returned results:
```toml
[server]
url = "https://rustypaste.shuttleapp.rs"
```
- Add instructions for installing on Alpine Linux
`rustypaste` is now available in [testing](https://pkgs.alpinelinux.org/packages?name=rustypaste&branch=edge) repositories.
- Add new crate features
- `shuttle`: enable an entry point for deploying on Shuttle
- `openssl`: use distro OpenSSL (binary size is reduced ~20% in release mode)
- `rustls`: use [rustls](https://github.com/rustls/rustls) (enabled as default)
### Changed
- Make the default landing page fancier
- Generate SBOM attestation for the Docker image
### Updated
- Bump dependencies
- Update the funding options
- Consider donating if you liked `rustypaste`: [https://donate.orhun.dev](https://donate.orhun.dev) 💖
## [0.8.4] - 2023-01-31
### Added
@ -13,18 +62,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- This is useful when e.g. you want to be able to share the link to a file that would play in the browser (like `.mp4`) but also share a link that will auto-download as well.
## [0.8.3] - 2023-01-30
### Updated
- Bump dependencies
- Switch to [Rust](https://hub.docker.com/_/rust) image for the Dockerfile
- Remove unused `clap` dependency
## [0.8.2] - 2022-10-04
### Updated
- Don't expose version endpoint in default config
- Set `expose_version` to `false` in the configuration file
## [0.8.1] - 2022-10-04
### Added
- Add `<server_address>/version` endpoint for retrieving the server version
```toml
@ -35,11 +90,14 @@ expose_version=true
If `expose_version` entry is not present in the configuration file, `/version` is not exposed. It is recommended to use this feature with authorization enabled.
### Fixed
- Replace unmaintained `dotenv` crate with `dotenvy`
- Fixes [RUSTSEC-2021-0141](https://rustsec.org/advisories/RUSTSEC-2021-0141.html)
## [0.8.0] - 2022-10-03
### Added
- Support adding a landing page
You can now specify a landing page text in the configuration file as follows:
@ -56,6 +114,7 @@ welcome!
If the landing page entry is not present in the configuration file, visiting the index page will redirect to the repository.
### Updated
- Do not check for duplicate files by default
- Set `duplicate_files` to `true` in the configuration file
- It is an expensive operation to do on slower hardware and can take an unreasonable amount of time for bigger files
@ -63,13 +122,17 @@ If the landing page entry is not present in the configuration file, visiting the
- Consider supporting me for my open-source work 💖
## [0.7.1] - 2022-05-21
### Added
- Aggressively test everything
- Add the missing unit tests for the server endpoints (code coverage is increased to 84%)
- Create a custom testing framework (written in Bash) for adding [test fixtures](https://github.com/orhun/rustypaste/tree/master/fixtures)
## [0.7.0] - 2022-03-26
### Added
- Support auto-deletion of expired files
`rustypaste` can now delete the expired files by itself. To enable this feature, add the following line to the `[paste]` section in the configuration file:
@ -86,26 +149,33 @@ For users who want to have this feature disabled, there is an alternative [shell
- For the installation and usage, see the Arch Linux [PKGBUILD](https://github.com/archlinux/svntogit-community/blob/packages/rustypaste/trunk/PKGBUILD).
### Updated
- Upgrade Actix dependencies
- `actix-web` is updated to [`4.0.*`](https://github.com/actix/actix-web/blob/master/actix-web/CHANGES.md#401---2022-02-25)
- Strip the binaries during automated builds
- Size of the Docker image is reduced by ~20%
### Fixed
- Prevent invalid attempts of serving directories
- This fixes an issue where requesting a directory was possible via e.g. `curl --path-as-is 0.0.0.0:8080/.`
- This issue had no security impact (path traversal wasn't possible) since internal server error was returned.
## [0.6.5] - 2022-03-13
### Added
- Add instructions for installing [rustypaste](https://archlinux.org/packages/community/x86_64/rustypaste/) on Arch Linux
- `pacman -S rustypaste` 🎉
### Fixed
- Fix a bug where the use of `CONFIG` environment variable causes a conflict between the configuration file path and `[config]` section
## [0.6.4] - 2022-03-11
### Added
- Support setting the refresh rate for hot-reloading the configuration file.
```toml
@ -121,11 +191,14 @@ timeout="30s"
```
### Security
- Bump [regex crate](https://github.com/rust-lang/regex) to **1.5.5**
- Fixes [CVE-2022-24713](https://github.com/advisories/GHSA-m5pq-gvj9-9vr8)
## [0.6.3] - 2022-02-24
### Added
- Support setting the authentication token in the configuration file.
- This is an alternative (but not recommended) way of setting up authentication when the use of `AUTH_TOKEN` environment variable is not applicable.
@ -135,28 +208,37 @@ auth_token="hunter2"
```
## [0.6.2] - 2021-12-05
### Updated
- Improve the concurrency
- Shrink the scope of non-suspendable types (`#[must_not_suspend]`) for dropping them before reaching a suspend point (`.await` call). This avoids possible deadlocks, delays, and situations where `Future`s not implementing `Send`.
- Reference: https://rust-lang.github.io/rfcs/3014-must-not-suspend-lint.html
## [0.6.1] - 2021-11-16
### Fixed
- Gracefully handle the hot-reloading errors.
- Errors that may occur while locking the [Mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html) are handled properly hence a single configuration change cannot take down the whole service due to [poisoning](https://doc.rust-lang.org/std/sync/struct.Mutex.html#poisoning).
## [0.6.0] - 2021-11-07
### Added
- Support pasting files from remote URLs (via `remote=` form field)
- `{server.max_content_length}` is used for download limit
- See [README.md#paste-file-from-remote-url](https://github.com/orhun/rustypaste#paste-file-from-remote-url)
- Hot reload configuration file to apply configuration changes instantly without restarting the server
### Changed
- Library: Switch to Rust 2021 edition
### Security
- Prevent serving an already expired file
In the previous versions, it was possible to view an expired file by using the correct extension (timestamp). e.g. `paste.com/expired_file.txt.1630094518049` will serve the file normally although `paste.com/expired_file.txt` says that it is expired. This version fixes this vulnerability by regex-checking the requested file's extension.
@ -164,7 +246,9 @@ In the previous versions, it was possible to view an expired file by using the c
reference: [f078a9afa74f8608ee3f2a6e705159df15915c78](https://github.com/orhun/rustypaste/commit/f078a9afa74f8608ee3f2a6e705159df15915c78)
## [0.5.0] - 2021-10-12
### Added
- Added an entry in the configuration file to disable "duplicate uploads":
```toml
@ -176,13 +260,17 @@ duplicate_files = false
Under the hood, it checks the SHA256 digest of the uploaded files.
## [0.4.1] - 2021-09-19
### Changed
- Update README.md:
- Mention the new standalone tool: [rustypaste-cli](https://github.com/orhun/rustypaste-cli)
- Add [installation](https://github.com/orhun/rustypaste#installation) section.
## [0.4.0] - 2021-08-27
### Added
- Support [expiring links](README.md#expiration) (via `expire:` header)
- Timestamps are used as extension for expiring files
- Expired files can be cleaned up with [this command](README.md#cleaning-up-expired-files)
@ -190,28 +278,39 @@ Under the hood, it checks the SHA256 digest of the uploaded files.
- `{server.upload_path}/oneshot` is used for storage
## [0.3.1] - 2021-08-10
### Fixed
- Switch to [upload-release-action](https://github.com/svenstaro/upload-release-action) for uploading releases
## [0.3.0] - 2021-08-09
### Added
- Support overriding MIME types (config: `mime_override`)
- Support blacklisting MIME types (config: `mime_blacklist`)
## [0.2.0] - 2021-08-04
### Added
- Support shortening URLs (via `url=` form field)
- `{server.upload_path}/url` is used for storage
## [0.1.3] - 2021-07-28
### Fixed
- Prevent sending empty file name and zero bytes
- Prevent path traversal on upload directory ([#2](https://github.com/orhun/rustypaste/issues/2))
- Check the content length while reading bytes for preventing OOM ([#1](https://github.com/orhun/rustypaste/issues/1))
## [0.1.2] - 2021-07-27
### Changed
- Update Continuous Deployment workflow to publish Docker images
## [0.1.1] - 2021-07-27
Initial release.

2063
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "rustypaste"
version = "0.8.4"
version = "0.9.0"
edition = "2021"
description = "A minimal file upload/pastebin service"
authors = ["Orhun Parmaksız <orhunparmaksiz@gmail.com>"]
@ -12,28 +12,46 @@ keywords = ["paste", "pastebin", "upload"]
categories = ["web-programming::http-server"]
include = ["src/**/*", "Cargo.*", "LICENSE", "README.md", "CHANGELOG.md"]
[features]
default = ["rustls"]
openssl = ["actix-web/openssl", "awc/openssl"]
rustls = ["actix-web/rustls", "awc/rustls"]
shuttle = [
"dep:shuttle-actix-web",
"dep:shuttle-runtime",
"dep:shuttle-static-folder",
"dep:tokio",
]
[dependencies]
actix-web = { version = "4.3.0", features = ["rustls"] }
actix-multipart = "0.5.0"
actix-web = { version = "4.3.1" }
actix-multipart = "0.6.0"
actix-files = "0.6.2"
awc = { version = "3.1.0", features = ["rustls"] }
awc = { version = "3.1.1" }
env_logger = "0.10.0"
log = "0.4.17"
serde = "1.0.152"
futures-util = "0.3.26"
petname = { version = "1.1.3", default-features = false, features = ["std_rng", "default_dictionary"] }
serde = "1.0.163"
futures-util = "0.3.28"
petname = { version = "1.1.3", default-features = false, features = [
"std_rng",
"default_dictionary",
] }
rand = "0.8.5"
dotenvy = "0.15.6"
dotenvy = "0.15.7"
url = "2.3.1"
mime = "0.3.16"
regex = "1.6.0"
mime = "0.3.17"
regex = "1.8.1"
serde_regex = "1.1.0"
lazy-regex = "2.4.1"
lazy-regex = "2.5.0"
humantime = "2.1.0"
humantime-serde = "1.1.1"
glob = "0.3.1"
ring = "0.16.20"
hotwatch = "0.4.5"
shuttle-actix-web = { version = "0.16.0", optional = true }
shuttle-runtime = { version = "0.16.0", optional = true }
shuttle-static-folder = { version = "0.16.0", optional = true }
tokio = { version = "1.28.1", optional = true }
text-template = "0.1.0"
[dependencies.config]
@ -42,11 +60,11 @@ default-features = false
features = ["toml", "yaml"]
[dependencies.byte-unit]
version = "4.0.18"
version = "4.0.19"
features = ["serde"]
[dependencies.infer]
version = "0.12.0"
version = "0.13.0"
default-features = false
[dev-dependencies]

View file

@ -14,9 +14,7 @@ RUN cp target/release/rustypaste build-out/
FROM scratch
WORKDIR /app
COPY --from=builder \
/app/build-out/rustypaste \
/app/config.toml ./
COPY --from=builder /app/build-out/rustypaste .
ENV SERVER__ADDRESS=0.0.0.0:8000
EXPOSE 8000
USER 1000:1000

View file

@ -1,5 +1,13 @@
<a href="https://github.com/orhun/rustypaste"><img src="img/rustypaste_logo.png" width="500"></a>
[![GitHub Release](https://img.shields.io/github/v/release/orhun/rustypaste?style=flat&labelColor=823213&color=2c2c2c&logo=GitHub&logoColor=white)](https://github.com/orhun/rustypaste/releases)
[![Crate Release](https://img.shields.io/crates/v/rustypaste?style=flat&labelColor=823213&color=2c2c2c&logo=Rust&logoColor=white)](https://crates.io/crates/rustypaste/)
[![Coverage](https://img.shields.io/codecov/c/gh/orhun/rustypaste?style=flat&labelColor=823213&color=2c2c2c&logo=Codecov&logoColor=white)](https://codecov.io/gh/orhun/rustypaste)
[![Continuous Integration](https://img.shields.io/github/actions/workflow/status/orhun/rustypaste/ci.yml?branch=master&style=flat&labelColor=823213&color=2c2c2c&logo=GitHub%20Actions&logoColor=white)](https://github.com/orhun/rustypaste/actions?query=workflow%3A%22Continuous+Integration%22)
[![Continuous Deployment](https://img.shields.io/github/actions/workflow/status/orhun/rustypaste/cd.yml?style=flat&labelColor=823213&color=2c2c2c&logo=GitHub%20Actions&logoColor=white&label=deploy)](https://github.com/orhun/rustypaste/actions?query=workflow%3A%22Continuous+Deployment%22)
[![Docker Builds](https://img.shields.io/github/actions/workflow/status/orhun/rustypaste/docker.yml?style=flat&labelColor=823213&color=2c2c2c&label=docker&logo=Docker&logoColor=white)](https://hub.docker.com/r/orhunp/rustypaste)
[![Documentation](https://img.shields.io/docsrs/rustypaste?style=flat&labelColor=823213&color=2c2c2c&logo=Rust&logoColor=white)](https://docs.rs/rustypaste/)
**Rustypaste-pretty** is a minimal file upload/pastebin service with client side highlighting provided by highlight.js.
Just add `?pretty` to the end of a link to highlight!
@ -14,6 +22,8 @@ $ curl https://paste.site.com/safe-toad.txt
some text
```
The public instance is available at [https://rustypaste.shuttleapp.rs](https://rustypaste.shuttleapp.rs) 🚀
## Features
- File upload & URL shortening & upload from URL
@ -22,6 +32,7 @@ some text
- pet name (e.g. `capital-mosquito.txt`)
- alphanumeric string (e.g. `yB84D2Dv.txt`)
- supports expiring links
- auto-expiration of files (optional)
- auto-deletion of expired files (optional)
- supports one shot links (can only be viewed once)
- guesses MIME types
@ -55,9 +66,17 @@ cargo install rustypaste
pacman -S rustypaste
```
### Alpine Linux
`rustypaste` is available for [Alpine Edge](https://pkgs.alpinelinux.org/packages?name=rustypaste&branch=edge). It can be installed via [apk](https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper) after enabling the [testing repository](https://wiki.alpinelinux.org/wiki/Repositories).
```sh
apk add rustypaste
```
### Binary releases
See the available binaries on [releases](https://github.com/orhun/rustypaste/releases/) page.
See the available binaries on the [releases](https://github.com/orhun/rustypaste/releases/) page.
### Build from source
@ -67,13 +86,34 @@ cd rustypaste/
cargo build --release
```
#### Testing
#### Feature flags
- `shuttle`: enable an entry point for deploying on Shuttle
- `openssl`: use distro OpenSSL (binary size is reduced ~20% in release mode)
- `rustls`: use [rustls](https://github.com/rustls/rustls) (enabled as default)
To enable a feature for build, pass `--features` flag to `cargo build` command.
For example, to reuse the OpenSSL present on a distro already:
```sh
cargo build --release --no-default-features --features openssl
```
#### Testing
##### Unit tests
```sh
# run unit tests
cargo test -- --test-threads 1
```
##### Test Fixtures
```sh
./fixtures/test-fixtures.sh
```
## Usage
The standalone command line tool (`rpaste`) is available [here](https://github.com/orhun/rustypaste-cli).

View file

@ -1,50 +1,42 @@
[config]
refresh_rate="1s"
refresh_rate = "1s"
[server]
address="127.0.0.1:8020"
#url = "https://rustypaste.shuttleapp.rs"
#workers=4
max_content_length="10MB"
upload_path="./upload"
timeout="30s"
expose_version=false
max_content_length = "10MB"
upload_path = "./upload"
timeout = "30s"
expose_version = false
style="monokai"
landing_page="""Submit files via HTTP POST here:
curl -F 'file=@example.txt' <server>"
This will return the finished URL.
The server administrator might remove any pastes that they do not personally
want to host.
If you are the server administrator and want to change this page, just go
into your config file and change it! If you change the expiry time, it is
recommended that you do.
Check out the GitHub repository at https://github.com/orhun/rustypaste
By default, pastes expire every hour. The server admin may or may not have
changed this."""
landing_page = """
"""
[paste]
random_url = { enabled = true, type = "petname", words = 2, separator = "-" }
#random_url = { enabled = true, type = "alphanumeric", length = 8 }
default_extension = "txt"
mime_override = [
{ mime = "image/jpeg", regex = "^.*\\.jpg$" },
{ mime = "image/png", regex = "^.*\\.png$" },
{ mime = "image/svg+xml", regex = "^.*\\.svg$" },
{ mime = "video/webm", regex = "^.*\\.webm$" },
{ mime = "video/x-matroska", regex = "^.*\\.mkv$" },
{ mime = "application/octet-stream", regex = "^.*\\.bin$" },
{ mime = "text/plain", regex = "^.*\\.(log|txt|diff)$" },
{ mime = "image/jpeg", regex = "^.*\\.jpg$" },
{ mime = "image/png", regex = "^.*\\.png$" },
{ mime = "image/svg+xml", regex = "^.*\\.svg$" },
{ mime = "video/webm", regex = "^.*\\.webm$" },
{ mime = "video/x-matroska", regex = "^.*\\.mkv$" },
{ mime = "application/octet-stream", regex = "^.*\\.bin$" },
{ mime = "text/plain", regex = "^.*\\.(log|txt|diff|sh|rs|toml)$" },
]
mime_blacklist = [
"application/x-dosexec",
"application/java-archive",
"application/java-vm"
"application/x-dosexec",
"application/java-archive",
"application/java-vm",
]
duplicate_files = true
# default_expiry = "1h"
delete_expired_files = { enabled = true, interval = "1h" }
[paste.highlight_override]

53
shuttle/config.toml Normal file
View file

@ -0,0 +1,53 @@
[config]
refresh_rate = "1s"
[server]
address = "127.0.0.1:8000"
url = "https://rustypaste.shuttleapp.rs"
#workers=4
max_content_length = "10MB"
upload_path = "./upload"
timeout = "30s"
expose_version = true
landing_page = """
Submit files via HTTP POST here:
curl -F 'file=@example.txt' https://rustypaste.shuttleapp.rs
This will return the URL of the uploaded file.
Pastes expire every hour. Uploaded files might not be persistent.
Check out the GitHub repository: https://github.com/orhun/rustypaste
Command line tool is available : https://github.com/orhun/rustypaste-cli
If you liked this, consider supporting me: https://donate.orhun.dev <3
🦀
"""
[paste]
# random_url = { enabled = true, type = "petname", words = 2, separator = "-" }
random_url = { enabled = true, type = "alphanumeric", length = 6 }
default_extension = "txt"
mime_override = [
{ mime = "image/jpeg", regex = "^.*\\.jpg$" },
{ mime = "image/png", regex = "^.*\\.png$" },
{ mime = "image/svg+xml", regex = "^.*\\.svg$" },
{ mime = "video/webm", regex = "^.*\\.webm$" },
{ mime = "video/x-matroska", regex = "^.*\\.mkv$" },
{ mime = "application/octet-stream", regex = "^.*\\.bin$" },
{ mime = "text/plain", regex = "^.*\\.(log|txt|diff|sh|kt|rs|toml)$" },
]
mime_blacklist = [
"application/x-dosexec",
"application/java-archive",
"application/java-vm",
]
duplicate_files = true
default_expiry = "1h"
delete_expired_files = { enabled = true, interval = "1h" }

View file

@ -31,6 +31,8 @@ pub struct Settings {
pub struct ServerConfig {
/// The socket address to bind.
pub address: String,
/// URL that can be used to access the server externally.
pub url: Option<String>,
/// Number of workers to start.
pub workers: Option<usize>,
/// Maximum content length.
@ -63,8 +65,11 @@ pub struct PasteConfig {
/// Media type blacklist.
#[serde(default)]
pub mime_blacklist: Vec<String>,
/// Allow duplicate uploads
/// Allow duplicate uploads.
pub duplicate_files: Option<bool>,
/// Default expiry time.
#[serde(default, with = "humantime_serde")]
pub default_expiry: Option<Duration>,
/// Delete expired files.
pub delete_expired_files: Option<CleanupConfig>,
/// Highlight override.

View file

@ -1,19 +1,18 @@
use crate::util;
use actix_web::http::header::{
ContentDisposition as ActixContentDisposition, DispositionParam, DispositionType, HeaderMap,
};
use actix_web::{error, Error as ActixError};
use std::time::Duration;
/// Custom HTTP header for expiry dates.
pub const EXPIRE: &str = "expire";
/// Parses the expiry date from the [`custom HTTP header`](EXPIRE).
pub fn parse_expiry_date(headers: &HeaderMap) -> Result<Option<u128>, ActixError> {
pub fn parse_expiry_date(headers: &HeaderMap, time: Duration) -> Result<Option<u128>, ActixError> {
if let Some(expire_time) = headers.get(EXPIRE).and_then(|v| v.to_str().ok()) {
let timestamp = util::get_system_time()?;
let expire_time =
humantime::parse_duration(expire_time).map_err(error::ErrorInternalServerError)?;
Ok(timestamp.checked_add(expire_time).map(|t| t.as_millis()))
Ok(time.checked_add(expire_time).map(|t| t.as_millis()))
} else {
Ok(None)
}
@ -62,9 +61,9 @@ impl ContentDisposition {
#[cfg(test)]
mod tests {
use super::*;
use crate::util;
use actix_web::http::header::{HeaderName, HeaderValue};
use std::thread;
use std::time::Duration;
#[test]
fn test_content_disposition() -> Result<(), ActixError> {
@ -97,7 +96,8 @@ mod tests {
HeaderName::from_static(EXPIRE),
HeaderValue::from_static("5ms"),
);
let expiry_time = parse_expiry_date(&headers)?.unwrap_or_default();
let time = util::get_system_time()?;
let expiry_time = parse_expiry_date(&headers, time)?.unwrap_or_default();
assert!(expiry_time > util::get_system_time()?.as_millis());
thread::sleep(Duration::from_millis(10));
assert!(expiry_time < util::get_system_time()?.as_millis());

View file

@ -1,9 +1,10 @@
use actix_web::middleware::Logger;
use actix_web::web::Data;
#[cfg(not(feature = "shuttle"))]
use actix_web::{App, HttpServer};
use awc::ClientBuilder;
use hotwatch::{Event, Hotwatch};
use rustypaste::config::Config;
use rustypaste::config::{Config, ServerConfig};
use rustypaste::paste::PasteType;
use rustypaste::server;
use rustypaste::util;
@ -11,17 +12,28 @@ use rustypaste::CONFIG_ENV;
use std::env;
use std::fs;
use std::io::Result as IoResult;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::sync::{mpsc, RwLock};
use std::thread;
use std::time::Duration;
#[cfg(feature = "shuttle")]
use {
actix_web::web::{self, ServiceConfig},
shuttle_actix_web::ShuttleActixWeb,
};
#[actix_web::main]
async fn main() -> IoResult<()> {
/// Sets up the application.
///
/// * loads the configuration
/// * initializes the logger
/// * creates the necessary directories
/// * spawns the threads
fn setup(config_folder: &Path) -> IoResult<(Data<RwLock<Config>>, ServerConfig, Hotwatch)> {
// Load the .env file.
dotenvy::dotenv().ok();
// Initialize logger.
#[cfg(not(feature = "shuttle"))]
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
// Parse configuration.
@ -30,7 +42,7 @@ async fn main() -> IoResult<()> {
env::remove_var(CONFIG_ENV);
PathBuf::from(path)
}
None => PathBuf::from("config.toml"),
None => config_folder.join("config.toml"),
};
let config = Config::parse(&config_path).expect("failed to parse config");
log::trace!("{:#?}", config);
@ -83,6 +95,7 @@ async fn main() -> IoResult<()> {
.unwrap_or_else(|_| panic!("failed to watch {config_path:?}"));
// Create a thread for cleaning up expired files.
let upload_path = server_config.upload_path.clone();
thread::spawn(move || loop {
let mut enabled = false;
if let Some(ref cleanup_config) = paste_config
@ -92,7 +105,7 @@ async fn main() -> IoResult<()> {
{
if cleanup_config.enabled {
log::debug!("Running cleanup...");
for file in util::get_expired_files(&server_config.upload_path) {
for file in util::get_expired_files(&upload_path) {
match fs::remove_file(&file) {
Ok(()) => log::info!("Removed expired file: {:?}", file),
Err(e) => log::error!("Cannot remove expired file: {}", e),
@ -118,6 +131,15 @@ async fn main() -> IoResult<()> {
}
});
Ok((config, server_config, hotwatch))
}
#[cfg(not(feature = "shuttle"))]
#[actix_web::main]
async fn main() -> IoResult<()> {
// Set up the application.
let (config, server_config, _) = setup(&PathBuf::new())?;
// Create an HTTP server.
let mut http_server = HttpServer::new(move || {
let http_client = ClientBuilder::new()
@ -144,3 +166,33 @@ async fn main() -> IoResult<()> {
// Run the server.
http_server.run().await
}
#[cfg(feature = "shuttle")]
#[shuttle_runtime::main]
async fn actix_web(
#[shuttle_static_folder::StaticFolder(folder = "shuttle")] static_folder: PathBuf,
) -> ShuttleActixWeb<impl FnOnce(&mut ServiceConfig) + Send + Clone + 'static> {
// Set up the application.
let (config, server_config, _) = setup(&static_folder)?;
// Create the service.
let service_config = move |cfg: &mut ServiceConfig| {
let http_client = ClientBuilder::new()
.timeout(
server_config
.timeout
.unwrap_or_else(|| Duration::from_secs(30)),
)
.disable_redirects()
.finish();
cfg.service(
web::scope("")
.app_data(Data::clone(&config))
.app_data(Data::new(http_client))
.wrap(Logger::default())
.configure(server::configure_routes),
);
};
Ok(service_config.into())
}

View file

@ -28,7 +28,9 @@ async fn index(config: web::Data<RwLock<Config>>) -> Result<HttpResponse, Error>
.read()
.map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?;
match &config.server.landing_page {
Some(page) => Ok(HttpResponse::Ok().body(page.clone())),
Some(page) => Ok(HttpResponse::Ok()
.content_type("text/plain; charset=\"UTF-8\"")
.body(page.clone())),
None => Ok(HttpResponse::Found()
.append_header(("Location", env!("CARGO_PKG_HOMEPAGE")))
.finish()),
@ -158,6 +160,18 @@ async fn upload(
) -> Result<HttpResponse, Error> {
let connection = request.connection_info().clone();
let host = connection.peer_addr().unwrap_or("unknown host");
let server_url = match config
.read()
.map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?
.server
.url
.clone()
{
Some(v) => v,
None => {
format!("{}://{}", connection.scheme(), connection.host(),)
}
};
auth::check(
host,
request.headers(),
@ -169,7 +183,16 @@ async fn upload(
.as_ref()
.cloned()),
)?;
let expiry_date = header::parse_expiry_date(request.headers())?;
let time = util::get_system_time()?;
let mut expiry_date = header::parse_expiry_date(request.headers(), time)?;
if expiry_date.is_none() {
expiry_date = config
.read()
.map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?
.paste
.default_expiry
.and_then(|v| time.checked_add(v).map(|t| t.as_millis()));
}
let mut urls: Vec<String> = Vec::new();
while let Some(item) = payload.next().await {
let mut field = item?;
@ -208,9 +231,8 @@ async fn upload(
.get_file(bytes_checksum)
{
urls.push(format!(
"{}://{}/{}\n",
connection.scheme(),
connection.host(),
"{}/{}\n",
server_url,
file.path
.file_name()
.map(|v| v.to_string_lossy())
@ -248,12 +270,7 @@ async fn upload(
Byte::from_bytes(paste.data.len() as u128).get_appropriate_unit(false),
host
);
urls.push(format!(
"{}://{}/{}\n",
connection.scheme(),
connection.host(),
file_name
));
urls.push(format!("{}/{}\n", server_url, file_name));
} else {
log::warn!("{} sent an invalid form field", host);
return Err(error::ErrorBadRequest("invalid form field"));