Files
2026-11-14 18:25:54 +01:00

202 lines
10 KiB
Markdown

# Pastable
Pastable is a free and open-source Plabble initiative for sharing text, code, short links, and one-time secrets.
## Run locally
With Rust and Cargo installed:
```sh
cargo run
```
The server listens on `0.0.0.0:3000` and is available locally at `http://localhost:3000`.
Set `PORT` to listen on another port.
On startup, the server creates the SQLite database and initializes its tables before accepting requests.
The default database is `pastable.db` in the working directory. Set `DATABASE_URL` to use a different location:
```sh
DATABASE_URL=sqlite://data.db cargo run
```
Route handlers can access the shared database pool with the `State(pool): State<SqlitePool>` extractor.
## Browser app
Open `/gui` for the browser workspace. Create text pastes, one-time secrets, and short links; choose custom IDs, languages, expiry, and view limits. After saving, a dialog prominently displays the returned link for copying or opening. Opening a share navigates directly to its page or redirect.
The language dropdown is populated from irosashi's supported grammars. The home page renders this README as HTML and shares the browser app's logo, navigation, and styling.
Sign in with HTTP Basic credentials to browse, filter, edit, and delete your own shares. A new username creates an account. Credentials stay in page memory and are cleared on sign-out or reload. Creating and managing shares uses the existing API without page reloads.
Opening a paste or following a short link counts as a view. Listing records and loading them into the editor do not. Updates preserve omitted options and view counters.
## Features
- Share text and code, with optional syntax highlighting.
- Set an expiration time or a maximum number of views.
- Share a secret that can only be viewed once.
- Create short links.
- Use HTTP Basic authentication to associate pastes with a user, update them, and list them.
## API
All paths are relative to the server URL. `{id}` is a path parameter containing a paste or short-link ID.
### Create a paste
| Method | Path | Behavior |
| --- | --- | --- |
| `POST` | `/` | Create a paste with a generated ID. |
| `POST` | `/{id}` | Use the requested ID if available; otherwise generate a random ID. |
| `POST` | `/once` | Create a paste with `max_views` fixed to `1`. |
| `POST` | `/once/{id}` | Request a custom ID and set `max_views` to `1`. Generate a random ID if the requested ID is taken. |
**Request body:** raw text, or an `application/x-www-form-urlencoded` form. Use a form to supply optional parameters.
| Form parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `content` | String | Yes | — | Text or code to store. For raw text requests, the entire body supplies this value. |
| `language` | String | No | Auto detect | Language for syntax highlighting. When omitted, irosashi detects from the first line (for example a shebang), then from a requested custom ID such as `main.rs`. Without a match, the paste stays plain text. Use `text` to force plain text. |
| `expires` | Integer | No | No expiration | Lifetime in seconds from the request; `0` expires immediately. Must be nonnegative. |
| `max_views` | Integer | No | Unlimited views | Positive maximum number of views before self-destruction. The `/once` routes force this to `1`. |
**Authentication:** optional HTTP Basic authentication. If supplied, the paste is associated with that user. An account is created if the user does not exist.
**Returns:** the assigned ID as plain text. This is an ID, not a full URL. Always use the returned ID, since a requested custom ID may already be taken.
Generated IDs contain exactly 8 alphanumeric characters, generated in Rust with `rand`. Confusing characters (`0`, `1`, `I`, `L`, `O`, `i`, `l`, `o`) are excluded. IDs are shared by pastes and short links. Reserved route names (`gui`, `list`, `once`, `to`, and `static`) and IDs containing `/` are unavailable as custom IDs; these also receive a generated ID.
Create a paste from raw text:
```sh
curl --request POST 'http://localhost:3000/' \
--header 'Content-Type: text/plain' \
--data-binary 'Hello, world!'
```
Create a paste with optional parameters:
```sh
curl 'http://localhost:3000/' \
--data-urlencode 'content=println!("Hello, world!");' \
--data-urlencode 'language=rust' \
--data-urlencode 'max_views=5'
```
Request a custom ID for a one-time secret:
```sh
curl --request POST 'http://localhost:3000/once/my-secret' \
--header 'Content-Type: text/plain' \
--data-binary 'A secret to share once'
```
### Read a paste
`GET /{id}` retrieves a paste by its ID.
| Parameter | Location | Required | Description |
| --- | --- | --- | --- |
| `id` | Path | Yes | The paste ID returned when it was created. |
| `Accept` | Header | No | Use `text/html` for syntax-highlighted HTML or `text/plain` for plain text. |
**Returns:** the paste as HTML or plain text, based on the `Accept` header. The default is plain text. HTML uses the stored language for syntax highlighting, falling back to plain text for missing or unknown languages.
HTML paste pages show the view counter, including the current read, and the maximum when set. HTML and plain-text responses include `X-Paste-Views` and, when limited, `X-Paste-Max-Views` response headers, so displaying the counter does not require another read.
The HTML page includes a **Copy raw code** button that preserves the original content and whitespace without another request or view. If clipboard access is unavailable, the raw text is selected for manual copying.
Each successful read increments `views`. The final permitted read returns the content and deletes the paste. Missing, expired, and exhausted pastes return `404`. Expired and exhausted records are also cleaned up in the background every minute.
```sh
curl 'http://localhost:3000/my-paste' \
--header 'Accept: text/plain'
```
### Create a short link
| Method | Path | Behavior |
| --- | --- | --- |
| `POST` | `/to` | Create a short link with a generated ID. |
| `POST` | `/to/{id}` | Use the requested ID if available; otherwise generate a random ID. |
**Request body:** the destination URL as raw text, or an `application/x-www-form-urlencoded` form with the following parameters:
| Form parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `href` | String | Yes | Absolute HTTP or HTTPS destination URL. For raw text requests, the entire body supplies this value. |
| `expires` | Integer | No | Lifetime in seconds from the request; `0` expires immediately. Must be nonnegative. Defaults to no expiration. |
| `max_views` | Integer | No | Positive maximum number of redirects before self-destruction. Defaults to unlimited redirects. |
**Returns:** the assigned ID as plain text. Use it in `/to/{id}` to follow the link.
```sh
curl 'http://localhost:3000/to' \
--data-urlencode 'href=https://example.com' \
--data-urlencode 'expires=3600' \
--data-urlencode 'max_views=5'
```
### Follow a short link
`GET /to/{id}` looks up a short link by its ID.
**Returns:** a `307 Temporary Redirect` to the destination URL, or `404` if the link is missing, expired, or exhausted. Each successful redirect increments `views`. The final permitted redirect deletes the short link immediately. Expiration and view limits use the same logic as text pastes.
### Update a paste
`PUT /{id}` updates a paste by its ID.
**Authentication:** HTTP Basic authentication of the creator is required.
The same request body as [the POST request](#create-a-paste). `content` is required; omitted `language`, `expires`, and `max_views` retain their current values. A supplied `expires` starts a new lifetime from the update request. The view counter is preserved, and `updated_at` is set to the current time.
**Returns:** `204 No Content` on success, `401` without authentication, `403` for another user's paste or an anonymous paste, and `404` for a missing, expired, or exhausted paste.
### Update a short link
`PUT /to/{id}` updates a short link by its ID.
**Authentication:** HTTP Basic authentication of the creator is required.
Use the same request body as [creating a short link](#create-a-short-link): the new destination as raw text, or a form with `href` and optional `expires` and `max_views`. Omitted options retain their current values. The view counter is preserved, and `updated_at` is set to the current time.
**Returns:** `204 No Content` on success, with the same authentication and missing-record errors as the paste update endpoint. Invalid destination URLs return `400`.
```sh
curl --request PUT --user 'alice:example-password' 'http://localhost:3000/to/my-link' \
--data-urlencode 'href=https://example.org' \
--data-urlencode 'expires=3600' \
--data-urlencode 'max_views=10'
```
### Delete a paste
`DELETE /{id}` deletes a paste or short link by its ID.
**Authentication:** HTTP Basic authentication of the creator is required.
**Returns:** `204 No Content` on success, with the same authentication and missing-paste errors as the update endpoint.
### List pastes
`GET /list` returns the authenticated user's pastes and short links.
**Authentication:** HTTP Basic authentication is required.
**Returns:** a plain-text list of that user's active records, ordered by creation time and ID. Each entry contains the ID on one line, followed by the content, with a blank line between entries. An empty list returns an empty body. Listing does not increment view counters. Missing authentication returns `401`.
With `Accept: text/html`, the response is an HTML table containing IDs, types, content, languages, view counts and limits, creation and update times, and expiration times. Dates are displayed in the browser's local timezone.
With `Accept: application/json`, the response is an array of full `Paste` records, including `id`, `paste_type` (`TextV1` or `UrlV1`), `content`, `language`, `expiration`, `max_views`, `views`, `created_at`, `updated_at`, and `owner`. Timestamps are Unix seconds; unset optional values are `null`.
```sh
curl --user 'alice:example-password' 'http://localhost:3000/list'
```
### Home page
`GET /` returns the home page.