1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
#!/usr/bin/env node
// Add a bookmark to Floccus encrypted bookmarks
// Usage: node add_bookmark.js <url> <title>
const crypto = require('crypto');
const fs = require('fs');
const BOOKMARKS_PATH = '/var/www/webdav/bookmarks.xbel';
const PASSWORD = process.env.BOOKMARKS_PASSWORD || JSON.parse(require('fs').readFileSync('/etc/automation/bookmarks.json', 'utf8')).password;
const url = process.argv[2];
const title = process.argv[3];
if (!url || !title) {
console.log('Usage: node add_bookmark.js <url> <title>');
process.exit(1);
}
function decrypt(data, password) {
const ciphertext = Buffer.from(data.ciphertext, 'base64');
const salt = data.salt;
const key = crypto.pbkdf2Sync(password, salt, 250000, 32, 'sha256');
const iv = ciphertext.slice(0, 16);
const encrypted = ciphertext.slice(16, -16);
const tag = ciphertext.slice(-16);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
}
function encrypt(plaintext, password, salt) {
const key = crypto.pbkdf2Sync(password, salt, 250000, 32, 'sha256');
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
const combined = Buffer.concat([iv, encrypted, tag]);
return combined.toString('base64');
}
// Read and decrypt
const data = JSON.parse(fs.readFileSync(BOOKMARKS_PATH, 'utf8'));
let xml = decrypt(data, PASSWORD);
// Find highest ID
const idMatch = xml.match(/highestId :(\d+):/);
let highestId = idMatch ? parseInt(idMatch[1]) : 100;
const newId = highestId + 1;
// Escape XML entities
const escapeXml = (str) => str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
// Add bookmark
const newBookmark = `<bookmark href="${escapeXml(url)}" id="${newId}">
<title>${escapeXml(title)}</title>
</bookmark>
</xbel>`;
xml = xml.replace(/highestId :(\d+):/, `highestId :${newId}:`);
xml = xml.replace('</xbel>', newBookmark);
// Encrypt and save
const newCiphertext = encrypt(xml, PASSWORD, data.salt);
fs.writeFileSync(BOOKMARKS_PATH, JSON.stringify({ ciphertext: newCiphertext, salt: data.salt }));
console.log(`Added bookmark: ${title}`);
console.log(`URL: ${url}`);
console.log(`ID: ${newId}`);
|