# 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`. 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` extractor. ## 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 | Not set | Language for syntax highlighting. | | `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. IDs are shared by pastes and short links. Reserved route names (`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. 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. ```sh curl --user 'alice:example-password' 'http://localhost:3000/list' ``` ### Home page `GET /` returns the home page.