url = require 'url'
winston = require 'winston'
Q = require 'q'
Channel = (require '../../lib/model/channels').Channel
config = require '../config/config'
routerConf = config.get 'router'
utils = require '../../lib/utils'
router = require '../../lib/middleware/router'
invertPathTransform = (pathTransform) ->
# see https://regex101.com/r/lW0cN0/1 for an explanation of this regex
return pathTransform.replace /s\/(.*?)\/(.*?)(?:$|\/(.*)$)/, 's/$2/$1/$3'
exports.fetchRewriteConfig = (channel, authType, callback) ->
# set the user defined rewrite config from current channel
rwConfig = []
if channel.rewriteUrlsConfig?
rwConfig = rwConfig.concat channel.rewriteUrlsConfig
if channel.addAutoRewriteRules
###
# Add in default (virtual) rewrite rules for hosts we proxy
#
# For example if the SHR for some reason sent back a link to a patient in the CR
# (using a direct link to the CR), then if we have a channel that points to the
# CR on a primary route we are able to rewrite the link to point to us instead
# because we know that host.
###
utils.getAllChannelsInPriorityOrder (err, channels) ->
if err?
return callback err
for channel in channels
for route in channel.routes
do ->
if route.primary
###
# if this channel has a pathTranform on its primary route then
# invert the path transform so that links that point to this route
# have the path transform reversed when they are rewritten
#
# For example, say we have a channel with urlPattern=/CSD/ and a
# pathTransform on the primary route as follows pathTransform=s/CSD/ihris/
# (ie. the actual server we are proxying is running on http://<host>:<port>/ihirs/).
# If we get links back from this server it will be something like
# http://<host>:<port>/ihirs/something/123 but we need it to be
# http://<him_host>:<him_port>/CSD/something/123. To do this we can reverse
# the pathTransform on the route (s/ihris/CSD/) and apply it while doing the
# rewrite.
###
if route.pathTransform
inverseTransform = invertPathTransform route.pathTransform
# rewrite to the secure port if tls was used for this transaction
if authType? is 'tls'
toPort = routerConf.httpsPort
else
toPort = routerConf.httpPort
# add 'virtual' rewrite config after any user defined config that has been set
rwConfig.push
'fromHost': route.host
'toHost': routerConf.externalHostname
'fromPort': route.port
'toPort': toPort
'pathTransform': if inverseTransform then inverseTransform else null
callback null, rwConfig
else
callback null, rwConfig
rewriteUrls = (body, channel, authType, callback) ->
exports.fetchRewriteConfig channel, authType, (err, rwConfig) ->
if err?
return callback err
# rewrite each found href, src or fullUrl attribute (in JSON or XML)
# See https://regex101.com/r/uY3fO1/1 for an explanation of this regex
newBody = body.replace /["|']?(?:href|src|fullUrl)["|']?[:|=]\s?["|'](\S*?)["|']/g, (match, hrefUrl) ->
hrefUrlObj = url.parse hrefUrl
# default to using this channel's host if no host so we can match a rewrite rule
if not hrefUrlObj.host?
for route in channel.routes
if route.primary
hrefUrlObj.hostname = route.host
hrefUrlObj.port = route.port.toString()
relativePath = true
break
for rewriteRule in rwConfig
# if we find a matching rewrite rule
if rewriteRule.fromHost.toLowerCase() is hrefUrlObj.hostname and (rewriteRule.fromPort.toString() is hrefUrlObj.port or (rewriteRule.fromPort is 80 and hrefUrlObj.port is null))
hrefUrlObj.host = null # so that hostname and port are used separately
hrefUrlObj.hostname = rewriteRule.toHost
hrefUrlObj.port = rewriteRule.toPort
# rewrite protocol depending on the port the rewriteRule uses
if hrefUrlObj.protocol
if rewriteRule.toPort is routerConf.httpsPort
hrefUrlObj.protocol = 'https'
else
hrefUrlObj.protocol = 'http'
# if this rewrite rule requires the path to be transformed then do the transform
if rewriteRule.pathTransform
hrefUrlObj.pathname = router.transformPath hrefUrlObj.pathname, rewriteRule.pathTransform
# we only run the first matching rule found
break
if relativePath # remove the host stuff before formating
hrefUrlObj.host = null
hrefUrlObj.hostname = null
hrefUrlObj.port = null
# replace the url in the match
replacement = url.format hrefUrlObj
winston.debug "Rewriting url #{hrefUrl} as #{replacement}"
return match.replace hrefUrl, replacement
callback null, newBody
if process.env.NODE_ENV is 'test'
exports.invertPathTransform = invertPathTransform
exports.rewriteUrls = rewriteUrls
exports.koaMiddleware = (next) ->
# do nothing to the request
yield next
# on response rewrite urls
if this.authorisedChannel.rewriteUrls
rewrite = Q.denodeify rewriteUrls
this.response.body = yield rewrite this.response.body.toString(), this.authorisedChannel, this.authenticationType
winston.info "Rewrote url in the response of transaction: #{this.transactionId}"
|