commit 3327207f05b02ea1826b1a6a0e32171a59e617cd Author: soheil khaledabadi Date: Fri Mar 27 14:09:52 2026 +0330 Add -> add project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/app/config/config.php b/app/config/config.php new file mode 100644 index 0000000..fd93941 --- /dev/null +++ b/app/config/config.php @@ -0,0 +1,15 @@ + [ + 'host' => $env['DB_HOST'] ?? 'localhost', + 'name' => $env['DB_NAME'] ?? 'paste', + 'user' => $env['DB_USER'] ?? 'root', + 'pass' => $env['DB_PASS'] ?? '' + ], + 'redis' => [ + 'host' => '127.0.0.1', + 'port' => 6379 + ], + 'master_key' => $env['MASTER_KEY'] ?? 'change_me_master_key' +]; diff --git a/app/controllers/SaveController.php b/app/controllers/SaveController.php new file mode 100644 index 0000000..b895ce4 --- /dev/null +++ b/app/controllers/SaveController.php @@ -0,0 +1,31 @@ + 0 ? time() + $expire : null; +$paste = new Paste($pdo); + +$paste->save($id, $enc['cipher'], $enc['iv'], $expire_time,$password_hash); + + +$url = "http://" . $_SERVER['HTTP_HOST'] . "/view.php?id=" . $id; + +echo json_encode([ + "success" => true, + "url" => $url +]); +exit; \ No newline at end of file diff --git a/app/controllers/ViewController.php b/app/controllers/ViewController.php new file mode 100644 index 0000000..7128c82 --- /dev/null +++ b/app/controllers/ViewController.php @@ -0,0 +1,59 @@ +get($id); + +if (!$data) { + die('Paste not found.'); +} + +if ($data['expire_time'] !== null && time() > (int)$data['expire_time']) { + die('Paste has expired.'); +} + +if ($data['password_hash']) { + if (!isset($_POST['password'])) { + echo ""; + echo "
"; + echo ""; + echo ""; + echo "
"; + exit; + } + + if (!password_verify($_POST['password'], $data['password_hash'])) { + die('Wrong password.'); + } +} + +$decrypted = decryptText($data['encrypted_text'], $data['iv'], $config['master_key']); + +if ($decrypted === false) { + die('Decryption failed.'); +} +?> + + + + + + View Paste + + + + +
+

Your Paste

+
+ +
+
+
+ + \ No newline at end of file diff --git a/app/core/db.php b/app/core/db.php new file mode 100644 index 0000000..7fc0cd1 --- /dev/null +++ b/app/core/db.php @@ -0,0 +1,13 @@ + PDO::ERRMODE_EXCEPTION] + ); +} catch (Exception $e) { + die('Database connection error'); +} +return $pdo; diff --git a/app/core/redis.php b/app/core/redis.php new file mode 100644 index 0000000..613cb18 --- /dev/null +++ b/app/core/redis.php @@ -0,0 +1,13 @@ +connect($config['redis']['host'], $config['redis']['port']); + } + return $redis; +} diff --git a/app/core/security.php b/app/core/security.php new file mode 100644 index 0000000..f3c6238 --- /dev/null +++ b/app/core/security.php @@ -0,0 +1,15 @@ + $cipher, + 'iv' => base64_encode($iv) + ]; +} +function decryptText($cipher, $iv, $key) { + return openssl_decrypt($cipher, 'AES-256-CBC', $key, 0, base64_decode($iv)); +} +function generateId() { + return bin2hex(random_bytes(16)); +} diff --git a/app/models/Paste.php b/app/models/Paste.php new file mode 100644 index 0000000..01e4e50 --- /dev/null +++ b/app/models/Paste.php @@ -0,0 +1,62 @@ +pdo = $pdo; + } + + public function save($id, $encrypted_text, $iv, $expire_time, $password_hash) + { + if ($expire_time === null) { + $stmt = $this->pdo->prepare("INSERT INTO pastes(id, encrypted_text, iv, expire_time, password_hash) + VALUES (?, ?, ?, NULL, ?)"); + return $stmt->execute([$id, $encrypted_text, $iv, $password_hash]); + } + + $redis = redisClient(); + + $expire_time = (int)$expire_time; + $ttl = max(1, $expire_time - time()); + + + $redis->setex( + "paste:$id", + $ttl, + json_encode([ + 'encrypted_text' => $encrypted_text, + 'iv' => $iv, + 'password_hash' => $password_hash + ]) + ); + + + return true; + } + + + public function get($id) + { + $redis = redisClient(); + + $data = $redis->get("paste:$id"); + if ($data !== false) { + $json = json_decode($data, true); + return [ + 'encrypted_text' => $json['encrypted_text'], + 'iv' => $json['iv'], + 'password_hash' => $json['password_hash'], + 'expire_time' => time() + $redis->ttl("paste:$id") + ]; + } + + $stmt = $this->pdo->prepare("SELECT * FROM pastes WHERE id = ?"); + $stmt->execute([$id]); + return $stmt->fetch(PDO::FETCH_ASSOC); + } +} diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..e69de29 diff --git a/public/assets/css/style.css b/public/assets/css/style.css new file mode 100644 index 0000000..695dd8e --- /dev/null +++ b/public/assets/css/style.css @@ -0,0 +1,83 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: monospace; +} + +body { + background: #0f172a; + color: #e2e8f0; + padding: 40px; +} + +/* container */ +.container { + max-width: 900px; + margin: auto; +} + +/* title */ +h1 { + margin-bottom: 20px; + font-size: 28px; +} + +/* textarea */ +textarea { + width: 100%; + min-height: 300px; + background: #020617; + border: 1px solid #334155; + color: #e2e8f0; + padding: 12px; + resize: vertical; + font-size: 14px; + border-radius: 6px; +} + +.usepassword, +select { + background: #020617; + border: 1px solid #334155; + color: #e2e8f0; + padding: 8px; + border-radius: 6px; + margin-top: 10px; +} + +/* button */ +button { + background: #38bdf8; + border: none; + color: #020617; + padding: 10px 16px; + margin-top: 15px; + border-radius: 6px; + cursor: pointer; + font-weight: bold; +} + +button:hover { + background: #0ea5e9; +} + +/* paste display */ +.paste-box { + background: #020617; + border: 1px solid #334155; + padding: 15px; + margin-top: 20px; + border-radius: 6px; + overflow-x: auto; +} + +/* link */ +a { + color: #38bdf8; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} diff --git a/public/assets/js/app.js b/public/assets/js/app.js new file mode 100644 index 0000000..fb1c30f --- /dev/null +++ b/public/assets/js/app.js @@ -0,0 +1,38 @@ +document.addEventListener("DOMContentLoaded", () => { + + const form = document.getElementById("pasteForm"); + const resultBox = document.getElementById("result"); + + form.addEventListener("submit", async (e) => { + e.preventDefault(); + + const formData = new FormData(form); + + const response = await fetch("/index.php?action=save", { + method: "POST", + body: formData + }); + + const data = await response.json(); + + if (!data.success) { + resultBox.innerHTML = "
" + data.message + "
"; + return; + } + + resultBox.innerHTML = ` +

Paste Created

+ + + + + `; + + document.getElementById("copyBtn").addEventListener("click", () => { + const input = document.getElementById("pasteLink"); + navigator.clipboard.writeText(input.value); + alert("Link copied!"); + }); + }); + +}); diff --git a/public/assets/js/main.js b/public/assets/js/main.js new file mode 100644 index 0000000..6b9953d --- /dev/null +++ b/public/assets/js/main.js @@ -0,0 +1,40 @@ +document.addEventListener("DOMContentLoaded", () => { + + const textarea = document.querySelector("textarea"); + const form = document.querySelector("form"); + const copyBtn = document.getElementById("copyBtn"); + + + if (textarea) { + textarea.addEventListener("input", () => { + textarea.style.height = "auto"; + textarea.style.height = textarea.scrollHeight + "px"; + }); + } + + if (form && textarea) { + form.addEventListener("submit", (e) => { + if (textarea.value.trim() === "") { + e.preventDefault(); + alert("Paste cannot be empty"); + } + }); + } + + if (copyBtn) { + copyBtn.addEventListener("click", () => { + const paste = document.getElementById("pasteContent"); + + if (!paste) return; + + navigator.clipboard.writeText(paste.innerText) + .then(() => { + copyBtn.textContent = "Copied!"; + setTimeout(() => { + copyBtn.textContent = "Copy"; + }, 2000); + }); + }); + } + +}); diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..7dad121 --- /dev/null +++ b/public/index.php @@ -0,0 +1,28 @@ + + + + + + +
+ + + + +
+ +
+ diff --git a/public/view.php b/public/view.php new file mode 100644 index 0000000..7b6f832 --- /dev/null +++ b/public/view.php @@ -0,0 +1,5 @@ +