# 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}"