Skip to content

Commit

Permalink
GTFS Schedule stop lookup: escape HaltID πŸ›βœ…πŸ”’
Browse files Browse the repository at this point in the history
  • Loading branch information
derhuerst committed Aug 29, 2024
1 parent 97cd1ca commit 09a7315
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 1 deletion.
17 changes: 16 additions & 1 deletion lib/gtfs-stop-by-aus-haltid.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {strictEqual} from 'node:assert/strict'
import QuickLRU from 'quick-lru'
import {connectToPostgres} from './db.js'

Expand All @@ -9,6 +10,20 @@ const stripDataProviderPrefixFromAusHaltID = (ausHaltId) => {
: ausHaltId
}

// > If pattern does not contain percent signs or underscore, then the pattern only represents the string itself; in that case LIKE acts like the equals operator. An underscore (_) in pattern stands for (matches) any single character; a percent sign (%) matches any string of zero or more characters.
// > To match a literal underscore or percent sign without matching other characters, the respective character in pattern must be preceded by the escape character. […]
// > https://www.postgresql.org/docs/7.3/functions-matching.html
const escapeForLikeOp = (input) => {
return input
.replaceAll('\\', '\\\\')
.replaceAll('%', '\\%')
.replaceAll('_', '\\_')
}
strictEqual(
escapeForLikeOp('foo\\bar\\\\baz%hey_there'),
'foo\\\\bar\\\\\\\\baz\\%hey\\_there',
)

const createQueryGtfsStopByAusHaltID = async (cfg, opt = {}) => {
const {
logger,
Expand Down Expand Up @@ -46,7 +61,7 @@ LIMIT 2
// By requiring stop IDs to end with the (stripped) HaltID (e.g. `de:12063:900210771` "Rathenow, Bahnhof"), effectively we only obtain stations (stops witout parent).
// [0] https://en.wikipedia.org/wiki/Identification_of_Fixed_Objects_in_Public_Transport
// [1] https://www.delfi.de/de/strategie-technik/architektur/
'%:' + strippedHaltId,
`%:${escapeForLikeOp(strippedHaltId)}`,
],
})

Expand Down
22 changes: 22 additions & 0 deletions test/gtfs-stop-by-aus-haltid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {test, after} from 'node:test'
import {deepStrictEqual} from 'node:assert'
import {createLogger} from '../lib/logger.js'
import {createQueryGtfsStopByAusHaltID} from '../lib/gtfs-stop-by-aus-haltid.js'

const {
uncachedQueryGtfsStopByAusHaltID: queryGtfsStopByAusHaltID,
stop,
} = await createQueryGtfsStopByAusHaltID({
logger: createLogger('gtfs-stop-by-aus-haltid-test', {
level: 'fatal',
})
})
after(async () => {
await stop()
})

test('does not allow `%` SQL injections', async (t) => {
// There is only one stop with a stop_id matching `%:90044994%`, so if we insert `90044994%` unescaped into a `LIKE '%' || $1` query, we obtain a non-ambiguous result through injection.
const stop = await queryGtfsStopByAusHaltID('90044994%')
deepStrictEqual(stop, null, 'must not return a stop')
})
1 change: 1 addition & 0 deletions test/index.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ NODE_ENV=production build-gtfs-match-index \

# ---

node gtfs-stop-by-aus-haltid.js
node vdv-aus-istfahrt-as-fptf-trip.js
node fptf-trip-as-gtfs-rt-tripupdate.js

0 comments on commit 09a7315

Please sign in to comment.