openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
143 lines (124 loc) • 4.52 kB
JavaScript
import xpath from 'xpath'
import { DOMParser as Dom } from 'xmldom'
import logger from 'winston'
import { config } from '../config'
import * as utils from '../utils'
import * as Channels from '../model/channels'
import { promisify } from 'util'
function matchContent (channel, ctx) {
if (channel.matchContentRegex) {
return matchRegex(channel.matchContentRegex, ctx.body)
} else if (channel.matchContentXpath && channel.matchContentValue) {
return matchXpath(channel.matchContentXpath, channel.matchContentValue, ctx.body)
} else if (channel.matchContentJson && channel.matchContentValue) {
return matchJsonPath(channel.matchContentJson, channel.matchContentValue, ctx.body)
} else if (channel.matchContentXpath || channel.matchContentJson) {
// if only the match expression is given, deny access
// this is an invalid channel
logger.error(`Channel with name '${channel.name}' is invalid as it has a content match expression but no value to match`)
return false
} else {
return true
}
}
function matchRegex (regexPat, body) {
const regex = new RegExp(regexPat)
return regex.test(body.toString())
}
function matchXpath (xpathStr, val, xml) {
const doc = new Dom().parseFromString(xml.toString())
const xpathVal = xpath.select(xpathStr, doc).toString()
return val === xpathVal
}
function matchJsonPath (jsonPath, val, json) {
const jsonObj = JSON.parse(json.toString())
const jsonVal = getJSONValByString(jsonObj, jsonPath)
return val === jsonVal.toString()
}
// taken from http://stackoverflow.com/a/6491621/588776
// readbility improved from the stackoverflow answer
function getJSONValByString (jsonObj, jsonPath) {
jsonPath = jsonPath.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties
jsonPath = jsonPath.replace(/^\./, '') // strip a leading dot
const parts = jsonPath.split('.')
while (parts.length) {
const part = parts.shift()
if (part in jsonObj) {
jsonObj = jsonObj[part]
} else {
return
}
}
return jsonObj
}
function extractContentType (ctHeader) {
const index = ctHeader.indexOf(';')
if (index !== -1) {
return ctHeader.substring(0, index).trim()
} else {
return ctHeader.trim()
}
}
function matchUrlPattern (channel, ctx) {
const pat = new RegExp(channel.urlPattern)
return pat.test(ctx.request.path)
}
function matchContentTypes (channel, ctx) {
if ((channel.matchContentTypes != null ? channel.matchContentTypes.length : undefined) > 0) {
if (ctx.request.header && ctx.request.header['content-type']) {
const ct = extractContentType(ctx.request.header['content-type'])
if (Array.from(channel.matchContentTypes).includes(ct)) {
return true
} else {
// deny access to channel if the content type doesnt match
return false
}
} else {
// deny access to channel if the content type isnt set
return false
}
} else {
return true // don't match on content type if this channel doesn't require it
}
}
// Needs to be mutable for testing
// eslint-disable-next-line
let matchFunctions = [
matchUrlPattern,
matchContent,
matchContentTypes
]
const matchChannel = (channel, ctx) => matchFunctions.every(matchFunc => matchFunc(channel, ctx))
const findMatchingChannel = (channels, ctx) => channels.find(channel => matchChannel(channel, ctx))
const matchRequest = (ctx, done) =>
utils.getAllChannelsInPriorityOrder((err, channels) => {
if (err) {
ctx.response.status = 500
logger.error('Could not fetch OpenHIM channels', err)
return done()
}
channels = channels.filter(Channels.isChannelEnabled)
const match = findMatchingChannel(channels, ctx)
return done(null, match)
})
export async function koaMiddleware (ctx, next) {
const matchReq = promisify(matchRequest)
const match = await matchReq(ctx)
if (match != null) {
logger.info(`The channel that matches the request ${ctx.request.path} is: ${match.name}`)
ctx.matchingChannel = match
} else {
logger.info(`No channel matched the request ${ctx.request.path}`)
}
await next()
}
// export private functions for unit testing
// note: you cant spy on these method because of this :(
if (process.env.NODE_ENV === 'test') {
exports.matchContent = matchContent
exports.matchRegex = matchRegex
exports.matchXpath = matchXpath
exports.matchJsonPath = matchJsonPath
exports.extractContentType = extractContentType
exports.matchRequest = matchRequest
}