Skip to content

Commit f6df2de

Browse files
authored
Merge pull request #743 from hackmdio/fix-to-use-url-safe-base64
Fix to use url-safe base64 in note url
2 parents 6b30f66 + 8bfe519 commit f6df2de

7 files changed

Lines changed: 101 additions & 10 deletions

File tree

lib/history.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22
// history
33
// external modules
4+
var LZString = require('lz-string')
45

56
// core
67
var config = require('./config')
@@ -27,7 +28,20 @@ function getHistory (userid, callback) {
2728
}
2829
var history = {}
2930
if (user.history) {
30-
history = parseHistoryToObject(JSON.parse(user.history))
31+
history = JSON.parse(user.history)
32+
// migrate LZString encoded note id to base64url encoded note id
33+
for (let i = 0, l = history.length; i < l; i++) {
34+
try {
35+
let id = LZString.decompressFromBase64(history[i].id)
36+
if (id && models.Note.checkNoteIdValid(id)) {
37+
history[i].id = models.Note.encodeNoteId(id)
38+
}
39+
} catch (err) {
40+
// most error here comes from LZString, ignore
41+
logger.error(err)
42+
}
43+
}
44+
history = parseHistoryToObject(history)
3145
}
3246
if (config.debug) {
3347
logger.info('read history success: ' + user.id)

lib/models/note.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
var fs = require('fs')
44
var path = require('path')
55
var LZString = require('lz-string')
6+
var base64url = require('base64url')
67
var md = require('markdown-it')()
78
var metaMarked = require('meta-marked')
89
var cheerio = require('cheerio')
@@ -114,6 +115,24 @@ module.exports = function (sequelize, DataTypes) {
114115
return false
115116
}
116117
},
118+
encodeNoteId: function (id) {
119+
// remove dashes in UUID and encode in url-safe base64
120+
let str = id.replace(/-/g, '')
121+
let hexStr = Buffer.from(str, 'hex')
122+
return base64url.encode(hexStr)
123+
},
124+
decodeNoteId: function (encodedId) {
125+
// decode from url-safe base64
126+
let id = base64url.toBuffer(encodedId).toString('hex')
127+
// add dashes between the UUID string parts
128+
let idParts = []
129+
idParts.push(id.substr(0, 8))
130+
idParts.push(id.substr(8, 4))
131+
idParts.push(id.substr(12, 4))
132+
idParts.push(id.substr(16, 4))
133+
idParts.push(id.substr(20, 12))
134+
return idParts.join('-')
135+
},
117136
checkNoteIdValid: function (id) {
118137
var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
119138
var result = id.match(uuidRegex)
@@ -190,13 +209,25 @@ module.exports = function (sequelize, DataTypes) {
190209
return _callback(err, null)
191210
})
192211
},
212+
// parse note id by LZString is deprecated, here for compability
193213
parseNoteIdByLZString: function (_callback) {
194214
// try to parse note id by LZString Base64
195215
try {
196216
var id = LZString.decompressFromBase64(noteId)
197217
if (id && Note.checkNoteIdValid(id)) { return callback(null, id) } else { return _callback(null, null) }
198218
} catch (err) {
199-
return _callback(err, null)
219+
logger.error(err)
220+
return _callback(null, null)
221+
}
222+
},
223+
parseNoteIdByBase64Url: function (_callback) {
224+
// try to parse note id by base64url
225+
try {
226+
var id = Note.decodeNoteId(noteId)
227+
if (id && Note.checkNoteIdValid(id)) { return callback(null, id) } else { return _callback(null, null) }
228+
} catch (err) {
229+
logger.error(err)
230+
return _callback(null, null)
200231
}
201232
},
202233
parseNoteIdByShortId: function (_callback) {

lib/realtime.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ var cookie = require('cookie')
55
var cookieParser = require('cookie-parser')
66
var url = require('url')
77
var async = require('async')
8-
var LZString = require('lz-string')
98
var randomcolor = require('randomcolor')
109
var Chance = require('chance')
1110
var chance = new Chance()
@@ -703,7 +702,7 @@ function operationCallback (socket, operation) {
703702
}
704703

705704
function updateHistory (userId, note, time) {
706-
var noteId = note.alias ? note.alias : LZString.compressToBase64(note.id)
705+
var noteId = note.alias ? note.alias : models.Note.encodeNoteId(note.id)
707706
if (note.server) history.updateHistory(userId, noteId, note.server.document, time)
708707
}
709708

lib/response.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// external modules
44
var fs = require('fs')
55
var markdownpdf = require('markdown-pdf')
6-
var LZString = require('lz-string')
76
var shortId = require('shortid')
87
var querystring = require('querystring')
98
var request = require('request')
@@ -124,7 +123,7 @@ function newNote (req, res, next) {
124123
alias: req.alias ? req.alias : null,
125124
content: req.body ? req.body : ''
126125
}).then(function (note) {
127-
return res.redirect(config.serverurl + '/' + LZString.compressToBase64(note.id))
126+
return res.redirect(config.serverurl + '/' + models.Note.encodeNoteId(note.id))
128127
}).catch(function (err) {
129128
logger.error(err)
130129
return response.errorInternalError(res)
@@ -179,7 +178,7 @@ function showNote (req, res, next) {
179178
findNote(req, res, function (note) {
180179
// force to use note id
181180
var noteId = req.params.noteId
182-
var id = LZString.compressToBase64(note.id)
181+
var id = models.Note.encodeNoteId(note.id)
183182
if ((note.alias && noteId !== note.alias) || (!note.alias && noteId !== id)) { return res.redirect(config.serverurl + '/' + (note.alias || id)) }
184183
return responseHackMD(res, note)
185184
})
@@ -321,7 +320,7 @@ function actionPDF (req, res, note) {
321320
function actionGist (req, res, note) {
322321
var data = {
323322
client_id: config.github.clientID,
324-
redirect_uri: config.serverurl + '/auth/github/callback/' + LZString.compressToBase64(note.id) + '/gist',
323+
redirect_uri: config.serverurl + '/auth/github/callback/' + models.Note.encodeNoteId(note.id) + '/gist',
325324
scope: 'gist',
326325
state: shortId.generate()
327326
}
@@ -418,7 +417,7 @@ function publishNoteActions (req, res, next) {
418417
var action = req.params.action
419418
switch (action) {
420419
case 'edit':
421-
res.redirect(config.serverurl + '/' + (note.alias ? note.alias : LZString.compressToBase64(note.id)))
420+
res.redirect(config.serverurl + '/' + (note.alias ? note.alias : models.Note.encodeNoteId(note.id)))
422421
break
423422
default:
424423
res.redirect(config.serverurl + '/s/' + note.shortid)
@@ -432,7 +431,7 @@ function publishSlideActions (req, res, next) {
432431
var action = req.params.action
433432
switch (action) {
434433
case 'edit':
435-
res.redirect(config.serverurl + '/' + (note.alias ? note.alias : LZString.compressToBase64(note.id)))
434+
res.redirect(config.serverurl + '/' + (note.alias ? note.alias : models.Note.encodeNoteId(note.id)))
436435
break
437436
default:
438437
res.redirect(config.serverurl + '/p/' + note.shortid)

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"Idle.Js": "git+https://github.com/shawnmclean/Idle.js",
1919
"async": "^2.1.4",
2020
"aws-sdk": "^2.7.20",
21+
"base64url": "^2.0.0",
2122
"blueimp-md5": "^2.6.0",
2223
"body-parser": "^1.15.2",
2324
"bootstrap": "^3.3.7",

public/js/history.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33

44
import store from 'store'
55
import S from 'string'
6+
import LZString from 'lz-string'
7+
8+
import {
9+
checkNoteIdValid,
10+
encodeNoteId
11+
} from './utils'
612

713
import {
814
checkIfAuth
@@ -291,6 +297,15 @@ function parseToHistory (list, notehistory, callback) {
291297
else if (!list || !notehistory) callback(list, notehistory)
292298
else if (notehistory && notehistory.length > 0) {
293299
for (let i = 0; i < notehistory.length; i++) {
300+
// migrate LZString encoded id to base64url encoded id
301+
try {
302+
let id = LZString.decompressFromBase64(notehistory[i].id)
303+
if (id && checkNoteIdValid(id)) {
304+
notehistory[i].id = encodeNoteId(id)
305+
}
306+
} catch (err) {
307+
console.error(err)
308+
}
294309
// parse time to timestamp and fromNow
295310
const timestamp = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'))
296311
notehistory[i].timestamp = timestamp.valueOf()

public/js/utils.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import base64url from 'base64url'
2+
3+
let uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
4+
5+
export function checkNoteIdValid (id) {
6+
let result = id.match(uuidRegex)
7+
if (result && result.length === 1) {
8+
return true
9+
} else {
10+
return false
11+
}
12+
}
13+
14+
export function encodeNoteId (id) {
15+
// remove dashes in UUID and encode in url-safe base64
16+
let str = id.replace(/-/g, '')
17+
let hexStr = Buffer.from(str, 'hex')
18+
return base64url.encode(hexStr)
19+
}
20+
21+
export function decodeNoteId (encodedId) {
22+
// decode from url-safe base64
23+
let id = base64url.toBuffer(encodedId).toString('hex')
24+
// add dashes between the UUID string parts
25+
let idParts = []
26+
idParts.push(id.substr(0, 8))
27+
idParts.push(id.substr(8, 4))
28+
idParts.push(id.substr(12, 4))
29+
idParts.push(id.substr(16, 4))
30+
idParts.push(id.substr(20, 12))
31+
return idParts.join('-')
32+
}

0 commit comments

Comments
 (0)