From b6fff91d1da2cabf4993f813d223823cfe3e9bca Mon Sep 17 00:00:00 2001 From: Lumi Pakkanen Date: Wed, 17 Jul 2024 18:03:10 +0300 Subject: [PATCH] Validate scale identifiers Add tests. --- index.ts | 19 ++++++++++++------- tests/utils.spec.ts | 28 ++++++++++++++++++++++++++++ utils.ts | 8 ++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 tests/utils.spec.ts create mode 100644 utils.ts diff --git a/index.ts b/index.ts index 74953db..c6e5292 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,7 @@ import {stat} from 'node:fs'; import {join, parse} from 'node:path'; import {cleanAndValidateEnvelope, validatePayload} from './data-processing'; +import {validateId} from './utils'; const INDEX_BODY = ` @@ -136,6 +137,9 @@ const server = Bun.serve({ await checkStatistics(); statistics['scale POST']++; const data = await req.json(); + if (!validateId(data.id)) { + return response('Bad identifier', {status: 400}); + } // Convert dashes to something more bash friendly. const id = (data.id as string).replaceAll('-', 'å'); const envelope = cleanAndValidateEnvelope(data.envelope); @@ -170,22 +174,23 @@ const server = Bun.serve({ await checkStatistics(); statistics['scale GET']++; - // Convert dashes to something more bash friendly. - const {dir, base, ext} = parse(path.replaceAll('-', 'å')); + const {dir, base, ext} = parse(path); if (dir !== '/scale' || base.includes('..')) { return response('Bad scale path', {status: 400}); } if (ext) { return response('Extensions have been depracated', {status: 400}); } - if (base.length > 255) { - return response('Scale id too long', {status: 414}); + if (!validateId(base)) { + return response('Bad identifier', {status: 400}); } - const filename = join(SCALE_PATH, base + '.json.gz'); + // Convert dashes to something more bash friendly. + const id = base.replaceAll('-', 'å'); + const filename = join(SCALE_PATH, id + '.json.gz'); const file = Bun.file(filename); - const count = statistics['scale GET by id'][base] ?? 0; - statistics['scale GET by id'][base] = count + 1; + const count = statistics['scale GET by id'][id] ?? 0; + statistics['scale GET by id'][id] = count + 1; const accept = req.headers.get('Accept-Encoding'); if ( diff --git a/tests/utils.spec.ts b/tests/utils.spec.ts new file mode 100644 index 0000000..6342de0 --- /dev/null +++ b/tests/utils.spec.ts @@ -0,0 +1,28 @@ +import {expect, it, describe} from 'bun:test'; +import {validateId} from '../utils'; + +describe('Identifier validator', () => { + it('accepts a short id', () => { + expect(validateId('spoob')).toBe(true); + }); + + it('accepts a typical id', () => { + expect(validateId('-riQ9Oj4W')).toBe(true); + }); + + it('rejects an id with slashes', () => { + expect(validateId('roob/crowspoob')).toBe(false); + }); + + it('rejects an empty id', () => { + expect(validateId('')).toBe(false); + }); + + it('rejects a long id', () => { + expect( + validateId( + 'aaeGEGJRJGAEGU234987897gfayhgf98ayg9yf9ydzf9b8d9zfby898zyfuiew98ry9we8yr98ay9fy8diguy98ydsfgyuisdyer89y938yruydifyu98dgiuydriygdryuoiusdygrgyd87ryg8d7ryg87dygiuydkxjhx9c80FASGFAESGaywe9r78y89y87yeg87y8e7yg87yeg87ya8ge7ya8eyg8aey7g87yage87aeg98ua9egu9agu98aue9g8urega87yarg87yar8g7yar87gy' + ) + ).toBe(false); + }); +}); diff --git a/utils.ts b/utils.ts new file mode 100644 index 0000000..aad8a08 --- /dev/null +++ b/utils.ts @@ -0,0 +1,8 @@ +const ID_RE = /^[a-z0-9-_]+$/i; + +export function validateId(id: string) { + if (id.length > 255) { + return false; + } + return Boolean(id.match(ID_RE)); +}