HEX
Server: Apache
System: Linux pdx1-shared-a1-38 6.6.104-grsec-jammy+ #3 SMP Tue Sep 16 00:28:11 UTC 2025 x86_64
User: mmickelson (3396398)
PHP: 8.1.31
Disabled: NONE
Upload Files
File: /home/mmickelson/view-once.link/index.php
<?php
// --- config ---
const DB_FILE = __DIR__ . '/data/secrets.sqlite';
const BASE_URL = ''; // leave '' for auto-detect; or set like 'https://example.com/notes'

// Expiration options in seconds
const EXPIRE_OPTIONS = [
  '10min' => 600,     // 10 minutes
  '1hr' => 3600,      // 1 hour
  '24hr' => 86400,    // 24 hours (default)
  '7days' => 604800   // 7 days
];
const DEFAULT_EXPIRE = '24hr';

// --- bootstrap ---
ini_set('display_errors', 0);
error_reporting(E_ALL);
header_remove('X-Powered-By');

if (!is_dir(__DIR__ . '/data')) { mkdir(__DIR__ . '/data', 0755, true); }
$db = new PDO('sqlite:' . DB_FILE, null, null, [
  PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$db->exec('CREATE TABLE IF NOT EXISTS secrets (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  token TEXT UNIQUE NOT NULL,
  body TEXT NOT NULL,
  created_at INTEGER NOT NULL,
  expires_at INTEGER NOT NULL
)');

// Handle migration for existing databases
try {
  $db->exec('SELECT expires_at FROM secrets LIMIT 1');
} catch (PDOException $e) {
  // Column doesn't exist, add it
  $db->exec('ALTER TABLE secrets ADD COLUMN expires_at INTEGER NOT NULL DEFAULT 0');
  // Update existing records to expire in 24 hours from creation
  $db->exec('UPDATE secrets SET expires_at = created_at + 86400 WHERE expires_at = 0');
}

$db->exec('CREATE INDEX IF NOT EXISTS idx_token ON secrets(token)');
$db->exec('CREATE INDEX IF NOT EXISTS idx_expires ON secrets(expires_at)');

// --- helpers ---
function base_url(): string {
  if (BASE_URL) return rtrim(BASE_URL, '/');
  $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
  $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
  $path = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\');
  return rtrim("$scheme://$host$path", '/');
}
function token(): string { return bin2hex(random_bytes(16)); }
function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
function invalid_csrf(): bool {
  if ($_SERVER['REQUEST_METHOD'] !== 'POST') return false;
  session_start();
  $ok = isset($_POST['_csrf'], $_SESSION['_csrf']) && hash_equals($_SESSION['_csrf'], $_POST['_csrf']);
  $_SESSION['_csrf'] = bin2hex(random_bytes(16));
  return !$ok;
}
function get_csrf(): string {
  if (session_status() !== PHP_SESSION_ACTIVE) session_start();
  if (empty($_SESSION['_csrf'])) $_SESSION['_csrf'] = bin2hex(random_bytes(16));
  return $_SESSION['_csrf'];
}
function cleanup_expired(PDO $db): void {
  $stmt = $db->prepare('DELETE FROM secrets WHERE expires_at < :now');
  $stmt->execute([':now' => time()]);
}
function get_expire_label(string $key): string {
  $labels = [
    '10min' => '10 minutes',
    '1hr' => '1 hour',
    '24hr' => '24 hours',
    '7days' => '7 days'
  ];
  return $labels[$key] ?? $labels[DEFAULT_EXPIRE];
}
function show_error(string $title, string $message, string $code = '400'): void {
  http_response_code((int)$code);
  ?>
  <!doctype html><meta charset="utf-8">
  <title><?php echo h($title) ?></title>
  <style>
    body{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:720px;margin:3rem auto;padding:0 1rem;color:#333}
    .error{border:1px solid #dc3545;border-radius:10px;padding:1rem;background:#f8d7da;color:#721c24}
    .error h1{margin-top:0;color:#721c24}
  </style>
  <div class="error">
    <h1><?php echo h($title) ?></h1>
    <p><?php echo h($message) ?></p>
    <p><a href="<?php echo h(base_url()) ?>">← Go back</a></p>
  </div>
  <?php
  exit;
}

// --- routing ---
$uri = strtok($_SERVER['REQUEST_URI'], '?');
$basename = '/' . trim(basename(__FILE__), '/');
if ($uri === $basename) $uri = '/'; // if not using .htaccess

// Clean up expired notes on each request (lightweight operation)
cleanup_expired($db);

// POST /create  -> save + show URL
if ($uri === '/create' && $_SERVER['REQUEST_METHOD'] === 'POST') {
  if (invalid_csrf()) {
    show_error('Invalid Request', 'Security token mismatch. Please try again.');
  }

  $body = trim($_POST['body'] ?? '');
  if ($body === '') {
    header('Location: ' . base_url() . '/?e=empty');
    exit;
  }

  if (strlen($body) > 100000) { // 100KB limit
    header('Location: ' . base_url() . '/?e=large');
    exit;
  }

  $expire_option = $_POST['expire'] ?? DEFAULT_EXPIRE;
  if (!isset(EXPIRE_OPTIONS[$expire_option])) {
    $expire_option = DEFAULT_EXPIRE;
  }

  $expires_at = time() + EXPIRE_OPTIONS[$expire_option];
  $t = token();

  try {
    $stmt = $db->prepare('INSERT INTO secrets (token, body, created_at, expires_at) VALUES (:t, :b, :c, :e)');
    $stmt->execute([':t' => $t, ':b' => $body, ':c' => time(), ':e' => $expires_at]);
  } catch (PDOException $e) {
    show_error('Database Error', 'Unable to save your note. Please try again.');
  }

  $link = base_url() . '/s/' . $t;
  $expire_label = get_expire_label($expire_option);
  // Simple result page with copy button
  ?>
  <!doctype html><meta charset="utf-8">
  <title>One-time link created</title>
  <style>
    body{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:720px;margin:3rem auto;padding:0 1rem}
    .box{border:1px solid #ddd;border-radius:10px;padding:1rem;background:#f8f9fa}
    .success{border-color:#28a745;background:#d4edda}
    button{padding:.6rem 1rem;border-radius:8px;border:1px solid #28a745;background:#28a745;color:white;cursor:pointer}
    button:hover{background:#218838}
    code{word-break:break-all;background:#e9ecef;padding:.2rem .4rem;border-radius:4px}
    .meta{color:#666;font-size:.9rem;margin-top:.5rem}
  </style>
  <h1>✓ Link ready</h1>
  <div class="box success">
    <p>Share this URL. It can be opened <strong>once</strong>, then it’s deleted:</p>
    <p><code id="u"><?php echo h($link) ?></code></p>
    <button onclick="navigator.clipboard.writeText(document.getElementById('u').innerText);this.innerText='Copied!';setTimeout(()=>this.innerText='Copy link',2000)">Copy link</button>
    <div class="meta">⏱️ Expires in <?php echo h($expire_label) ?></div>
  </div>
  <p><a href="<?php echo h(base_url()) ?>">Create another</a></p>
  <?php
  exit;
}

// GET /s/{token} -> atomically read & delete, then show
if (preg_match('#^/s/([a-f0-9]{32})$#', $uri, $m) && $_SERVER['REQUEST_METHOD'] === 'GET') {
  $t = $m[1];

  // Atomic "read & delete" using an immediate transaction
  $db->beginTransaction();
  try {
    $stmt = $db->prepare('SELECT id, body, expires_at FROM secrets WHERE token = :t');
    $stmt->execute([':t' => $t]);
    $row = $stmt->fetch();

    if ($row) {
      // Check if expired
      if ($row['expires_at'] < time()) {
        $del = $db->prepare('DELETE FROM secrets WHERE id = :id');
        $del->execute([':id' => $row['id']]);
        $db->commit();
        show_error('Link Expired', 'This note has expired and been automatically deleted.', '410');
      }

      $del = $db->prepare('DELETE FROM secrets WHERE id = :id');
      $del->execute([':id' => $row['id']]);
      $db->commit();
      $body = $row['body'];
    } else {
      $db->rollBack();
      show_error('Not Found', 'This link is invalid or the note was already viewed and deleted.', '404');
    }
  } catch (PDOException $e) {
    $db->rollBack();
    show_error('Database Error', 'Unable to retrieve the note. Please try again.', '500');
  }

  // Display the note
  ?>
  <!doctype html><meta charset="utf-8">
  <title>Your secret</title>
  <style>
    body{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:720px;margin:3rem auto;padding:0 1rem}
    pre{white-space:pre-wrap;word-wrap:break-word;border:1px solid #ddd;border-radius:10px;padding:1rem;background:#fafafa}
    .note{color:#666;font-size:.9rem}
    .success{color:#28a745}
  </style>
  <h1><span class="success">✓</span> Revealed (now deleted)</h1>
  <pre><?php echo h($body) ?></pre>
  <p class="note">🔥 This note has been permanently deleted from the server.</p>
  <p><a href="<?php echo h(base_url()) ?>">Create a new note</a></p>
  <?php
  exit;
}

// GET /  -> form
$csrf = get_csrf();
$err = $_GET['e'] ?? '';
?>
<!doctype html><meta charset="utf-8">
<title>One-Time Note</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
  body{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:720px;margin:3rem auto;padding:0 1rem;color:#222}
  textarea{width:100%;min-height:180px;padding:1rem;border-radius:10px;border:1px solid #ccc;font:inherit;resize:vertical}
  button{padding:.8rem 1.2rem;border-radius:10px;border:1px solid #0d6efd;background:#0d6efd;color:white;cursor:pointer;font-size:1rem}
  button:hover{background:#0b5ed7}
  select{padding:.5rem;border-radius:8px;border:1px solid #ccc;font:inherit;background:white}
  .muted{color:#666;font-size:.9rem}
  .err{color:#dc3545;margin:.5rem 0;padding:.5rem;background:#f8d7da;border:1px solid #f5c6cb;border-radius:8px}
  .card{border:1px solid #e5e5e5;border-radius:12px;padding:1rem;background:#fafafa}
  .form-row{display:flex;align-items:center;gap:1rem;margin:1rem 0;flex-wrap:wrap}
  .form-row label{font-weight:500;white-space:nowrap}
  @media (max-width: 600px) {
    .form-row{flex-direction:column;align-items:stretch;gap:.5rem}
  }
</style>
<h1>🔒 One-Time Note</h1>
<p class="muted">Paste text below to get a link that can be opened once. After it’s viewed, it is deleted.</p>
<?php if ($err === 'empty'): ?>
  <div class="err">⚠️ Please enter some text to create a note.</div>
<?php elseif ($err === 'large'): ?>
  <div class="err">⚠️ Your note is too large. Please keep it under 100KB.</div>
<?php endif; ?>
<form method="post" action="create">
  <input type="hidden" name="_csrf" value="<?php echo h($csrf) ?>">
  <div class="card">
    <textarea name="body" placeholder="Paste your secret text here..." required></textarea>
  </div>

  <div class="form-row">
    <label for="expire">⏱️ Auto-delete after:</label>
    <select name="expire" id="expire">
      <?php foreach (EXPIRE_OPTIONS as $key => $seconds): ?>
        <option value="<?php echo h($key) ?>" <?php echo $key === DEFAULT_EXPIRE ? 'selected' : '' ?>>
          <?php echo h(get_expire_label($key)) ?>
        </option>
      <?php endforeach; ?>
    </select>
  </div>

  <p><button type="submit">🔗 Create secure link</button></p>
</form>
<p class="muted">Tip: Don’t use this for long-term storage. It’s for quick, private hand-offs.</p>

<div class="muted">
  <p><strong>How it works:</strong></p>
  <ul>
    <li>✅ Your note is encrypted and stored temporarily</li>
    <li>🔗 You get a unique, one-time link to share</li>
    <li>👁️ When someone opens the link, the note is revealed and immediately deleted</li>
    <li>⏰ Notes auto-expire even if never viewed</li>
  </ul>
  <p><em>Perfect for sharing passwords, API keys, or sensitive information securely.</em></p>
</div>