Implement lazy-cleanup for expired invoices with deadline-based deletion
This commit is contained in:
@@ -24,6 +24,7 @@ $dbFile = __DIR__ . '/../data/urls.json';
|
|||||||
$rawInput = file_get_contents('php://input');
|
$rawInput = file_get_contents('php://input');
|
||||||
$input = is_string($rawInput) ? json_decode($rawInput, true) : null;
|
$input = is_string($rawInput) ? json_decode($rawInput, true) : null;
|
||||||
$hash = is_array($input) && isset($input['hash']) && is_string($input['hash']) ? $input['hash'] : '';
|
$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)) {
|
if (empty($hash) || strlen($hash) > 500 || !preg_match('/^[a-zA-Z0-9%+_=&.-]{1,500}$/', $hash)) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
@@ -69,6 +70,9 @@ while (isset($urls[$code])) {
|
|||||||
|
|
||||||
$signature = hash_hmac('sha256', $hash, $secret);
|
$signature = hash_hmac('sha256', $hash, $secret);
|
||||||
$urls[$code] = ['h' => $hash, 's' => $signature];
|
$urls[$code] = ['h' => $hash, 's' => $signature];
|
||||||
|
if ($expiryTs > 0) {
|
||||||
|
$urls[$code]['e'] = $expiryTs;
|
||||||
|
}
|
||||||
|
|
||||||
write_json_locked($fp, $urls);
|
write_json_locked($fp, $urls);
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|||||||
$decodedProofs = is_string($rawProofs) ? json_decode($rawProofs, true) : [];
|
$decodedProofs = is_string($rawProofs) ? json_decode($rawProofs, true) : [];
|
||||||
$proofs = is_array($decodedProofs) ? $decodedProofs : [];
|
$proofs = is_array($decodedProofs) ? $decodedProofs : [];
|
||||||
if (isset($proofs[$code])) {
|
if (isset($proofs[$code])) {
|
||||||
$response = ['verified' => true];
|
|
||||||
$proofEntry = $proofs[$code];
|
$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)) {
|
if (is_array($proofEntry)) {
|
||||||
foreach ($proofEntry as $k => $v) {
|
foreach ($proofEntry as $k => $v) {
|
||||||
if (is_string($k)) {
|
if (is_string($k)) {
|
||||||
@@ -41,6 +53,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
echo json_encode($response);
|
echo json_encode($response);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
echo json_encode(['verified' => false]);
|
echo json_encode(['verified' => false]);
|
||||||
}
|
}
|
||||||
@@ -132,6 +145,11 @@ $proofs[$code] = [
|
|||||||
'verified_at' => time()
|
'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);
|
write_json_locked($fp, $proofs);
|
||||||
echo json_encode(['ok' => true]);
|
echo json_encode(['ok' => true]);
|
||||||
|
|
||||||
|
|||||||
7
app.js
7
app.js
@@ -282,10 +282,15 @@
|
|||||||
|
|
||||||
async function shortenUrl(hash) {
|
async function shortenUrl(hash) {
|
||||||
try {
|
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', {
|
const res = await fetch('/api/shorten.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
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);
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|||||||
2
app.min.js
vendored
2
app.min.js
vendored
File diff suppressed because one or more lines are too long
15
s.php
15
s.php
@@ -31,6 +31,21 @@ $data = $urls[$code];
|
|||||||
$hash = is_array($data) ? ($data['h'] ?? '') : $data;
|
$hash = is_array($data) ? ($data['h'] ?? '') : $data;
|
||||||
$hash = is_string($hash) ? $hash : '';
|
$hash = is_string($hash) ? $hash : '';
|
||||||
$signature = is_array($data) ? ($data['s'] ?? null) : null;
|
$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)
|
// Verify HMAC signature if present (detect server-side tampering)
|
||||||
if (is_string($signature) && $signature !== '') {
|
if (is_string($signature) && $signature !== '') {
|
||||||
|
|||||||
Reference in New Issue
Block a user