# All files / src/middleware requestMatching.coffee

42.35% Statements 36/85
6.25% Branches 1/16
0% Functions 0/16
41.67% Lines 35/84

    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
    137
    1382x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x

    2x
    2x
    2x
    2x
    2x

    2x
    2x

    2x

    2x

    2x

    2x

    2x

    2x

    2x

    2x

    2x

    2x

    2x

    2x

    2x

    2x
    2x
    2x
    2x
    2x
    2x
     Q = require "q"
    xpath = require "xpath"
    dom = require("xmldom").DOMParser
    logger = require "winston"
    config = require '../config/config'
    utils = require '../utils'
    auditing = require '../auditing'
    Channels = require('../model/channels')
    Channel = Channels.Channel

    statsdServer = config.get 'statsd'
    application = config.get 'application'
    himSourceID = config.get('auditing').auditEvents.auditSourceID
    SDC = require 'statsd-client'
    os = require 'os'

    domain = "#{os.hostname()}.#{application.name}.appMetrics"
    sdc = new SDC statsdServer

    matchContent = (channel, ctx) ->
      if channel.matchContentRegex
        return matchRegex channel.matchContentRegex, ctx.body
      else if channel.matchContentXpath and channel.matchContentValue
        return matchXpath channel.matchContentXpath, channel.matchContentValue, ctx.body
      else if channel.matchContentJson and channel.matchContentValue
        return matchJsonPath channel.matchContentJson, channel.matchContentValue, ctx.body
      else if channel.matchContentXpath or 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

    matchRegex = (regexPat, body) ->
      regex = new RegExp regexPat
      return regex.test body.toString()

    matchXpath = (xpathStr, val, xml) ->
      doc = new dom().parseFromString(xml.toString())
      xpathVal = xpath.select(xpathStr, doc).toString()
      return val == xpathVal

    matchJsonPath = (jsonPath, val, json) ->
      jsonObj = JSON.parse json.toString()
      jsonVal = getJSONValByString jsonObj, jsonPath
      return val == jsonVal.toString()

    # taken from http://stackoverflow.com/a/6491621/588776
    # readbility improved from the stackoverflow answer
    getJSONValByString = (jsonObj, jsonPath) ->
      jsonPath = jsonPath.replace(/\[(\w+)\]/g, '.$1')  # convert indexes to properties
      jsonPath = jsonPath.replace(/^\./, '')            # strip a leading dot
      parts = jsonPath.split('.')
      while parts.length
        part = parts.shift()
        if part of jsonObj
          jsonObj = jsonObj[part]
        else
          return
      return jsonObj

    extractContentType = (ctHeader) ->
      index = ctHeader.indexOf ';'
      if index isnt -1
        return ctHeader.substring(0, index).trim()
      else
        return ctHeader.trim()

    matchUrlPattern = (channel, ctx) ->
      pat = new RegExp channel.urlPattern
      return pat.test ctx.request.path

    matchContentTypes = (channel, ctx) ->
      if channel.matchContentTypes?.length > 0
        if ctx.request.header and ctx.request.header['content-type']
          ct = extractContentType ctx.request.header['content-type']
          if ct in channel.matchContentTypes
            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

    matchFunctions = [
      matchUrlPattern,
      matchContent,
      matchContentTypes
    ]

    matchChannel = (channel, ctx) ->
      matchFunctions.every (matchFunc) ->
        return matchFunc channel, ctx

    findMatchingChannel = (channels, ctx) ->
      return channels.find (channel) ->
        return matchChannel channel, ctx

    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

        match = findMatchingChannel channels, ctx
        done null, match

    exports.koaMiddleware = (next) ->
      startTime = new Date() if statsdServer.enabled
      matchReq = Q.denodeify matchRequest
      match = yield matchReq this

      if match?
        logger.info "The channel that matches the request #{this.request.path} is: #{match.name}"
        this.matchingChannel = match
      else
        logger.info "No channel matched the request #{this.request.path}"

      sdc.timing "#{domain}.authorisationMiddleware", startTime if statsdServer.enabled
      yield 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