Add -> add project
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
15
app/config/config.php
Normal file
15
app/config/config.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
$env = parse_ini_file(__DIR__ . '/../../.env');
|
||||||
|
return [
|
||||||
|
'db' => [
|
||||||
|
'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'
|
||||||
|
];
|
||||||
31
app/controllers/SaveController.php
Normal file
31
app/controllers/SaveController.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
require __DIR__ . '/../core/security.php';
|
||||||
|
$pdo = require __DIR__ . '/../core/db.php';
|
||||||
|
require __DIR__ . '/../models/Paste.php';
|
||||||
|
$config = require __DIR__ . '/../config/config.php';
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
header('Location: /index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$text = $_POST['text'] ?? '';
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
$expire = isset($_POST['expire']) ? intval($_POST['expire']) : 0;
|
||||||
|
if (trim($text) === '') {
|
||||||
|
die('Text is required');
|
||||||
|
}
|
||||||
|
$id = generateId();
|
||||||
|
$enc = encryptText($text, $config['master_key']);
|
||||||
|
$password_hash = $password !== '' ? password_hash($password, PASSWORD_DEFAULT) : null;
|
||||||
|
$expire_time = $expire > 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;
|
||||||
59
app/controllers/ViewController.php
Normal file
59
app/controllers/ViewController.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
require __DIR__ . '/../core/security.php';
|
||||||
|
$pdo = require __DIR__ . '/../core/db.php';
|
||||||
|
require __DIR__ . '/../models/Paste.php';
|
||||||
|
$config = require __DIR__ . '/../config/config.php';
|
||||||
|
|
||||||
|
$id = $_GET['id'] ?? '';
|
||||||
|
|
||||||
|
$paste = new Paste($pdo);
|
||||||
|
$data = $paste->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 "<link rel='stylesheet' href='/assets/css/style.css'>";
|
||||||
|
echo "<form method='post'>";
|
||||||
|
echo "<input type='password' class='usepassword' name='password' placeholder='Password'>";
|
||||||
|
echo "<button type='submit'>View</button>";
|
||||||
|
echo "</form>";
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>View Paste</title>
|
||||||
|
<link rel="stylesheet" href="/assets/css/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Your Paste</h1>
|
||||||
|
<div class="paste-box">
|
||||||
|
<button id="copyBtn">Copy</button>
|
||||||
|
<pre id="pasteContent"><?= htmlspecialchars($decrypted) ?></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
app/core/db.php
Normal file
13
app/core/db.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
$config = require __DIR__ . '/../config/config.php';
|
||||||
|
try {
|
||||||
|
$pdo = new PDO(
|
||||||
|
"mysql:host={$config['db']['host']};dbname={$config['db']['name']};charset=utf8mb4",
|
||||||
|
$config['db']['user'],
|
||||||
|
$config['db']['pass'],
|
||||||
|
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||||
|
);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
die('Database connection error');
|
||||||
|
}
|
||||||
|
return $pdo;
|
||||||
13
app/core/redis.php
Normal file
13
app/core/redis.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
function redisClient()
|
||||||
|
{
|
||||||
|
static $redis = null;
|
||||||
|
if ($redis === null) {
|
||||||
|
$config = require __DIR__ . '/../config/config.php';
|
||||||
|
|
||||||
|
$redis = new Redis();
|
||||||
|
$redis->connect($config['redis']['host'], $config['redis']['port']);
|
||||||
|
}
|
||||||
|
return $redis;
|
||||||
|
}
|
||||||
15
app/core/security.php
Normal file
15
app/core/security.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
function encryptText($text, $key) {
|
||||||
|
$iv = random_bytes(16);
|
||||||
|
$cipher = openssl_encrypt($text, 'AES-256-CBC', $key, 0, $iv);
|
||||||
|
return [
|
||||||
|
'cipher' => $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));
|
||||||
|
}
|
||||||
62
app/models/Paste.php
Normal file
62
app/models/Paste.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../core/db.php';
|
||||||
|
require_once __DIR__ . '/../core/redis.php';
|
||||||
|
|
||||||
|
|
||||||
|
class Paste
|
||||||
|
{
|
||||||
|
private $pdo;
|
||||||
|
public function __construct($pdo)
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
public/.htaccess
Normal file
0
public/.htaccess
Normal file
83
public/assets/css/style.css
Normal file
83
public/assets/css/style.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
38
public/assets/js/app.js
Normal file
38
public/assets/js/app.js
Normal file
@@ -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 = "<div class='error'>" + data.message + "</div>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultBox.innerHTML = `
|
||||||
|
<h3>Paste Created</h3>
|
||||||
|
|
||||||
|
<input type="text" id="pasteLink" value="${data.url}" readonly style="width:100%; padding:8px;">
|
||||||
|
|
||||||
|
<button id="copyBtn">Copy Link</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById("copyBtn").addEventListener("click", () => {
|
||||||
|
const input = document.getElementById("pasteLink");
|
||||||
|
navigator.clipboard.writeText(input.value);
|
||||||
|
alert("Link copied!");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
40
public/assets/js/main.js
Normal file
40
public/assets/js/main.js
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
28
public/index.php
Normal file
28
public/index.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
$action = $_GET['action'] ?? '';
|
||||||
|
|
||||||
|
if ($action === 'save') {
|
||||||
|
require __DIR__ . '/../app/controllers/SaveController.php';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<link rel="stylesheet" href="/assets/css/style.css">
|
||||||
|
<script src="/assets/js/main.js"></script>
|
||||||
|
<script src="/assets/js/app.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<form method="POST" action="?action=save" id="pasteForm">
|
||||||
|
<textarea name="text" placeholder="Enter your text..." required></textarea>
|
||||||
|
<input type="text" class="usepassword" name="password" placeholder="Password (Optional)">
|
||||||
|
<select name="expire">
|
||||||
|
<option value="60">1 MIN</option>
|
||||||
|
<option value="3600">1 Hours</option>
|
||||||
|
<option value="86400">2 Day</option>
|
||||||
|
<option value="0">without time</option>
|
||||||
|
</select>
|
||||||
|
<button type="submit">Create link</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="result"></div>
|
||||||
|
|
||||||
5
public/view.php
Normal file
5
public/view.php
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__ . '/../app/controllers/ViewController.php';
|
||||||
|
|
||||||
|
?>
|
||||||
Reference in New Issue
Block a user