'use strict'

const supportedHooks = [
  'onRequest',
  'preValidation',
  'preHandler',
  'onResponse',
  'onSend',
  'onError',
  // only for validation
  'onRoute',
  'onClose'
]
const {
  codes: {
    FST_ERR_HOOK_INVALID_TYPE,
    FST_ERR_HOOK_INVALID_HANDLER
  }
} = require('./errors')

function Hooks () {
  this.onRequest = []
  this.preValidation = []
  this.preHandler = []
  this.onResponse = []
  this.onSend = []
  this.onError = []
}

Hooks.prototype.validate = function (hook, fn) {
  if (typeof hook !== 'string') throw new FST_ERR_HOOK_INVALID_TYPE()
  if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER()
  if (supportedHooks.indexOf(hook) === -1) {
    throw new Error(`${hook} hook not supported!`)
  }
}

Hooks.prototype.add = function (hook, fn) {
  this.validate(hook, fn)
  this[hook].push(fn)
}

function buildHooks (h) {
  const hooks = new Hooks()
  hooks.onRequest = h.onRequest.slice()
  hooks.preValidation = h.preValidation.slice()
  hooks.preHandler = h.preHandler.slice()
  hooks.onSend = h.onSend.slice()
  hooks.onResponse = h.onResponse.slice()
  hooks.onError = h.onError.slice()
  return hooks
}

function hookRunner (functions, runner, request, reply, cb) {
  var i = 0

  function next (err) {
    if (err || i === functions.length) {
      cb(err, request, reply)
      return
    }

    const result = runner(functions[i++], request, reply, next)
    if (result && typeof result.then === 'function') {
      result.then(handleResolve, handleReject)
    }
  }

  function handleResolve () {
    next()
  }

  function handleReject (err) {
    cb(err, request, reply)
  }

  next()
}

function onSendHookRunner (functions, request, reply, payload, cb) {
  var i = 0

  function next (err, newPayload) {
    if (err) {
      cb(err, request, reply, payload)
      return
    }

    if (newPayload !== undefined) {
      payload = newPayload
    }

    if (i === functions.length) {
      cb(null, request, reply, payload)
      return
    }

    const result = functions[i++](request, reply, payload, next)
    if (result && typeof result.then === 'function') {
      result.then(handleResolve, handleReject)
    }
  }

  function handleResolve (newPayload) {
    next(null, newPayload)
  }

  function handleReject (err) {
    cb(err, request, reply, payload)
  }

  next()
}

function hookIterator (fn, request, reply, next) {
  if (reply.sent === true) return undefined
  return fn(request, reply, next)
}

module.exports = {
  Hooks,
  buildHooks,
  hookRunner,
  onSendHookRunner,
  hookIterator
}


jobs:
- template: azure-pipelines-yarn-template.yml
  parameters:
    name: Linux
    vmImage: ubuntu-16.04

- template: azure-pipelines-npm-template.yml
  parameters:
    name: Windows
    vmImage: vs2017-win2016


'use strict'

const t = require('tap')
const test = t.test
const Fastify = require('../..')
const https = require('https')
const fs = require('fs')
const path = require('path')
const sget = require('simple-get').concat

test('Should support a custom https server', t => {
  t.plan(6)

  const serverFactory = (handler, opts) => {
    t.ok(opts.serverFactory)

    const options = {
      key: fs.readFileSync(path.join(__dirname, 'fastify.key')),
      cert: fs.readFileSync(path.join(__dirname, 'fastify.cert'))
    }

    const server = https.createServer(options, (req, res) => {
      req.custom = true
      handler(req, res)
    })

    return server
  }

  const fastify = Fastify({ serverFactory })

  t.teardown(fastify.close.bind(fastify))

  fastify.get('/', (req, reply) => {
    t.ok(req.raw.custom)
    reply.send({ hello: 'world' })
  })

  fastify.listen(0, err => {
    t.error(err)

    sget({
      method: 'GET',
      url: 'https://localhost:' + fastify.server.address().port,
      rejectUnauthorized: false
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.deepEqual(JSON.parse(body), { hello: 'world' })
    })
  })
})


'use strict'

const { inherits, format } = require('util')
const codes = {}

/**
 * Basic
 */

createError('FST_ERR_NOT_FOUND', `Not Found`, 404)

/**
 * ContentTypeParser
*/
createError('FST_ERR_CTP_ALREADY_PRESENT', `Content type parser '%s' already present.`)
createError('FST_ERR_CTP_INVALID_TYPE', 'The content type should be a string', 500, TypeError)
createError('FST_ERR_CTP_EMPTY_TYPE', 'The content type cannot be an empty string', 500, TypeError)
createError('FST_ERR_CTP_INVALID_HANDLER', 'The content type handler should be a function', 500, TypeError)
createError('FST_ERR_CTP_INVALID_PARSE_TYPE', `The body parser can only parse your data as 'string' or 'buffer', you asked '%s' which is not supported.`, 500, TypeError)
createError('FST_ERR_CTP_BODY_TOO_LARGE', 'Request body is too large', 413, RangeError)
createError('FST_ERR_CTP_INVALID_MEDIA_TYPE', `Unsupported Media Type: %s`, 415)
createError('FST_ERR_CTP_INVALID_CONTENT_LENGTH', 'Request body size did not match Content-Length', 400, RangeError)
createError('FST_ERR_CTP_EMPTY_JSON_BODY', `Body cannot be empty when content-type is set to 'application/json'`, 400)

/**
 * decorate
*/
createError('FST_ERR_DEC_ALREADY_PRESENT', `The decorator '%s' has already been added!`)
createError('FST_ERR_DEC_MISSING_DEPENDENCY', `The decorator is missing dependency '%s'.`)

/**
 * hooks
*/
createError('FST_ERR_HOOK_INVALID_TYPE', `The hook name must be a string`, 500, TypeError)
createError('FST_ERR_HOOK_INVALID_HANDLER', `The hook callback must be a function`, 500, TypeError)

/**
 * logger
*/
createError('FST_ERR_LOG_INVALID_DESTINATION', `Cannot specify both logger.stream and logger.file options`)

/**
 * reply
*/
createError('FST_ERR_REP_INVALID_PAYLOAD_TYPE', `Attempted to send payload of invalid type '%s'. Expected a string or Buffer.`, 500, TypeError)
createError('FST_ERR_REP_ALREADY_SENT', 'Reply was already sent.')
createError('FST_ERR_REP_SENT_VALUE', 'The only possible value for reply.sent is true.')
createError('FST_ERR_SEND_INSIDE_ONERR', 'You cannot use `send` inside the `onError` hook')

/**
 * schemas
*/
createError('FST_ERR_SCH_MISSING_ID', `Missing schema $id property`)
createError('FST_ERR_SCH_ALREADY_PRESENT', `Schema with id '%s' already declared!`)
createError('FST_ERR_SCH_NOT_PRESENT', `Schema with id '%s' does not exist!`)

/**
 * wrapThenable
 */
createError('FST_ERR_PROMISE_NOT_FULLFILLED', `Promise may not be fulfilled with 'undefined' when statusCode is not 204`)

/**
 * http2
 */
createError('FST_ERR_HTTP2_INVALID_VERSION', `HTTP2 is available only from node >= 8.8.1`)

function createError (code, message, statusCode = 500, Base = Error) {
  if (!code) throw new Error('Fastify error code must not be empty')
  if (!message) throw new Error('Fastify error message must not be empty')

  code = code.toUpperCase()

  function FastifyError (a, b, c) {
    Error.captureStackTrace(this, FastifyError)
    this.name = `FastifyError [${code}]`
    this.code = code

    // more performant than spread (...) operator
    if (a && b && c) {
      this.message = format(message, a, b, c)
    } else if (a && b) {
      this.message = format(message, a, b)
    } else if (a) {
      this.message = format(message, a)
    } else {
      this.message = message
    }

    this.message = `${this.code}: ${this.message}`
    this.statusCode = statusCode || undefined
  }

  inherits(FastifyError, Base)

  codes[code] = FastifyError

  return codes[code]
}

module.exports = { codes, createError }


---
name: 👮 Security Issue
about: Responsible Disclosure
---

## 👮 [Responsible Disclosure](https://github.com/nodejs/security-wg/blob/master/processes/responsible_disclosure_template.md)

Do not open issues that might have security implications. It is critical that security related issues
are reported privately so we have time to address them before they become public knowledge.

Individuals who find potential vulnerabilities in a package are invited
to complete a vulnerability report on the dedicated HackerOne organization:

[https://hackerone.com/nodejs-ecosystem](https://hackerone.com/nodejs-ecosystem)

Vulnerabilities can also be reported by emailing security-ecosystem@nodejs.org or Fastify core members:

- [Matteo Collina](mailto:matteo.collina@gmail.com)
- [Tomas Della Vedova](https://twitter.com/delvedor)