dev-_007haste-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README +7 -0
- package/cert/cert.pem +21 -0
- package/cert/key.pem +28 -0
- package/package.json +18 -0
- package/public/index.html +84 -0
- package/public/view.html +52 -0
- package/server.js +119 -0
package/README
ADDED
package/cert/cert.pem
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIDiTCCAnGgAwIBAgIUaYtIq6l74H/ECqm2HYrHGmwh4AwwDQYJKoZIhvcNAQEL
|
|
3
|
+
BQAwVDELMAkGA1UEBhMCVUExEzARBgNVBAgMClNvbWUtU3RhdGUxCzAJBgNVBAoM
|
|
4
|
+
AkRQMQswCQYDVQQLDAJoYTEWMBQGCSqGSIb3DQEJARYHYWFALmlvYTAeFw0yNTA3
|
|
5
|
+
MTUwODU4NDRaFw0yNjA3MTUwODU4NDRaMFQxCzAJBgNVBAYTAlVBMRMwEQYDVQQI
|
|
6
|
+
DApTb21lLVN0YXRlMQswCQYDVQQKDAJEUDELMAkGA1UECwwCaGExFjAUBgkqhkiG
|
|
7
|
+
9w0BCQEWB2FhQC5pb2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCr
|
|
8
|
+
1niYQMRY3Jt19Z0sleSwhxXv9xjyqiwgD8KKCAu34DghLAIqh1P3tEHi8FFfUxgM
|
|
9
|
+
cO2XVYmTe06xhYJF+zLkfDHIBeuYP0svrMXA5Q9h7xieX50c+ac5A5aDibqjaiQq
|
|
10
|
+
M+e28X5DGC8+nI5BaPtBEm76ugerf5SxYXhmPurU7zuE9I7+E7atRaALSYwAtjGO
|
|
11
|
+
pGw533s0/EpZQEGy8/Snpnm0d0/DQZE88Ma5IxMFgjQgol6yAgiqDXoinbTEbFFE
|
|
12
|
+
xTtW6jb5ZzbRN7AlDy6+V4upfRgIUYzOuM9dKoaN5T5nFjkzz//XIsTJDE1t731Q
|
|
13
|
+
XHT8OU6SIBx70CxmjF6jAgMBAAGjUzBRMB0GA1UdDgQWBBRfU7K2lhkLt7Q7d7Rw
|
|
14
|
+
NSkOK3j5pDAfBgNVHSMEGDAWgBRfU7K2lhkLt7Q7d7RwNSkOK3j5pDAPBgNVHRMB
|
|
15
|
+
Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCm/MJeroEBsHsWN3K7F9+e2UCe
|
|
16
|
+
X7LyIQqhHzuoJgFXQt2/fz7QSDGGScT8ntnAmdSjl29fQKt1PepthNLevbiYcg3X
|
|
17
|
+
qQoEbyNUN1ur2Y8r+6siKT+XjqYqIehIiTNiz3vmLmMYKr8AW/R7Jx2s/jWpYb4d
|
|
18
|
+
RSd2tmflMgphSYUI6J3ja1wNngl29Zi/xeuJu7fM6wxT8puXpy2wFnpugksFMUKE
|
|
19
|
+
pItSinifPXPrAnTK0VcdRwsVTtO6SkOJjHouYqlowI2AoSBUUAhkgaii7ksiieF3
|
|
20
|
+
VePVr0x0VkGuf0ziVH1J5W3Blp64OO8ZhClBE97mv59lVSxPWTDa/TfCg/55
|
|
21
|
+
-----END CERTIFICATE-----
|
package/cert/key.pem
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-----BEGIN PRIVATE KEY-----
|
|
2
|
+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCr1niYQMRY3Jt1
|
|
3
|
+
9Z0sleSwhxXv9xjyqiwgD8KKCAu34DghLAIqh1P3tEHi8FFfUxgMcO2XVYmTe06x
|
|
4
|
+
hYJF+zLkfDHIBeuYP0svrMXA5Q9h7xieX50c+ac5A5aDibqjaiQqM+e28X5DGC8+
|
|
5
|
+
nI5BaPtBEm76ugerf5SxYXhmPurU7zuE9I7+E7atRaALSYwAtjGOpGw533s0/EpZ
|
|
6
|
+
QEGy8/Snpnm0d0/DQZE88Ma5IxMFgjQgol6yAgiqDXoinbTEbFFExTtW6jb5ZzbR
|
|
7
|
+
N7AlDy6+V4upfRgIUYzOuM9dKoaN5T5nFjkzz//XIsTJDE1t731QXHT8OU6SIBx7
|
|
8
|
+
0CxmjF6jAgMBAAECggEAIkgCvgkpwrqcLx/Fb/glbAw/FaT7rqnBbNIIEwaA4/mQ
|
|
9
|
+
6RlBA2AFIeUtwcsEyNoocY7z712KuUf/zo2ejDyNjiSyv79inXh/FO9Icm+gLn8H
|
|
10
|
+
L81OaFHsCQVMuUg4WlKaDWtWflpWQKdqAXk1Lhp2CajWSCHl1tJ4hRy3atqBeeI+
|
|
11
|
+
GqWd994RNUt5Jp0uK+EFXlNwnnMXRU8vdntOHpnDBIJ6VEEwa1T3W0PDQzAgFEqX
|
|
12
|
+
ko2uSIOGERF0IbWChZFLJALKij9VAVtz/hEpASeochAkGgMdsXWHvmPobtGs17Ww
|
|
13
|
+
JZthw2Sw+PqkGIqvDrprX2sujzHXR/m+4C+/r7ugAQKBgQDpN1Baq6JgRm6JB/FE
|
|
14
|
+
C7nikLz1hSWdhqMrs0qagWTv+nifRB8DjqReoagMZFDdRUtegikcYSYV9uoOSN2G
|
|
15
|
+
DbEvwqHfHVv46VnO35Aq/WkvBgOBMfOYqeFeZdBwC76oBpvQEpLdZu0n7sB0Xakh
|
|
16
|
+
q5667RC2VQ7XoNUbzxhxv04gAQKBgQC8oBlDAeY4LDk++W5LFrBfQY6hPvKYT4rK
|
|
17
|
+
ApF1smrpBik0xuD5IHLkJQSZLuWMxDSG02xPXLf81KY44+PzZ3bt6Kkt78zGvOWB
|
|
18
|
+
NjEHnuXY/v29Bk+YXIZXjJRlkcdusvCoWZf1mz3dNetEAwyifpaegC9sLJswDm/7
|
|
19
|
+
iUZ4dA3+owKBgGB1MlTuHDC5sMoYcN92AiIFP0JFGbO3lhFjDH4u+nKPJAirgjBU
|
|
20
|
+
dQN21Sya6R87o9qo9xv3ymrXPyM9W/IwA5L/azf+yqV+zlhT+yrsuO5sDFMJN/ly
|
|
21
|
+
efYzIizVjrusUBGVPQ05nukfRJZcjrGQbAxgNMNVFth0evAPjcquF0ABAoGBAI5h
|
|
22
|
+
FtBylZGfKBL1+p/pX3Z3qK7kMgBwQiYb7Cp4Y6TDhsUUvequ6Kp3mtH6CUHJNJD0
|
|
23
|
+
9BOvvB23ckUNGBq55ZbNgS5wjRUSrvZqrnW/JmY2i9dr9Rbf+HQLZFSKxowzu96H
|
|
24
|
+
ymGVLgxVT+IFnzrk7NJ1ldZEkVuj+jlXNH9lss6bAoGBAMNISITTIr/n/S5i6ap9
|
|
25
|
+
5GU1mdVMc8d7jBF5ckWm+xE+U3AF7lJDA2bZ/ml9HANFj0J2zKUxjhruXpCbGaGd
|
|
26
|
+
DpvoMMc+ualpkszIpIC19Av9oX0SKnFaHa5nUmAG/A6nua51DFAqNvvepoISdmyy
|
|
27
|
+
WeSifK0ywhJQWXUJMjngCJrI
|
|
28
|
+
-----END PRIVATE KEY-----
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dev-_007haste-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Simple hastebin-like server with HTTPS",
|
|
5
|
+
"main": "server.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node server.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"express": "^5.1.0"
|
|
11
|
+
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"author": "aa.arsenenko",
|
|
16
|
+
"license": "ISC"
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Paste</title>
|
|
5
|
+
<style>
|
|
6
|
+
html, body {
|
|
7
|
+
margin: 0; padding: 0; height: 100%;
|
|
8
|
+
background: #1d1f21; color: #c5c8c6;
|
|
9
|
+
font-family: monospace;
|
|
10
|
+
}
|
|
11
|
+
textarea {
|
|
12
|
+
width: 100%; height: 100%;
|
|
13
|
+
border: none; resize: none; outline: none;
|
|
14
|
+
background: #1d1f21; color: #c5c8c6;
|
|
15
|
+
font-size: 16px; padding: 1em;
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
}
|
|
18
|
+
#controls {
|
|
19
|
+
position: fixed; top: 10px; right: 10px;
|
|
20
|
+
z-index: 10;
|
|
21
|
+
display: flex; gap: 8px;
|
|
22
|
+
}
|
|
23
|
+
#password, #ttl {
|
|
24
|
+
padding: 5px; font-size: 14px;
|
|
25
|
+
background: #373b41; border: none;
|
|
26
|
+
color: #c5c8c6; border-radius: 3px;
|
|
27
|
+
}
|
|
28
|
+
#saveBtn {
|
|
29
|
+
background: #373b41; color: #fff;
|
|
30
|
+
border: none; padding: 8px 16px;
|
|
31
|
+
cursor: pointer; font-size: 14px;
|
|
32
|
+
border-radius: 3px;
|
|
33
|
+
}
|
|
34
|
+
#saveBtn:hover { background: #4e5257; }
|
|
35
|
+
</style>
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<div id="controls">
|
|
39
|
+
<input type="password" id="password" placeholder="(optional) password" />
|
|
40
|
+
<select id="ttl" title="Select paste expiration">
|
|
41
|
+
<option value="none" selected>No expiration</option>
|
|
42
|
+
<option value="1m">1 minute</option>
|
|
43
|
+
<option value="10m">10 minutes</option>
|
|
44
|
+
<option value="1h">1 hour</option>
|
|
45
|
+
<option value="1d">1 day</option>
|
|
46
|
+
<option value="7d">7 days</option>
|
|
47
|
+
</select>
|
|
48
|
+
|
|
49
|
+
<button id="saveBtn" onclick="save()">Save</button>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<textarea id="text" placeholder="Paste your code or text here..."></textarea>
|
|
53
|
+
|
|
54
|
+
<script>
|
|
55
|
+
const textarea = document.getElementById('text');
|
|
56
|
+
|
|
57
|
+
window.addEventListener('keydown', function (e) {
|
|
58
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
save();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
async function save() {
|
|
65
|
+
const text = textarea.value;
|
|
66
|
+
const password = document.getElementById('password').value;
|
|
67
|
+
const ttl = document.getElementById('ttl').value;
|
|
68
|
+
|
|
69
|
+
const res = await fetch('/save', {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {'Content-Type': 'application/json'},
|
|
72
|
+
body: JSON.stringify({ text, password, ttl })
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (res.ok) {
|
|
76
|
+
const data = await res.json();
|
|
77
|
+
window.location.href = data.url;
|
|
78
|
+
} else {
|
|
79
|
+
alert('Save failed');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
package/public/view.html
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>View Paste</title>
|
|
5
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
|
6
|
+
<style>
|
|
7
|
+
html, body {
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
background: #1d1f21;
|
|
11
|
+
color: #c5c8c6;
|
|
12
|
+
font-family: monospace;
|
|
13
|
+
height: 100%;
|
|
14
|
+
}
|
|
15
|
+
pre {
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 1em;
|
|
18
|
+
white-space: pre-wrap;
|
|
19
|
+
word-wrap: break-word;
|
|
20
|
+
font-size: 16px;
|
|
21
|
+
overflow: auto;
|
|
22
|
+
}
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<pre><code id="output" class="plaintext">Loading...</code></pre>
|
|
27
|
+
|
|
28
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
29
|
+
<script>
|
|
30
|
+
const params = new URLSearchParams(location.search);
|
|
31
|
+
const id = params.get('id');
|
|
32
|
+
|
|
33
|
+
function fetchPaste(password = '') {
|
|
34
|
+
fetch(`/data/${id}?password=${encodeURIComponent(password)}`)
|
|
35
|
+
.then(res => res.json())
|
|
36
|
+
.then(data => {
|
|
37
|
+
if (data.error) throw new Error(data.error);
|
|
38
|
+
const el = document.getElementById('output');
|
|
39
|
+
el.textContent = data.text;
|
|
40
|
+
hljs.highlightElement(el);
|
|
41
|
+
})
|
|
42
|
+
.catch(err => {
|
|
43
|
+
const pass = prompt("Enter password for this paste:");
|
|
44
|
+
if (pass !== null) fetchPaste(pass);
|
|
45
|
+
else document.getElementById('output').textContent = 'Access denied.';
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fetchPaste();
|
|
50
|
+
</script>
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
package/server.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const https = require('https');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
|
|
7
|
+
const app = express();
|
|
8
|
+
const PORT = 3000;
|
|
9
|
+
const DATA_DIR = path.join(__dirname, 'data');
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR);
|
|
12
|
+
|
|
13
|
+
app.use(express.json());
|
|
14
|
+
app.use(express.static(path.join(__dirname, 'public')));
|
|
15
|
+
|
|
16
|
+
// Загружаем SSL сертификат и ключ
|
|
17
|
+
const SSL_KEY_PATH = path.join(__dirname, 'cert', 'key.pem');
|
|
18
|
+
const SSL_CERT_PATH = path.join(__dirname, 'cert', 'cert.pem');
|
|
19
|
+
|
|
20
|
+
const sslOptions = {
|
|
21
|
+
key: fs.readFileSync(SSL_KEY_PATH),
|
|
22
|
+
cert: fs.readFileSync(SSL_CERT_PATH)
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Удаляем просроченные пасты
|
|
26
|
+
function cleanupExpired() {
|
|
27
|
+
const files = fs.readdirSync(DATA_DIR);
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
|
|
30
|
+
for (const file of files) {
|
|
31
|
+
if (!file.endsWith('.json')) continue;
|
|
32
|
+
|
|
33
|
+
const filePath = path.join(DATA_DIR, file);
|
|
34
|
+
let data;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
data = JSON.parse(fs.readFileSync(filePath));
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error(`Ошибка чтения файла ${file}:`, err);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (data.expireAt && now > data.expireAt) {
|
|
44
|
+
try {
|
|
45
|
+
fs.unlinkSync(filePath);
|
|
46
|
+
console.log(`[CLEANUP] Удалена просроченная паста: ${file}`);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(`Не удалось удалить файл ${file}:`, err);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Запуск очистки каждую минуту
|
|
55
|
+
setInterval(cleanupExpired, 60 * 1000);
|
|
56
|
+
cleanupExpired();
|
|
57
|
+
|
|
58
|
+
app.post('/save', (req, res) => {
|
|
59
|
+
const { text, password, ttl } = req.body;
|
|
60
|
+
if (!text) return res.status(400).send('No text provided');
|
|
61
|
+
|
|
62
|
+
const id = crypto.randomBytes(3).toString('hex');
|
|
63
|
+
const filePath = path.join(DATA_DIR, `${id}.json`);
|
|
64
|
+
|
|
65
|
+
let expireAt = null;
|
|
66
|
+
if (ttl && ttl !== 'none') {
|
|
67
|
+
const num = parseInt(ttl.slice(0, -1));
|
|
68
|
+
const unit = ttl.slice(-1);
|
|
69
|
+
const multipliers = { m: 60 * 1000, h: 60 * 60 * 1000, d: 24 * 60 * 60 * 1000 };
|
|
70
|
+
|
|
71
|
+
if (!multipliers[unit] || isNaN(num)) {
|
|
72
|
+
return res.status(400).send('Invalid TTL format');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
expireAt = Date.now() + multipliers[unit] * num;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const data = {
|
|
79
|
+
text,
|
|
80
|
+
password: password || null,
|
|
81
|
+
expireAt
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
85
|
+
|
|
86
|
+
const url = `/view.html?id=${id}`;
|
|
87
|
+
console.log(`[NEW] https://192.168.88.234:${PORT}${url}`);
|
|
88
|
+
res.json({ url });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
app.get('/data/:id', (req, res) => {
|
|
92
|
+
const filePath = path.join(DATA_DIR, `${req.params.id}.json`);
|
|
93
|
+
if (!fs.existsSync(filePath)) return res.status(404).send('Not found');
|
|
94
|
+
|
|
95
|
+
let data;
|
|
96
|
+
try {
|
|
97
|
+
data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
98
|
+
} catch (err) {
|
|
99
|
+
return res.status(500).send('Error reading paste');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
|
|
104
|
+
if (data.expireAt && now > data.expireAt) {
|
|
105
|
+
fs.unlinkSync(filePath);
|
|
106
|
+
return res.status(404).send('Paste expired');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const given = req.query.password || '';
|
|
110
|
+
if (data.password && data.password !== given) {
|
|
111
|
+
return res.status(403).json({ error: 'Incorrect password' });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
res.json({ text: data.text });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
https.createServer(sslOptions, app).listen(PORT, () => {
|
|
118
|
+
console.log(`HTTPS Server running at https://192.168.88.234:${PORT}`);
|
|
119
|
+
});
|