Implement lazy-cleanup for expired invoices with deadline-based deletion

This commit is contained in:
Alexander Schmidt
2026-03-26 11:01:32 +01:00
parent c4e3f3cd15
commit ee0d0d4124
5 changed files with 50 additions and 8 deletions

View File

@@ -24,6 +24,7 @@ $dbFile = __DIR__ . '/../data/urls.json';
$rawInput = file_get_contents('php://input');
$input = is_string($rawInput) ? json_decode($rawInput, true) : null;
$hash = is_array($input) && isset($input['hash']) && is_string($input['hash']) ? $input['hash'] : '';
$expiryTs = is_array($input) && isset($input['expiry_ts']) ? intval($input['expiry_ts']) : 0;
if (empty($hash) || strlen($hash) > 500 || !preg_match('/^[a-zA-Z0-9%+_=&.-]{1,500}$/', $hash)) {
http_response_code(400);
@@ -69,6 +70,9 @@ while (isset($urls[$code])) {
$signature = hash_hmac('sha256', $hash, $secret);
$urls[$code] = ['h' => $hash, 's' => $signature];
if ($expiryTs > 0) {
$urls[$code]['e'] = $expiryTs;
}
write_json_locked($fp, $urls);

View File

@@ -31,8 +31,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$decodedProofs = is_string($rawProofs) ? json_decode($rawProofs, true) : [];
$proofs = is_array($decodedProofs) ? $decodedProofs : [];
if (isset($proofs[$code])) {
$response = ['verified' => true];
$proofEntry = $proofs[$code];
$proofExpiry = is_array($proofEntry) ? intval($proofEntry['e'] ?? 0) : 0;
// Check if proof has expired (lazy cleanup)
if ($proofExpiry > 0 && time() > $proofExpiry) {
unset($proofs[$code]);
[$fp, $allProofs] = read_json_locked($dbFile);
if (isset($allProofs[$code])) {
unset($allProofs[$code]);
write_json_locked($fp, $allProofs);
}
echo json_encode(['verified' => false]);
} else {
$response = ['verified' => true];
if (is_array($proofEntry)) {
foreach ($proofEntry as $k => $v) {
if (is_string($k)) {
@@ -41,6 +53,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
}
}
echo json_encode($response);
}
} else {
echo json_encode(['verified' => false]);
}
@@ -132,6 +145,11 @@ $proofs[$code] = [
'verified_at' => time()
];
// Copy expiry timestamp from URL if it exists
if (isset($urls[$code]) && is_array($urls[$code]) && isset($urls[$code]['e']) && $urls[$code]['e'] > 0) {
$proofs[$code]['e'] = $urls[$code]['e'];
}
write_json_locked($fp, $proofs);
echo json_encode(['ok' => true]);

7
app.js
View File

@@ -282,10 +282,15 @@
async function shortenUrl(hash) {
try {
// Calculate expiry timestamp if deadline is set
let expiryTs = null;
if (selectedDays && selectedDays > 0) {
expiryTs = Math.floor((Date.now() + selectedDays * 86400000) / 1000);
}
const res = await fetch('/api/shorten.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ hash: hash })
body: JSON.stringify({ hash: hash, expiry_ts: expiryTs })
});
if (!res.ok) throw new Error('HTTP ' + res.status);
const data = await res.json();

2
app.min.js vendored

File diff suppressed because one or more lines are too long

15
s.php
View File

@@ -31,6 +31,21 @@ $data = $urls[$code];
$hash = is_array($data) ? ($data['h'] ?? '') : $data;
$hash = is_string($hash) ? $hash : '';
$signature = is_array($data) ? ($data['s'] ?? null) : null;
$expiryTs = is_array($data) ? intval($data['e'] ?? 0) : 0;
// Check if URL has expired (lazy cleanup)
if ($expiryTs > 0 && time() > $expiryTs) {
require_once __DIR__ . '/api/_helpers.php';
// Delete expired URL
[$fp, $urls] = read_json_locked(__DIR__ . '/data/urls.json');
if (isset($urls[$code])) {
unset($urls[$code]);
write_json_locked($fp, $urls);
}
http_response_code(410);
echo 'Gone';
exit;
}
// Verify HMAC signature if present (detect server-side tampering)
if (is_string($signature) && $signature !== '') {