68 Commits

Author SHA1 Message Date
844c608deb Remove Woodpecker configuration.
All checks were successful
Docker Image Creation / build-docker (push) Successful in 3m47s
Signed-off-by: Kellen Renshaw <kellen@bluequartz.xyz>
2023-11-20 07:20:59 -07:00
f1a2a30e30 Remove container image override, no longer needed.
All checks were successful
Docker Image Creation / build-docker (push) Successful in 3m50s
Signed-off-by: Kellen Renshaw <kellen@bluequartz.xyz>
2023-11-20 06:57:31 -07:00
186f6c217b Use fixed version to build.
All checks were successful
Docker Image Creation / build-docker (push) Successful in 3m47s
Signed-off-by: Kellen Renshaw <kellen@bluequartz.xyz>
2023-11-20 04:44:01 -07:00
0cd4560ee7 Merge remote-tracking branch 'upstream/master'
Pull in the latest commits from upstream.
2023-11-19 12:00:50 -07:00
503b97a2ed Switch to catthehacker image for Docker binary
All checks were successful
Docker Image Creation / build-docker (push) Successful in 8m22s
Signed-off-by: Kellen Renshaw <kellen@bluequartz.xyz>
2023-11-19 11:58:09 -07:00
bcf1eab953 Create Gitea workflow to build and push the Docker image.
Some checks failed
Docker Image Creation / build-docker (push) Failing after 47s
Signed-off-by: Kellen Renshaw <kellen@bluequartz.xyz>
2023-11-19 11:41:39 -07:00
alk3pInjection
8d02b31688 Revert "use static linking in Dockerfile"
The root cause of glibc version mismatch (#60) is we're trying to
build on bookworm and run on bullseye. The proper fix is simply
aligning the distro version during multi-stage builds.

While it's okay to statically link against musl libc, I don't see
any benefits in doing so, which _might_ also introduce performance
regressions.

Switch to smaller "distroless" image while we're at it.

This partially reverts commit dc6f9b5ec6.

Signed-off-by: alk3pInjection <webmaster@raspii.tech>
2023-10-22 12:19:12 +01:00
Alistair Bahr
dc6f9b5ec6 use static linking in Dockerfile; add docker-compose.yml 2023-10-17 16:56:01 +01:00
Jordan Doyle
793c2476af Add flake.nix 2022-12-04 20:27:07 +00:00
89fe9a15a0 Add registry config
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Signed-off-by: Kellen Renshaw <kellen@bluequartz.xyz>
2022-10-14 18:15:47 -07:00
ac93a7d379 Revert workarounds, Let's Encrypt certs in place.
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Signed-off-by: Kellen Renshaw <kellen@bluequartz.xyz>
2022-10-14 16:47:46 -07:00
dc04017e67 Fix /home/kellen issue
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Signed-off-by: Kellen Renshaw <kellen@bluequartz.xyz>
2022-10-13 20:25:08 -07:00
322b131550 Fix SSL issue in clone
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Signed-off-by: Kellen Renshaw <kellen@bluequartz.xyz>
2022-10-13 17:00:31 -07:00
316cad44d8 Tweak to CI
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Signed-off-by: Kellen Renshaw <kellen@bluequartz.xyz>
2022-10-12 22:07:55 -07:00
8353c82b63 Added Woodpecker CI config
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Signed-off-by: Kellen Renshaw <kellen@bluequartz.xyz>
2022-10-12 21:38:22 -07:00
dependabot[bot]
4fe099fd3e Bump bytes from 1.1.0 to 1.2.1
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.1.0 to 1.2.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.1.0...v1.2.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-09 21:12:56 +01:00
dependabot[bot]
cc0591f813 Bump once_cell from 1.10.0 to 1.14.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.10.0 to 1.14.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.10.0...v1.14.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-09 21:12:41 +01:00
dependabot[bot]
060c5e8733 Bump once_cell from 1.10.0 to 1.13.1
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.10.0 to 1.13.1.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.10.0...v1.13.1)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-09 21:05:30 +01:00
dependabot[bot]
a8c64ad8e8 Bump tokio from 1.17.0 to 1.20.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.17.0 to 1.20.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.17.0...tokio-1.20.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-09 21:05:06 +01:00
dependabot[bot]
c77be8f078 Bump log from 0.4.14 to 0.4.17
Bumps [log](https://github.com/rust-lang/log) from 0.4.14 to 0.4.17.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.14...0.4.17)

---
updated-dependencies:
- dependency-name: log
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-09 21:04:57 +01:00
dependabot[bot]
0902cdaa26 Bump parking_lot from 0.12.0 to 0.12.1
Bumps [parking_lot](https://github.com/Amanieu/parking_lot) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/Amanieu/parking_lot/releases)
- [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Amanieu/parking_lot/compare/0.12.0...0.12.1)

---
updated-dependencies:
- dependency-name: parking_lot
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-09 21:04:45 +01:00
DasLulilaan
c9c4a83ed4 fix newlines in browser display 2022-09-09 21:04:31 +01:00
Jordan Doyle
9fff1df6e6 Fix font difference between code block and textarea 2022-03-28 17:53:39 +01:00
Jordan Doyle
057ed640ee Create docker-publish.yml 2022-03-14 22:59:02 +00:00
Jordan Doyle
3defb20ad0 CI 2022-03-14 22:51:26 +00:00
Jordan Doyle
e6642a7a94 Merge branch 'actix' 2022-03-14 22:46:33 +00:00
Jordan Doyle
776bec1b30 Bump version 2022-03-14 22:45:28 +00:00
Jordan Doyle
e1639c032a Add bat for its extended syntax sets 2022-03-14 22:45:27 +00:00
Jordan Doyle
e8c3d1a5fc Bump dependencies 2022-03-14 22:45:27 +00:00
jordan@doyle.la
c686cba0c4 Run on port 8000 in docker 2020-07-28 01:23:09 +01:00
jordan@doyle.la
009dea438f Fix docker build for actix 2020-07-28 01:01:36 +01:00
jordan@doyle.la
2bbab69e94 cargo fmt 2020-07-23 15:28:55 +01:00
jordan@doyle.la
4eb9049a1a Bump version to v1.0.5 2020-07-23 15:18:22 +01:00
jordan@doyle.la
54f531a0a8 Fixes #16: New lines lost when only a \n is given 2020-07-23 15:15:57 +01:00
jordan@doyle.la
598baa2ace cargo update 2020-07-22 12:12:26 +01:00
Aaron
5b5c7dc2fb Update Rocket dependency branch to "master"
Signed-off-by: Aaron <admin@datahoarder.dev>
2020-07-19 21:06:31 +01:00
jordan@doyle.la
ebe0df5cc6 Upgrade rocket to latest version 2020-07-07 16:05:06 +01:00
Chris Martin
439f4bf2b4 Add viewport meta tag for better mobile interface 2020-06-13 11:15:41 +01:00
Shaarad Dalvi
377d4a129d Upated dockerfile to install libclang-dev
Build of `onig_sys` fails with missing dependency on clang.
2020-05-15 03:08:14 +01:00
jordan@doyle.la
9c53294b88 Move to async rocket 2020-04-13 11:47:50 +01:00
jordan@doyle.la
ccef0d9370 Move to actix-web 2020-04-13 11:47:26 +01:00
jordan@doyle.la
296cd50b7b Move to async rocket 2020-04-13 11:47:25 +01:00
Jordan Doyle
b095d2464c Fix build after updating dependencies & make scripts posix-compliant 2020-04-12 16:44:59 +01:00
Jordan Doyle
3c76d4fd1a Remove FreeBSD build as it currently fails 2020-04-12 16:44:57 +01:00
Jordan Doyle
06b90b8271 Do a cargo check instead of duplicating the build 2020-04-12 16:44:55 +01:00
Jordan Doyle
21b04b88ae Add rel="help" link to this repo (sorry! feel free to patch this out) 2020-04-12 16:44:50 +01:00
Jordan Doyle
53d97709fa Bump package versions 2020-04-12 16:44:37 +01:00
Jordan Doyle
ff3d193734 Update README.md 2020-04-12 15:42:37 +01:00
Ron Nazarov
2b53d0a34c Fixed "snippits" to "snippets" 2020-04-12 15:40:26 +01:00
Jordan Doyle
2502cc7f5c Update .travis.yml 2020-04-12 14:39:54 +01:00
Jordan Doyle
4bd924b423 Bump version to 1.0.3 2020-04-05 14:51:03 +01:00
j_dickert
be88df22f7 Add linenumbering
Fix rocket dependencies
2020-04-05 14:48:42 +01:00
Olgierd "Allgreed" Kasprowicz
275ace8356 Add Dockerfile 2019-05-23 19:47:45 +01:00
Jordan Johnson-Doyle
41900eaeee Fix deploy script on arm & windows builds 2019-02-21 18:12:43 +00:00
Jordan Doyle
1115bbc937 We're maturing like a fine cheese 2019-02-21 16:53:14 +00:00
Jordan Johnson-Doyle
5b7868080f Prevent cloning paste on show where not necessary 2019-02-21 16:41:39 +00:00
Jordan Johnson-Doyle
c2219e15f4 Avoid cloning generated id when submitting paste 2019-02-21 11:51:36 +00:00
Jordan Johnson-Doyle
26b21d7b39 Bump version to 1.0.2 2019-02-16 19:32:14 +00:00
Jordan Johnson-Doyle
fda03cfe21 Add config to Cargo.toml for crates.io 2019-02-16 19:25:39 +00:00
Jordan Johnson-Doyle
e466683062 Unnecessary fully qualified struct reference 2019-02-16 19:00:54 +00:00
Jordan Johnson-Doyle
2fad96945e Strip debug symbols on release builds from Travis 2019-02-16 18:56:18 +00:00
Jordan Johnson-Doyle
c3b886c196 Remove dependency on ring 2019-02-16 18:55:37 +00:00
Jordan Johnson-Doyle
caa8c3568d Remove unused dependencies, slightly optimise release binary size 2019-02-16 18:05:06 +00:00
Jordan Johnson-Doyle
22f5e257a9 Bump to 1.0.1 2019-02-16 11:51:14 +00:00
Jordan Johnson-Doyle
f675f75b3b Rename render -> show_paste 2019-02-16 11:51:14 +00:00
Jordan Johnson-Doyle
cd3d911ca0 Use overflow: auto instead of overflow: scroll 2019-02-16 11:51:13 +00:00
Jordan Doyle
4c9f1af0ab Update README.md 2019-02-16 11:04:23 +00:00
Jordan Doyle
1fe4db7420 Update README.md 2019-02-15 23:26:52 +00:00
29 changed files with 2616 additions and 839 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
target

View File

@@ -0,0 +1,33 @@
name: Docker Image Creation
run-name: ${{ gitea.actor }} building Docker image
on: [push]
jobs:
build-docker:
runs-on: ubuntu-22.04
steps:
- name: Checkout the repo
uses: actions/checkout@v4
- name: Login to Gitea Docker Registry
uses: docker/login-action@v3
with:
registry: git.bluequartz.xyz
username: ${{ gitea.actor }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: git.bluequartz.xyz/kellen/bin
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

12
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "monthly"

16
.github/workflows/audit.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: Security audit
on:
push:
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'
jobs:
security_audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

14
.github/workflows/audit_cron.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: Security audit (cron)
on:
schedule:
- cron: '0 0 * * *'
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

62
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
on: [push, pull_request]
name: CI
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: check
test:
name: Test Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: test
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
with:
command: clippy

93
.github/workflows/docker-publish.yml vendored Normal file
View File

@@ -0,0 +1,93 @@
name: Docker
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
schedule:
- cron: '45 20 * * *'
push:
branches: [ master ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ master ]
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@1e95c1de343b5b0c23352d6417ee3e48d5bcd422
with:
cosign-release: 'v1.4.0'
# Workaround: https://github.com/docker/build-push-action/issues/461
- name: Setup Docker buildx
uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
COSIGN_EXPERIMENTAL: "true"
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: cosign sign ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@
# These are backup files generated by rustfmt
**/*.rs.bk
result

View File

@@ -25,17 +25,15 @@ matrix:
os: osx
# *BSD
- env: TARGET=x86_64-unknown-freebsd DISABLE_TESTS=1
# - env: TARGET=x86_64-unknown-freebsd DISABLE_TESTS=1
- env: TARGET=x86_64-unknown-netbsd DISABLE_TESTS=1
# Windows
- env: TARGET=x86_64-pc-windows-gnu
allow_failures:
- env: TARGET=x86_64-unknown-linux-gnu
before_install:
- set -e
- command -v apt-get && sudo apt-get install -y libclang-dev
- rustup self update
install:
@@ -56,7 +54,7 @@ deploy:
# - Encrypt it: `travis encrypt 0123456789012345678901234567890123456789
# - Paste the output down here
api_key:
secure: "VRWAaqZi3ttaVfvgaSK0hQAIYDFUvPdOhhHxQESNO5qoexjc9wioDe7qfi2PcIKEMcEnXgVlmqKEyd0K4Cur9gWKDCfVPUEDTlph+Xdx+p5ViB+9Bz1z9qFSJchZtqqPGqfaq4Fk5HjBr6V31JFV0pjDfy2dHJzjQke+CGG8Zk/1n4H0dYqxZ4EyxcmuIxAnplyUDNdOrbwkuRKEGXLg94Ayk/OmLqHwvCJGYsa3jSgS2ySDT3nRaM9CG5WxKaEQMW4uEWJLbgmqFaxCWh1sOYt+brDWXc9aIJIFsR4b8wdHdHVL0C34dW/0xjxSQPN3PK3jKZYduiu49XgHtpKF9fvsFQSeQwMsAizucJtVKaJr1LfjKk3WwKPuiRotrPVwggdm9/zjXn1POerLR1TMNZr2fxI2iqkXA2sjpRJiDL7sBDg+glY09lGZazyCRIMszxuthoxjw38ZqwYzU4iM7mCkQQHFzVObZdanDtsCH6sM0FDfb7LmJ9uAdDHnzKwTKk23w0hEmdQJHkYimwCQR8Yp7jAhRyxNxYuWtdpuWP4WXDRmLypqMszNqFkZymvVFmMAm0hcSRxwkup4eUWY4yVuHch8/sl51zKXAFk35P2/gZyfoD7+yhiBY5j6dN4s6VBuePc9knRAUP3vWxdTjVg+PoQuWFEC5pHUvsUm/yk="
secure: "I1pMvhljeuqXkQDtaktLAcC0VFwlHZIXiM81f+FI+m8pWBV6eAy4alD9+tBSl808g8VIyv1nTbw/UGxVlKALqhR0iT9eCCxvVrgJuWKawJYuzrKxYnKf82t3RjTO1qh6Uf3LeCuZ+ReGbFeR4wTlxC9CQLwdBg3xUA+8bNNJigrEECmNjrUigmRdnKb1R1KXCNW9AwXMlVv+4I1me50/dFLdnhlaAjpsWFXKb2vAk/xoyob1WMEZbInrD/NX1kOz0UPiIX/K8Qjsgp6SAFxjhWGu9jk7VnzvEGPUmTS7OJ4tols2Lhde1AC0y/pElt3YFjA3qfKJfk4B8bUAizomeR+GwJ5YD/CEqhopa8b34SuxeHUOkX9GxPoba08qAmqPAeWcO2hlS0aBiiIUMIOgUqjGDy3MiMqg7VdalkiNtRsr2iCXXSe9p2FPWFsYW1bjGCbIYGBrmWZZEIVYJk2laSmIng27MBvlJFdDkX5Nf3d75Y5U/ZAUwfhpA8ZxwVbddKjYs1S8x09s5yhVOjxJzK2G0aJIg94wMflYEsygrZxjpMkOI79HMZrKArY2S6N6wqgKIdFM2kCmk0ps9DM9pn2o9h91EpnAi7PUd8RIB8yOh9g/K4K9ClvScUqagLR+DvjPoMh68F7Y5XnbuZkIgQ7fKB0cBtB+JnrIE3b3icI="
file_glob: true
file: $CRATE_NAME-$TRAVIS_TAG-$TARGET.*
on:

2379
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,39 @@
[package]
name = "bin"
version = "1.0.0"
version = "2.0.0"
description = "a paste bin."
repository = "https://github.com/w4/bin"
license = "WTFPL OR 0BSD"
authors = ["Jordan Doyle <jordan@doyle.la>"]
edition = "2018"
edition = "2021"
[dependencies]
argh = "0.1"
log = "0.4"
pretty_env_logger = "0.4"
linked-hash-map = "0.5"
askama = { git = "https://github.com/djc/askama", rev = "fc5addc4", features = ["with-rocket"] }
askama_escape = { git = "https://github.com/djc/askama", rev = "fc5addc4" }
rocket = "0.4"
lazy_static = "1.2"
rand = "0.6"
gpw = "0.1"
syntect = "3.0"
chashmap = { git = "https://github.com/redox-os/tfs", rev = "b3e7cae1" }
once_cell = "1.14"
parking_lot = "0.12"
bytes = { version = "1.2", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
rand = { version = "0.8" }
gpw = "0.1"
actix = "0.13"
actix-web = "4.0"
htmlescape = "0.3"
askama = "0.11"
bat = "0.20"
syntect = "4.6"
tokio = { version = "1.20", features = ["sync"] }
futures = "0.3"
[profile.release]
lto = true
codegen-units = 1
incremental = false
[badges]
travis-ci = { repository = "w4/bin" }
is-it-maintained-issue-resolution = { repository = "w4/bin" }
is-it-maintained-open-issues = { repository = "w4/bin" }
maintenance = { status = "passively-maintained" }

15
Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM rust:1-slim-bookworm AS builder
RUN apt update && apt install -y libclang-dev
COPY . /sources
WORKDIR /sources
RUN cargo build --release
RUN chown nobody:nogroup /sources/target/release/bin
FROM gcr.io/distroless/cc-debian12
COPY --from=builder /sources/target/release/bin /pastebin
USER nobody
EXPOSE 8000
ENTRYPOINT ["/pastebin", "0.0.0.0:8000"]

View File

@@ -1,32 +1,59 @@
# bin.
a pastebin.
# bin
a paste bin.
A paste bin that's actually minimalist. No database requirement, no commenting functionality, no self-destructing or time bomb messages and no social media integration—just an application to quickly send snippets of text to people.
[bin](https://bin.gy/) is written in Rust in around 300 lines of code. It's fast, it's simple, there's code highlighting and you can ⌘+A without going to the 'plain' page. It's revolutionary in the paste bin industry, disrupting markets and pushing boundaries never seen before.
There's no good open source pastebin solutions. I'm sorry to everyone who has one on GitHub but I have to say it. We try to cram as many little features as humanly possible into them and still try and call them minimalist. I don't want to run Redis, I don't want commenting functionality, I don't want self-destructing or time bomb messages and I especially don't want social media integration—I don't know about you but normally I just need to send a quick little snippit of code to someone, it doesn't need a title and I don't *really* mind when it disappears as long as its around long enough for them to see. Honestly, [I'm guilty of it myself](https://github.com/w4/hidden-note), we've all made a pastebin at one point or another but when it comes to making one to release to the public we create abominations.
##### so how do you get bin?
[bin.](https://bin.doyle.la/) is written in Rust in around 100 lines of code. It's fast, it's simple, there's code highlighting and you can ⌘+A without going to the 'plain' page. Revolutionary in the pastebin industry, disrupting markets and pushing boundaries never seen before.
##### curl support?
Download the latest version from the [releases](https://github.com/w4/bin/releases) page, extract it and run the `./bin` executable. You can also compile it from source using Cargo if you swing that way:
```bash
$ curl -X PUT --data 'hello world' bin.doyle.la
https://bin.doyle.la/cateettary
$ curl https://bin.doyle.la/cateettary
hello world
# nix-shell provides an environment with rust/cargo installed
$ nix-shell
[nix-shell:~/Code/bin]$ cargo build --release
Compiling bin v1.0.0 (/Users/jordanjd/Code/bin)
Finished release [optimized] target(s) in 3.61s
[nix-shell:~/Code/bin]$ ./target/release/bin
...
```
##### how do you run bin?
##### how do you run it?
```bash
$ ./bin
```
##### good one, what settings are there?
##### funny, what settings are there?
bin. uses [rocket](https://rocket.rs) so you can add a [rocket config file](https://api.rocket.rs/v0.3/rocket/config/) if you like. You can set `ROCKET_PORT` in your environment if you want to change the default port (8820).
```
$ ./bin
bin's only configuration value is `BIN_BUFFER_SIZE` which defaults to 2000. Change this value if you want your bin to hold more pastes.
Usage: bin [<bind_addr>] [--buffer-size <buffer-size>] [--max-paste-size <max-paste-size>]
a pastebin.
Positional Arguments:
bind_addr socket address to bind to (default: 127.0.0.1:8820)
Options:
--buffer-size maximum amount of pastes to store before rotating (default:
1000)
--max-paste-size maximum paste size in bytes (default. 32kB)
--help display usage information
```
##### is there curl support?
```bash
$ curl -X PUT --data 'hello world' https://bin.gy
https://bin.gy/cateettary
$ curl https://bin.gy/cateettary
hello world
```
##### how does syntax highlighting work?

View File

@@ -19,7 +19,8 @@ main() {
cross rustc --bin bin --target $TARGET --release -- -C lto
cp target/$TARGET/release/bin $stage/
[ "$TARGET" = "arm-unknown-linux-gnueabi" ] || [ "$TARGET" = "x86_64-pc-windows-gnu" ] || strip target/$TARGET/release/bin
cp target/$TARGET/release/* $stage/
cd $stage
tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *

View File

@@ -4,7 +4,7 @@ set -ex
# TODO This is the "test phase", tweak it as you see fit
main() {
cross build --target $TARGET
cross check
cross build --target $TARGET --release
if [ ! -z $DISABLE_TESTS ]; then
@@ -12,7 +12,6 @@ main() {
fi
cross test --target $TARGET
cross test --target $TARGET --release
}
# we don't run the "test phase" when doing deploys

8
docker-compose.yml Normal file
View File

@@ -0,0 +1,8 @@
version: '3'
services:
bin:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"

77
flake.lock generated Normal file
View File

@@ -0,0 +1,77 @@
{
"nodes": {
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1662220400,
"narHash": "sha256-9o2OGQqu4xyLZP9K6kNe1pTHnyPz0Wr3raGYnr9AIgY=",
"owner": "nix-community",
"repo": "naersk",
"rev": "6944160c19cb591eb85bbf9b2f2768a935623ed3",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "master",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1670118144,
"narHash": "sha256-tdh9H4oomljZaKpCkZox8jmwt8p78oGLpK9cjFBy3Qk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "95f1ec721652d91a2993311d6cf537d3724690be",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1670118144,
"narHash": "sha256-tdh9H4oomljZaKpCkZox8jmwt8p78oGLpK9cjFBy3Qk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "95f1ec721652d91a2993311d6cf537d3724690be",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"naersk": "naersk",
"nixpkgs": "nixpkgs_2",
"utils": "utils"
}
},
"utils": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

83
flake.nix Normal file
View File

@@ -0,0 +1,83 @@
{
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 ];
RUST_SRC_PATH = rustPlatform.rustLibSrc;
};
nixosModules.default = { config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.paste-bin;
in
{
options.services.paste-bin = {
enable = mkEnableOption "paste-bin";
bindAddress = mkOption {
default = "[::]:8000";
description = "Address and port to listen on";
type = types.str;
};
maxPasteSize = mkOption {
default = 32768;
description = "Max allowed size of an individual paste";
type = types.int;
};
bufferSize = mkOption {
default = 1000;
description = "Maximum amount of pastes to store at a time";
type = types.int;
};
};
config = mkIf cfg.enable {
systemd.services.bin = {
enable = true;
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
serviceConfig = {
Type = "exec";
ExecStart = "${self.defaultPackage."${system}"}/bin/bin --buffer-size ${toString cfg.bufferSize} --max-paste-size ${toString cfg.maxPasteSize} ${cfg.bindAddress}";
Restart = "on-failure";
CapabilityBoundingSet = "";
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
PrivateMounts = true;
ProtectHome = true;
ProtectClock = true;
ProtectProc = "noaccess";
ProcSubset = "pid";
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectHostname = true;
RestrictSUIDSGID = true;
RestrictRealtime = true;
RestrictNamespaces = true;
LockPersonality = true;
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
SystemCallFilter = [ "@system-service" "~@privileged" ];
};
};
};
};
});
}

View File

@@ -2,9 +2,9 @@ with import <nixpkgs> {};
let src = fetchFromGitHub {
owner = "mozilla";
repo = "nixpkgs-mozilla";
# commited 12/2/2019
rev = "37f7f33ae3ddd70506cd179d9718621b5686c48d";
sha256 = "0cmvc9fnr38j3n0m4yf0k6s2x589w1rdby1qry1vh435v79gp95j";
# commited 19/2/2020
rev = "e912ed483e980dfb4666ae0ed17845c4220e5e7c";
sha256 = "08fvzb8w80bkkabc1iyhzd15f4sm7ra10jn32kfch5klgl0gj3j3";
};
in
with import "${src.out}/rust-overlay.nix" pkgs pkgs;
@@ -14,6 +14,9 @@ stdenv.mkDerivation {
buildInputs = [
latest.rustChannels.nightly.cargo
latest.rustChannels.nightly.rust
stdenv.cc.libc
] ++
pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.Security;
LIBCLANG_PATH="${llvmPackages.libclang}/lib";
}

56
src/errors.rs Normal file
View File

@@ -0,0 +1,56 @@
use actix_web::{body::BoxBody, http::header, http::StatusCode, web, HttpResponse, ResponseError};
use std::fmt::{Formatter, Write};
macro_rules! impl_response_error_for_http_resp {
($ty:ty, $path:expr, $status:expr) => {
impl ResponseError for $ty {
fn error_response(&self) -> HttpResponse {
HtmlResponseError::error_response(self)
}
}
impl std::fmt::Display for $ty {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", include_str!($path))
}
}
impl HtmlResponseError for $ty {
fn status_code(&self) -> StatusCode {
$status
}
}
};
}
#[derive(Debug)]
pub struct NotFound;
impl_response_error_for_http_resp!(NotFound, "../templates/404.html", StatusCode::NOT_FOUND);
#[derive(Debug)]
pub struct InternalServerError(pub Box<dyn std::error::Error>);
impl_response_error_for_http_resp!(
InternalServerError,
"../templates/500.html",
StatusCode::INTERNAL_SERVER_ERROR
);
pub trait HtmlResponseError: ResponseError {
fn status_code(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
fn error_response(&self) -> HttpResponse {
let mut resp = HttpResponse::new(HtmlResponseError::status_code(self));
let mut buf = web::BytesMut::new();
let _ = write!(&mut buf, "{}", self);
resp.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/html; charset=utf-8"),
);
resp.set_body(BoxBody::new(buf))
}
}

View File

@@ -1,26 +1,45 @@
extern crate syntect;
use bat::assets::HighlightingAssets;
use once_cell::sync::Lazy;
use syntect::{
html::{ClassStyle, ClassedHTMLGenerator},
parsing::SyntaxSet,
};
use syntect::easy::HighlightLines;
use syntect::highlighting::ThemeSet;
use syntect::html::{styled_line_to_highlighted_html, IncludeBackground};
use syntect::parsing::SyntaxSet;
thread_local!(pub static BAT_ASSETS: HighlightingAssets = HighlightingAssets::from_binary());
/// Takes the content of a paste and the extension passed in by the viewer and will return the content
/// highlighted in the appropriate format in HTML.
///
/// Returns `None` if the extension isn't supported.
pub fn highlight(content: &str, ext: &str) -> Option<String> {
lazy_static! {
static ref SS: SyntaxSet = SyntaxSet::load_defaults_newlines();
static ref TS: ThemeSet = ThemeSet::load_defaults();
}
static SS: Lazy<SyntaxSet> = Lazy::new(SyntaxSet::load_defaults_newlines);
let syntax = SS.find_syntax_by_extension(ext)?;
let mut h = HighlightLines::new(syntax, &TS.themes["base16-ocean.dark"]);
let regions = h.highlight(content, &SS);
Some(styled_line_to_highlighted_html(
&regions[..],
IncludeBackground::No,
))
BAT_ASSETS.with(|f| {
let ss = f.get_syntax_set().ok().unwrap_or(&SS);
let syntax = ss.find_syntax_by_extension(ext)?;
let mut html_generator =
ClassedHTMLGenerator::new_with_class_style(syntax, ss, ClassStyle::Spaced);
for line in LinesWithEndings(content.trim()) {
html_generator.parse_html_for_line_which_includes_newline(line);
}
Some(html_generator.finalize())
})
}
pub struct LinesWithEndings<'a>(&'a str);
impl<'a> Iterator for LinesWithEndings<'a> {
type Item = &'a str;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.0.is_empty() {
None
} else {
let split = self.0.find('\n').map_or(self.0.len(), |i| i + 1);
let (line, rest) = self.0.split_at(split);
self.0 = rest;
Some(line)
}
}
}

View File

@@ -1,36 +1,21 @@
extern crate gpw;
extern crate linked_hash_map;
extern crate rand;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use actix_web::web::Bytes;
use linked_hash_map::LinkedHashMap;
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use std::cell::RefCell;
use std::env;
use std::sync::RwLock;
lazy_static! {
static ref ENTRIES: RwLock<LinkedHashMap<String, String>> = RwLock::new(LinkedHashMap::new());
static ref BUFFER_SIZE: usize = env::var("BIN_BUFFER_SIZE")
.map(|f| f
.parse::<usize>()
.expect("Failed to parse value of BIN_BUFFER_SIZE"))
.unwrap_or(1000usize);
}
pub type PasteStore = RwLock<LinkedHashMap<String, Bytes>>;
static BUFFER_SIZE: Lazy<usize> = Lazy::new(|| argh::from_env::<crate::BinArgs>().buffer_size);
/// Ensures `ENTRIES` is less than the size of `BIN_BUFFER_SIZE`. If it isn't then
/// `ENTRIES.len() - BIN_BUFFER_SIZE` elements will be popped off the front of the map.
///
/// During the purge, `ENTRIES` is locked and the current thread will block.
fn purge_old() {
let entries_len = ENTRIES.read().unwrap_or_else(|p| p.into_inner()).len();
if entries_len > *BUFFER_SIZE {
let to_remove = entries_len - *BUFFER_SIZE;
let mut entries = ENTRIES.write().unwrap_or_else(|p| p.into_inner());
fn purge_old(entries: &mut LinkedHashMap<String, Bytes>) {
if entries.len() > *BUFFER_SIZE {
let to_remove = entries.len() - *BUFFER_SIZE;
for _ in 0..to_remove {
entries.pop_front();
@@ -41,32 +26,29 @@ fn purge_old() {
/// Generates a 'pronounceable' random ID using gpw
pub fn generate_id() -> String {
thread_local!(static KEYGEN: RefCell<gpw::PasswordGenerator> = RefCell::new(gpw::PasswordGenerator::default()));
KEYGEN.with(|k| {
k.borrow_mut().next().unwrap_or_else(|| {
thread_rng()
.sample_iter(&Alphanumeric)
.take(6)
.collect::<String>()
})
KEYGEN.with(|k| k.borrow_mut().next()).unwrap_or_else(|| {
thread_rng()
.sample_iter(&Alphanumeric)
.take(6)
.map(char::from)
.collect()
})
}
/// Stores a paste under the given id
pub fn store_paste(id: String, content: String) {
purge_old();
ENTRIES
.write()
.unwrap_or_else(|p| p.into_inner())
.insert(id, content);
pub fn store_paste(entries: &PasteStore, id: String, content: Bytes) {
let mut entries = entries.write();
purge_old(&mut entries);
entries.insert(id, content);
}
/// Get a paste by id.
///
/// Returns `None` if the paste doesn't exist.
pub fn get_paste(id: &str) -> Option<String> {
ENTRIES
.read()
.unwrap_or_else(|p| p.into_inner())
.get(id)
.cloned()
pub fn get_paste(entries: &PasteStore, id: &str) -> Option<Bytes> {
// need to box the guard until owning_ref understands Pin is a stable address
entries.read().get(id).map(Bytes::clone)
}

View File

@@ -1,118 +1,193 @@
#![feature(proc_macro_hygiene, decl_macro)]
#![feature(type_alias_enum_variants)]
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate rocket;
extern crate askama;
extern crate askama_escape;
#![deny(clippy::pedantic)]
#![allow(clippy::unused_async)]
mod errors;
mod highlight;
mod io;
mod params;
use highlight::highlight;
use io::{generate_id, get_paste, store_paste};
use params::{HostHeader, IsPlaintextRequest};
use crate::{
errors::{InternalServerError, NotFound},
highlight::highlight,
io::{generate_id, get_paste, store_paste, PasteStore},
params::{HostHeader, IsPlaintextRequest},
};
use askama::Template;
use askama_escape::{Html, MarkupDisplay};
use actix_web::{
http::header,
web::{self, Bytes, Data, FormConfig, PayloadConfig},
App, Error, HttpRequest, HttpResponse, HttpServer, Responder,
};
use askama::{Html as AskamaHtml, MarkupDisplay, Template};
use log::{error, info};
use once_cell::sync::Lazy;
use std::{
borrow::Cow,
net::{IpAddr, Ipv4Addr, SocketAddr},
};
use syntect::html::{css_for_theme_with_class_style, ClassStyle};
use rocket::http::{ContentType, Status};
use rocket::request::Form;
use rocket::response::content::Content;
use rocket::response::Redirect;
use rocket::Data;
#[derive(argh::FromArgs, Clone)]
/// a pastebin.
pub struct BinArgs {
/// socket address to bind to (default: 127.0.0.1:8820)
#[argh(
positional,
default = "SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8820)"
)]
bind_addr: SocketAddr,
/// maximum amount of pastes to store before rotating (default: 1000)
#[argh(option, default = "1000")]
buffer_size: usize,
/// maximum paste size in bytes (default. 32kB)
#[argh(option, default = "32 * 1024")]
max_paste_size: usize,
}
use std::io::Read;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "INFO");
}
pretty_env_logger::init();
///
/// Homepage
///
let args: BinArgs = argh::from_env();
let store = Data::new(PasteStore::default());
let server = HttpServer::new({
let args = args.clone();
move || {
App::new()
.app_data(store.clone())
.app_data(PayloadConfig::default().limit(args.max_paste_size))
.app_data(FormConfig::default().limit(args.max_paste_size))
.wrap(actix_web::middleware::Compress::default())
.route("/", web::get().to(index))
.route("/", web::post().to(submit))
.route("/", web::put().to(submit_raw))
.route("/", web::head().to(HttpResponse::MethodNotAllowed))
.route("/highlight.css", web::get().to(highlight_css))
.route("/{paste}", web::get().to(show_paste))
.route("/{paste}", web::head().to(HttpResponse::MethodNotAllowed))
.default_service(web::to(|req: HttpRequest| async move {
error!("Couldn't find resource {}", req.uri());
HttpResponse::from_error(NotFound)
}))
}
});
info!("Listening on http://{}", args.bind_addr);
server.bind(args.bind_addr)?.run().await
}
#[derive(Template)]
#[template(path = "index.html")]
struct Index {}
struct Index;
#[get("/")]
fn index() -> Index {
Index {}
async fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
render_template(&req, &Index)
}
///
/// Submit Paste
///
#[derive(FromForm)]
#[derive(serde::Deserialize)]
struct IndexForm {
val: String,
val: Bytes,
}
#[post("/", data = "<input>")]
fn submit(input: Form<IndexForm>) -> Redirect {
async fn submit(input: web::Form<IndexForm>, store: Data<PasteStore>) -> impl Responder {
let id = generate_id();
store_paste(id.clone(), input.into_inner().val);
Redirect::to(uri!(render: id))
let uri = format!("/{}", &id);
store_paste(&store, id, input.into_inner().val);
HttpResponse::Found()
.append_header((header::LOCATION, uri))
.finish()
}
#[put("/", data = "<input>")]
fn submit_raw(input: Data, host: HostHeader) -> std::io::Result<String> {
let mut data = String::new();
input.open().take(1024 * 1000).read_to_string(&mut data)?;
async fn submit_raw(
data: Bytes,
host: HostHeader,
store: Data<PasteStore>,
) -> Result<String, Error> {
let id = generate_id();
store_paste(id.clone(), data);
let uri = if let Some(Ok(host)) = host.0.as_ref().map(|v| std::str::from_utf8(v.as_bytes())) {
format!("https://{}/{}", host, id)
} else {
format!("/{}", id)
};
match *host {
Some(host) => Ok(format!("https://{}/{}", host, id)),
None => Ok(id),
}
store_paste(&store, id, data);
Ok(uri)
}
///
/// Show paste page
///
#[derive(Template)]
#[template(path = "paste.html")]
struct Render {
content: MarkupDisplay<Html, String>,
struct ShowPaste<'a> {
content: MarkupDisplay<AskamaHtml, Cow<'a, String>>,
}
#[get("/<key>")]
fn render(key: String, plaintext: IsPlaintextRequest) -> Result<Content<String>, Status> {
async fn show_paste(
req: HttpRequest,
key: actix_web::web::Path<String>,
plaintext: IsPlaintextRequest,
store: Data<PasteStore>,
) -> Result<HttpResponse, Error> {
let mut splitter = key.splitn(2, '.');
let key = splitter.next().ok_or_else(|| Status::NotFound)?;
let key = splitter.next().unwrap();
let ext = splitter.next();
// get() returns a read-only lock, we're not going to be writing to this key
// again so we can hold this for as long as we want
let entry = get_paste(key).ok_or_else(|| Status::NotFound)?;
let entry = get_paste(&store, key).ok_or(NotFound)?;
if *plaintext {
Ok(Content(ContentType::Plain, entry))
Ok(HttpResponse::Ok()
.content_type("text/plain; charset=utf-8")
.body(entry))
} else {
let template = Render {
content: match ext {
None => MarkupDisplay::new_unsafe(entry, Html),
Some(extension) => highlight(&entry, extension)
.map(|h| MarkupDisplay::new_safe(h, Html))
.ok_or_else(|| Status::NotFound)?,
let data = std::str::from_utf8(entry.as_ref())?;
let code_highlighted = match ext {
Some(extension) => match highlight(data, extension) {
Some(html) => html,
None => return Err(NotFound.into()),
},
None => htmlescape::encode_minimal(data),
};
template
.render()
.map(|html| Content(ContentType::HTML, html))
.map_err(|_| Status::InternalServerError)
// Add <code> tags to enable line numbering with CSS
let html = format!(
"<code>{}</code>",
code_highlighted.replace('\n', "</code><code>")
);
let content = MarkupDisplay::new_safe(Cow::Borrowed(&html), AskamaHtml);
render_template(&req, &ShowPaste { content })
}
}
fn main() {
rocket::ignite()
.mount("/", routes![index, submit, submit_raw, render])
.launch();
async fn highlight_css() -> HttpResponse {
static CSS: Lazy<Bytes> = Lazy::new(|| {
highlight::BAT_ASSETS.with(|s| {
Bytes::from(css_for_theme_with_class_style(
s.get_theme("OneHalfDark"),
ClassStyle::Spaced,
))
})
});
HttpResponse::Ok()
.content_type("text/css")
.body(CSS.clone())
}
fn render_template<T: Template>(req: &HttpRequest, template: &T) -> Result<HttpResponse, Error> {
match template.render() {
Ok(html) => Ok(HttpResponse::Ok().content_type("text/html").body(html)),
Err(e) => {
error!("Error while rendering template for {}: {}", req.uri(), e);
Err(InternalServerError(Box::new(e)).into())
}
}
}

View File

@@ -1,7 +1,11 @@
use std::ops::Deref;
use rocket::request::{FromRequest, Outcome};
use rocket::Request;
use actix_web::{
dev::Payload,
http::header::{self, HeaderValue},
FromRequest, HttpMessage, HttpRequest,
};
use futures::future::ok;
/// Holds a value that determines whether or not this request wanted a plaintext response.
///
@@ -17,25 +21,22 @@ impl Deref for IsPlaintextRequest {
}
}
impl<'a, 'r> FromRequest<'a, 'r> for IsPlaintextRequest {
type Error = ();
impl FromRequest for IsPlaintextRequest {
type Error = actix_web::Error;
type Future = futures::future::Ready<Result<Self, Self::Error>>;
fn from_request(request: &'a Request<'r>) -> Outcome<IsPlaintextRequest, ()> {
if let Some(format) = request.format() {
if format.is_plain() {
return Outcome::Success(IsPlaintextRequest(true));
}
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
if req.content_type() == "text/plain" {
return ok(IsPlaintextRequest(true));
}
match request
match req
.headers()
.get_one("User-Agent")
.and_then(|u| u.splitn(2, '/').next())
.get(header::USER_AGENT)
.and_then(|u| u.to_str().unwrap().split('/').next())
{
None | Some("Wget") | Some("curl") | Some("HTTPie") => {
Outcome::Success(IsPlaintextRequest(true))
}
_ => Outcome::Success(IsPlaintextRequest(false)),
None | Some("Wget" | "curl" | "HTTPie") => ok(IsPlaintextRequest(true)),
_ => ok(IsPlaintextRequest(false)),
}
}
}
@@ -44,20 +45,13 @@ impl<'a, 'r> FromRequest<'a, 'r> for IsPlaintextRequest {
///
/// The inner value of this `HostHeader` will be `None` if there was no Host header
/// on the request.
pub struct HostHeader<'a>(pub Option<&'a str>);
pub struct HostHeader(pub Option<HeaderValue>);
impl<'a> Deref for HostHeader<'a> {
type Target = Option<&'a str>;
impl FromRequest for HostHeader {
type Error = actix_web::Error;
type Future = futures::future::Ready<Result<Self, Self::Error>>;
fn deref(&self) -> &Option<&'a str> {
&self.0
}
}
impl<'a, 'r> FromRequest<'a, 'r> for HostHeader<'a> {
type Error = ();
fn from_request(request: &'a Request<'r>) -> Outcome<HostHeader<'a>, ()> {
Outcome::Success(HostHeader(request.headers().get_one("Host")))
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
ok(Self(req.headers().get(header::HOST).cloned()))
}
}

15
templates/404.html Normal file
View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>404 Not Found</title>
</head>
<body align="center">
<div align="center">
<h1>404: Not Found</h1>
<p>The requested resource could not be found.</p>
<hr />
<small>bin.</small>
</div>
</body>
</html>

15
templates/500.html Normal file
View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>500 Internal Server Error</title>
</head>
<body align="center">
<div align="center">
<h1>500: Internal Server Error</h1>
<p>An error occurred while fetching the requested resource.</p>
<hr />
<small>bin.</small>
</div>
</body>
</html>

View File

@@ -1,28 +1,28 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>bin.</title>
<link rel="help" href="https://github.com/w4/bin">
<style>
* { box-sizing: border-box; }
html, body { margin: 0; }
body {
height: 100vh;
padding: 2rem;
background: #263238;
color: #B0BEC5;
font-family: 'Courier New', Courier, monospace;
line-height: 1.1;
display: flex;
}
{% block styles %}
{% endblock styles %}
body, code, textarea { font-family: Monaco, Menlo, Courier, Courier New, Andale Mono, monospace; }
code {
display: block;
}
{% block styles %}{% endblock styles %}
</style>
{% block head %}{% endblock head %}
</head>
<body>{% block content %}{% endblock content %}</body>
</html>

View File

@@ -6,10 +6,15 @@
textarea {
height: 100%;
width: 100%;
background: none;
border: none;
color: inherit;
outline: 0;
resize: none;
overflow: auto;
color: inherit;
font-family: inherit;
font-size: 1rem;
line-height: inherit;
@@ -24,7 +29,6 @@
width: 3rem;
border: none;
border-radius: 50%;
background: #2196F3;
color: white;

View File

@@ -5,11 +5,28 @@
height: 100%;
width: 100%;
margin: 0;
overflow: scroll;
overflow: auto;
font-family: inherit;
font-size: 1rem;
line-height: inherit;
counter-reset: line;
}
code {
counter-increment: line;
}
code::before {
content: counter(line);
display: inline-block;
width: 2em; /* Fixed width */
padding: 0 1em 0.3em 0;
margin-right: .5em;
color: #888;
-webkit-user-select: none;
}
{% endblock styles %}
{% block content %}<pre><code>{{ content|safe }}</code></pre>{% endblock content %}
{% block head %}
<link rel="stylesheet" type="text/css" href="highlight.css" />
{% endblock head %}
{% block content %}<pre>{{ content|safe }}</pre>{% endblock content %}