paste-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 01fdb2b1f66e
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 6eca480d6447d873e2e5eed9
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
functions/api.ts
// Paste's public HTTP API: no account. Anyone can make a paste and anyone with its id can read it;
// the owner key that comes back is what lets you edit or delete it. Every route answers JSON except
// /raw; GET / describes the routes. Writes go through the same policy rules as the web app.
import { SITE_URL } from "./lib/config";
import {
  ID_RE, randomId, randomKey, pasteUrl, rawUrl, adminUrl, byteLength, expiresAt,
  checkContent, checkTitle, parseExpires, detectLanguage, isLanguage, isPasswordLocked,
} from "./lib/paste";

const NOW = { operation: "time", value: "now" };
const GONE = "This paste does not exist. It may have expired, or been deleted after it was read.";

const cors = {
  "access-control-allow-origin": "*",
  "access-control-allow-methods": "GET, POST, PATCH, DELETE, OPTIONS",
  "access-control-allow-headers": "content-type, x-owner-key",
};
const json = (body: unknown, status = 200) =>
  new Response(JSON.stringify(body, null, 2), { status, headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", ...cors } });
const fail = (status: number, error: string) => json({ error }, status);

class HttpError extends Error { constructor(public status: number, message: string) { super(message); } }

async function body(req: Request): Promise<Record<string, any>> {
  try {
    const parsed = await req.json();
    if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
  } catch { /* fall through */ }
  throw new HttpError(400, "Send a JSON object body, or the text itself with content-type: text/plain.");
}

/** What POST /pastes was sent: a JSON object, or (with content-type text/plain) the text itself with options in the query string. */
async function createInput(req: Request, url: URL): Promise<Record<string, any>> {
  if (!/^text\/plain\b/i.test(req.headers.get("content-type") || "")) return body(req);
  const q = url.searchParams;
  const flag = q.get("burn");
  return {
    content: await req.text(),
    title: q.get("title"),
    language: q.get("language"),
    expires: q.get("expires"),
    burn: flag == null ? undefined : !["0", "false", "no"].includes(flag.toLowerCase()),
  };
}

interface Paste { content: string; title: string; language: string; ttl: number; burn: boolean; encrypted: boolean; opened?: boolean; createdAt: number }

/** Reads a paste. The rules refuse an expired paste, so it reads as gone. */
async function load(ctx: any, id: string): Promise<Paste> {
  let paste: Paste | null = null;
  if (ID_RE.test(id)) {
    try {
      paste = await ctx.bounded.get(`pastes/${id}`);
    } catch (err) {
      if (!/denied|403|forbidden|not allowed/i.test(String((err as Error)?.message ?? err))) throw err;
    }
  }
  if (!paste || paste.opened) throw new HttpError(404, GONE);
  return paste;
}

/** A burn paste is marked opened, which the rules allow only once, so of two simultaneous readers only one gets it. Then it is deleted. */
async function burn(ctx: any, id: string) {
  try {
    await ctx.bounded.set(`pastes/${id}`, { opened: true });
  } catch {
    throw new HttpError(404, GONE);
  }
  await ctx.bounded.delete(`pastes/${id}`);
}

/** Every API caller shares one identity, so owner actions always check the key first. */
async function ownerKey(ctx: any, req: Request, id: string): Promise<string> {
  const key = req.headers.get("x-owner-key")?.trim() || "";
  if (!key) throw new HttpError(401, "Send the paste's owner key in the x-owner-key header.");
  const stored = await ctx.bounded.get(`pastekeys/${id}`);
  if (!stored || stored.key !== key) throw new HttpError(403, "That owner key does not match this paste.");
  // Become an owner with the key (once), so the rules let this route change the paste.
  const me = ctx.user.id;
  if (!(await ctx.bounded.get(`pastes/${id}/owners/${me}`))) {
    await ctx.bounded.setMany([
      { path: `pasteclaims/${id}/c/${me}`, document: { key } },
      { path: `pastes/${id}/owners/${me}`, document: { at: NOW } },
    ]);
  }
  return key;
}

function describe(id: string, p: Paste, withContent: boolean) {
  const exp = expiresAt(p.createdAt, p.ttl);
  return {
    id,
    url: pasteUrl(id),
    rawUrl: rawUrl(id),
    title: p.title,
    language: p.language,
    size: byteLength(p.content),
    encrypted: p.encrypted,
    password: isPasswordLocked(p),
    burn: p.burn,
    createdAt: new Date(p.createdAt * 1000).toISOString(),
    expiresAt: exp ? new Date(exp * 1000).toISOString() : null,
    ...(withContent ? { content: p.content } : {}),
  };
}

const routes = {
  name: "Paste by OpenApps",
  about: "Share text, code and logs by link. Free, no account, no key. Pastes are unlisted: only people with the link can read them.",
  docs: `${SITE_URL}/llms.txt`,
  openapi: `${SITE_URL}/openapi.json`,
  routes: [
    "POST /pastes {content, title?, language?, expires?, burn?} -> the link, the raw link, a private admin link and the owner key",
    "POST /pastes?title=&expires=&burn= with content-type: text/plain and the text as the body -> the same, for sending a file as it is",
    "GET /pastes/:id -> the paste as JSON (a burn-after-reading paste is deleted by this read; add ?peek=1 to see its details without reading it)",
    "GET /pastes/:id/raw -> the paste as plain text (also deletes a burn-after-reading paste)",
    "PATCH /pastes/:id with header x-owner-key {content?, title?, language?} -> the updated paste",
    "DELETE /pastes/:id with header x-owner-key -> deletes the paste",
  ],
  limits: "content up to 512 KB in the web app; this API takes request bodies up to 128 KiB. expires: never, 10m, 1h, 1d, 1w, 1mo, 1y, or seconds from 60 to 31536000.",
};

export default async function api(req: Request, ctx: any) {
  if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: cors });
  const url = new URL(req.url);
  const parts = url.pathname.split("/").filter(Boolean);
  // Accept both /pastes/... and /api/pastes/... so a copied path works either way.
  if (parts[0] === "api") parts.shift();
  try {
    if (!parts.length && req.method === "GET") return json(routes);
    if (parts[0] !== "pastes") return fail(404, "Unknown route. GET / lists the routes.");

    if (parts.length === 1) {
      if (req.method !== "POST") return fail(405, "Use POST /pastes to make a paste. Nobody can list pastes.");
      const input = await createInput(req, url);
      const content = checkContent(input.content);
      if ("error" in content) return fail(400, content.error);
      const title = checkTitle(input.title);
      if ("error" in title) return fail(400, title.error);
      const lang = input.language == null || input.language === "" || input.language === "auto" ? detectLanguage(content.content) : input.language;
      if (!isLanguage(lang)) return fail(400, `Unknown language "${lang}". GET ${SITE_URL}/llms.txt lists them, or leave it out to detect it.`);
      const expires = parseExpires(input.expires);
      if ("error" in expires) return fail(400, expires.error);
      if (input.burn != null && typeof input.burn !== "boolean") return fail(400, "burn must be true or false.");
      const me = ctx.user.id;
      const id = randomId();
      const key = randomKey();
      const paste = { content: content.content, title: title.title, language: lang, ttl: expires.ttl, burn: input.burn === true, encrypted: false };
      await ctx.bounded.setMany([
        { path: `pastes/${id}`, document: { ...paste, opened: false, createdBy: me, createdAt: NOW } },
        { path: `pastes/${id}/owners/${me}`, document: { at: NOW } },
        { path: `pastekeys/${id}`, document: { key } },
      ]);
      const made = describe(id, { ...paste, createdAt: Math.floor(Date.now() / 1000) }, false);
      return json({ ...made, adminUrl: adminUrl(id, key), ownerKey: key }, 201);
    }

    const id = parts[1];
    if (!id || parts.length > 3) return fail(404, "Unknown route. GET / lists the routes.");

    if (parts.length === 3) {
      if (parts[2] !== "raw") return fail(404, "Unknown route. GET / lists the routes.");
      if (req.method !== "GET") return fail(405, "Use GET on /pastes/<id>/raw.");
      const paste = await load(ctx, id);
      if (paste.encrypted) return fail(409, "This paste is encrypted in the browser. Open its full link in a browser to read it.");
      if (paste.burn) await burn(ctx, id);
      return new Response(paste.content, { headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store", ...cors } });
    }

    if (req.method === "GET") {
      const paste = await load(ctx, id);
      if (paste.burn) {
        if (url.searchParams.has("peek")) return json(describe(id, paste, false));
        await burn(ctx, id);
        return json({ ...describe(id, paste, true), deleted: true });
      }
      return json(describe(id, paste, true));
    }

    if (req.method === "PATCH") {
      const paste = await load(ctx, id);
      await ownerKey(ctx, req, id);
      const input = await body(req);
      const patch: Partial<Paste> = {};
      if (input.content != null) {
        if (paste.encrypted) return fail(400, "This paste is encrypted. Edit it from its admin link in a browser.");
        const c = checkContent(input.content);
        if ("error" in c) return fail(400, c.error);
        patch.content = c.content;
      }
      if (input.title != null) {
        const t = checkTitle(input.title);
        if ("error" in t) return fail(400, t.error);
        patch.title = t.title;
      }
      if (input.language != null) {
        if (!isLanguage(input.language)) return fail(400, `Unknown language "${input.language}".`);
        patch.language = input.language;
      }
      if (!Object.keys(patch).length) return fail(400, "Send content, title, language, or any of them.");
      await ctx.bounded.set(`pastes/${id}`, patch);
      return json(describe(id, { ...paste, ...patch }, false));
    }

    if (req.method === "DELETE") {
      await load(ctx, id);
      await ownerKey(ctx, req, id);
      await ctx.bounded.delete(`pastes/${id}`);
      return json({ id, deleted: true });
    }

    return fail(405, "Use GET, PATCH or DELETE on /pastes/<id>.");
  } catch (err) {
    if (err instanceof HttpError) return fail(err.status, err.message);
    const message = err instanceof Error ? err.message : String(err);
    return fail(500, `Something went wrong: ${message}`);
  }
}

Change history

Quality pass: password lock, year expiry, line links, text/plain API
01fdb2b1f6 · 4 hours ago
Bounded source sync
fe5788c2d2 · 1 day ago
Bounded source sync
ac6f7dbc95 · 1 day ago
Bounded source sync
0a45926293 · 1 day ago