All files / src/middleware rewriteUrls.coffee

28% Statements 14/50
0% Branches 0/8
0% Functions 0/8
28.57% Lines 14/49
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 1371x 1x 1x   1x 1x 1x 1x 1x   1x       1x                                                                                                                   1x                                                                                                         1x 1x   1x                
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}"