rustypaste 0.17.0

A minimal file upload/pastebin service
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
<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** is a minimal file upload/pastebin service.

```sh
$ echo "some text" > awesome.txt

$ curl -F "file=@awesome.txt" https://paste.site.com
https://paste.site.com/safe-toad.txt

$ curl https://paste.site.com/safe-toad.txt
some text
```

</details>

<details>
  <summary>Table of Contents</summary>

<!-- vim-markdown-toc GFM -->

* [Features]#features
* [Installation]#installation
  * [From crates.io]#from-cratesio
  * [Arch Linux]#arch-linux
  * [Alpine Linux]#alpine-linux
  * [FreeBSD]#freebsd
  * [Binary releases]#binary-releases
  * [Build from source]#build-from-source
    * [Feature flags]#feature-flags
    * [Testing]#testing
      * [Unit tests]#unit-tests
      * [Test Fixtures]#test-fixtures
* [Usage]#usage
  * [CLI]#cli
    * [Expiration]#expiration
    * [One shot files]#one-shot-files
    * [One shot URLs]#one-shot-urls
    * [URL shortening]#url-shortening
    * [Paste file from remote URL]#paste-file-from-remote-url
    * [Cleaning up expired files]#cleaning-up-expired-files
    * [Delete file from server]#delete-file-from-server
    * [Override the filename]#override-the-filename
  * [Server]#server
    * [Authentication]#authentication
    * [List endpoint]#list-endpoint
    * [HTML Form]#html-form
    * [Docker]#docker
    * [Nginx]#nginx
  * [Third Party Clients]#third-party-clients
  * [Contributing]#contributing
    * [License]#license

<!-- vim-markdown-toc -->

</details>

## Features

- File upload & URL shortening & upload from URL
  - supports basic HTTP authentication
  - random file names (optional)
    - pet name (e.g. `capital-mosquito.txt`)
    - alphanumeric string (e.g. `yB84D2Dv.txt`)
    - random suffix (e.g. `file.MRV5as.tar.gz`)
  - supports expiring links
    - auto-expiration of files (optional)
    - auto-deletion of expired files (optional)
  - supports one shot links/URLs (can only be viewed once)
  - guesses MIME types
    - supports overriding and blacklisting
    - supports forcing to download via `?download=true`
  - no duplicate uploads (optional)
  - listing/deleting files
  - custom landing page
- Single binary
  - [binary releases]https://github.com/orhun/rustypaste/releases
- Simple configuration
  - supports hot reloading
- Easy to deploy
  - [docker images]https://hub.docker.com/r/orhunp/rustypaste
  - [appjail images]https://github.com/AppJail-makejails/rustypaste
- No database
  - filesystem is used
- Self-hosted
  - _centralization is bad!_
- Written in Rust
  - _blazingly fast!_

## Installation

<details>
  <summary>Packaging status</summary>

[![Packaging status](https://repology.org/badge/vertical-allrepos/rustypaste.svg)](https://repology.org/project/rustypaste/versions)

</details>

### From crates.io

```sh
cargo install rustypaste
```

### Arch Linux

```sh
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 [community repository](https://wiki.alpinelinux.org/wiki/Repositories).

```sh
apk add rustypaste
```

### FreeBSD

```sh
pkg install rustypaste
```

### Binary releases

See the available binaries on the [releases](https://github.com/orhun/rustypaste/releases/) page.

### Build from source

```sh
git clone https://github.com/orhun/rustypaste.git
cd rustypaste/
cargo build --release
```

#### Feature flags

- `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
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).

### CLI

```sh
function rpaste() {
  curl -F "file=@$1" -H "Authorization: <auth_token>" "<server_address>"
}
```

**\*** consider reading authorization headers from a file. (e.g. `-H @rpaste_auth`)

```sh
# upload a file
$ rpaste x.txt

# paste from stdin
$ rpaste -
```

#### Expiration

```sh
$ curl -F "file=@x.txt" -H "expire:10min" "<server_address>"
```

supported units:

- `nsec`, `ns`
- `usec`, `us`
- `msec`, `ms`
- `seconds`, `second`, `sec`, `s`
- `minutes`, `minute`, `min`, `m`
- `hours`, `hour`, `hr`, `h`
- `days`, `day`, `d`
- `weeks`, `week`, `w`
- `months`, `month`, `M`
- `years`, `year`, `y`

#### One shot files

```sh
$ curl -F "oneshot=@x.txt" "<server_address>"
```

#### One shot URLs

```sh
$ curl -F "oneshot_url=https://example.com" "<server_address>"
```

#### URL shortening

```sh
$ curl -F "url=https://example.com/some/long/url" "<server_address>"
```

#### Paste file from remote URL

```sh
$ curl -F "remote=https://example.com/file.png" "<server_address>"
```

#### Cleaning up expired files

Configure `[paste].delete_expired_files` to set an interval for deleting the expired files automatically.

On the other hand, following script can be used as [cron](https://en.wikipedia.org/wiki/Cron) for cleaning up the expired files manually:

```sh
#!/bin/env sh
now=$(date +%s)
find upload/ -maxdepth 2 -type f -iname "*.[0-9]*" |
while read -r filename; do
	[ "$(( ${filename##*.} / 1000 - "${now}" ))" -lt 0 ] && rm -v "${filename}"
done
```

#### Delete file from server

Set `delete_tokens` array in [config.toml](./config.toml) to activate the [`DELETE`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) endpoint and secure it with one (or more) auth token(s).

```sh
$ curl -H "Authorization: <auth_token>" -X DELETE "<server_address>/file.txt"
```

> The `DELETE` endpoint will not be exposed and will return `404` error if `delete_tokens` are not set.

#### Override the filename

When using the `random_url` config option, or when pasting a file [from remote URL](#paste-file-from-remote-url), rustypaste automatically selects a filename.

This can be overridden by sending a header called `filename`:

```sh
curl -F "file=@x.txt" -H "filename: <file_name>" "<server_address>"
curl -F "remote=https://example.com/file.png" -H "filename: <file_name>" "<server_address>"
```

### Server

To start the server:

```sh
$ rustypaste
```

If the configuration file is not found in the current directory, specify it via `CONFIG` environment variable:

```sh
$ CONFIG="$HOME/.rustypaste.toml" rustypaste
```

#### Authentication

To enable basic HTTP auth, set the `AUTH_TOKEN` environment variable (via `.env`):

```sh
$ echo "AUTH_TOKEN=$(openssl rand -base64 16)" > .env
$ rustypaste
```

There are 2 options for setting multiple auth tokens:

- Via the array field `[server].auth_tokens` in your `config.toml`.
- Or by writing a newline separated list to a file and passing its path to rustypaste via `AUTH_TOKENS_FILE` and `DELETE_TOKENS_FILE` respectively.

> If neither `AUTH_TOKEN`, `AUTH_TOKENS_FILE` nor `[server].auth_tokens` are set, the server will not require any authentication.
>
> Exception is the `DELETE` endpoint, which requires at least one token to be set. See [deleting files from server]#delete-file-from-server for more information.

See [config.toml](./config.toml) for configuration options.

#### MIME handling

rustypaste determines a file's MIME type from its extension (with optional overrides) and serves
text-like types as `text/plain; charset=utf-8` to avoid script execution.

- `[paste].mime_override` lets you override MIME types by filename regex.
- `[paste].mime_blacklist` blocks uploads of specific MIME types.
- `[paste].text_mime_overrides` forces additional detected/guessed MIME types to be rendered as plaintext (unlike `mime_override` which matches by filename regex, this matches the content's actual MIME type).

#### List endpoint

Set `expose_list` to true in [config.toml](./config.toml) to be able to retrieve a JSON formatted list of files in your uploads directory. This will not include oneshot files, oneshot URLs, or URLs.

```sh
$ curl "http://<server_address>/list"

[{"file_name":"accepted-cicada.txt","file_size":241,"expires_at_utc":null}]
```

This route will require an `AUTH_TOKEN` if one is set.

#### HTML Form

It is possible to use an HTML form for uploading files. To do so, you need to update two fields in your `config.toml`:

- Set the `[landing_page].content_type` to `text/html; charset=utf-8`.
- Update the `[landing_page].text` field with your HTML form or point `[landing_page].file` to your html file.

For an example, see [examples/html_form.toml](./examples/html_form.toml)

#### Docker

Following command can be used to run a container which is built from the [Dockerfile](./Dockerfile) in this repository:

```sh
$ docker run --rm -d \
  -v "$(pwd)/upload/":/app/upload \
  -v "$(pwd)/config.toml":/app/config.toml \
  --env-file "$(pwd)/.env" \
  -e "RUST_LOG=debug" \
  -p 8000:8000 \
  --name rustypaste \
  orhunp/rustypaste
```

- uploaded files go into `./upload` (on the host machine)
- set the `AUTH_TOKEN` via `-e` or `--env-file` to enable auth

You can build this image using `docker build -t rustypaste .` command.

If you want to run the image using [docker compose](https://docs.docker.com/compose/), simply run `docker-compose up -d`. (see [docker-compose.yml](./docker-compose.yml))

#### Nginx

Example server configuration with reverse proxy:

```nginx
server {
    listen 80;
    location / {
        proxy_pass                         http://localhost:8000/;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-For   $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        add_header X-XSS-Protection        "1; mode=block";
        add_header X-Frame-Options         "sameorigin";
        add_header X-Content-Type-Options  "nosniff";
    }
}
```

If you get a `413 Request Entity Too Large` error during upload, set the max body size in `nginx.conf`:

```nginx
http {
    # ...
    client_max_body_size 100M;
}
```

### Third Party Clients

- [dbohdan/ferripaste]https://github.com/dbohdan/ferripaste - Alternative rustypaste CLI client
- [rukh-debug/droidypaste]https://github.com/rukh-debug/droidypaste - Android client built with React Native and Expo
- [rukh-debug/rustypaste-gui.sh]https://gist.github.com/rukh-debug/cc42900f86e39cacef6f7a6ba77ebf58 - Linux's Minimal GUI client powered by zenity
- [ShareX]https://github.com/ShareX/ShareX - ShareX as handy GUI client for rustypaste ([.sxcu profile example]https://gist.github.com/Null-Kelvin/c726a080762781a603cbc1b713d36cc6)
- [Silvenga/rustypaste-ui]https://github.com/Silvenga/rustypaste-ui - A modern, single file, web UI to interact with the rustypaste server.

### Contributing

Pull requests are welcome!

Consider submitting your ideas via [issues](https://github.com/orhun/rustypaste/issues/new) first and check out the [existing issues](https://github.com/orhun/rustypaste/issues).

#### License

<sup>
All code is licensed under <a href="LICENSE">The MIT License</a>.
</sup>