<commit_before>#!/usr/bin/env node

const escapeHTML = require('escape-html')
const fs = require('fs')
const {JSDOM, VirtualConsole} = require('jsdom')
const prerender = require('prerender')
const querystring = require('querystring')
const r2 = require('r2')
const {Readability} = require('readability/index')
const {spawn, spawnSync} = require('child_process')
const tldjs = require('tldjs')
const tmp = require('tmp')
const URL = require('url')

const {findNextPageLink} = require('./readability.js')

require('prerender/lib/util').log = console.error

function removeNode (node) {
  node.parentNode.removeChild(node)
}

function removeNodes (nodes) {
  for (let i = nodes.length - 1; i >= 0; i--) removeNode(nodes[i])
}

function nonempty (node) {
  return node.textContent.trim() || node.querySelector('img')
}

async function waitProcess (proc) {
  return new Promise(function (resolve, reject) {
    proc.on('close', function (code) {
      if (code === 0) {
        resolve()
      } else {
        reject(new Error(`child process exited with code ${code}`))
      }
    })
  })
}

async function eof (stream) {
  return new Promise(function (resolve, reject) {
    stream.on('end', resolve)
    stream.on('error', reject)
  })
}

async function readStream (stream) {
  let chunks = []
  stream.setEncoding('utf8')
  stream.on('data', [].push.bind(chunks))
  await eof(stream)
  return chunks.join('')
}

function applyFilterList (fname, body, domain) {
  let classes = new Set()
  let ids = new Set()
  let selectors = []
  let domainSelectors = {}

  let lines = fs.readFileSync(fname, 'ascii').split('\n')
  for (const line of lines) {
    let rule = line.split('##') // cosmetic filters
    if (rule.length !== 2) continue
    let [domains, selector] = rule

    // skip rules that trigger syntax errors
    if (selector.includes('onclick^') || selector.includes('[body]')) continue

    if (domains === '') { // generic filter
      if (selector.startsWith('.')) {
        classes.add(selector.slice(1))
      } else if (selector.startsWith('#')) {
        ids.add(selector.slice(1))
      } else {
        selectors.push(selector)
      }
      continue
    }

    for (const domain of domains.split(',')) {
      if (!domainSelectors[domain]) domainSelectors[domain] = []
      domainSelectors[domain].push(selector)
    }
  }

  let nodes = body.querySelectorAll('[class]')
  for (let i = nodes.length - 1; i >= 0; i--) {
    const node = nodes[i]
    for (let i = 0; i < node.classList.length; i++) {
      if (classes.has(node.classList[i])) {
        removeNode(node)
        break
      }
    }
  }

  nodes = body.querySelectorAll('[id]')
  for (let i = nodes.length - 1; i >= 0; i--) {
    if (ids.has(nodes[i].id)) removeNode(nodes[i])
  }

  removeNodes(body.querySelectorAll(selectors.join()))

  if (domainSelectors[domain]) {
    removeNodes(body.querySelectorAll(domainSelectors[domain].join()))
  }
}

const virtualConsole = new VirtualConsole()
virtualConsole.sendTo(console, {omitJSDOMErrors: true})
const options = {virtualConsole}

async function fromURL (url) {
  try {
    return await JSDOM.fromURL(url, options)
  } catch (e) {
    console.error('Retrying ' + url)
    return JSDOM.fromURL(url, options)
  }
}

let ignoreTitles = false
let postscript = ''
let allowPrerender = true
let pages = 1

async function peruse (window, loc) {
  let content = await preprocess(window, loc)
  await postprocess(content)
}

async function preprocess (window, loc) {
  if (loc === undefined) loc = window.location
  let document = window.document

  let canonical = document.querySelector('link[rel="canonical"]')
  if (loc.href === 'about:blank' && canonical) loc = URL.parse(canonical.href)
  canonical = document.querySelector('meta[property="og:url"]')
  if (loc.href === 'about:blank' && canonical) loc = URL.parse(canonical.content)

  let base = document.getElementsByTagName('base')[0]
  if (base) {
    base.href = URL.resolve(loc.href, base.href)
  } else {
    base = document.createElement('base')
    base.href = loc.href
    base = document.head.appendChild(base)
  }

  let domain = tldjs.getDomain(loc.href)
  applyFilterList('easylist/fanboy-annoyance.txt', document.body, domain)

  removeNodes(document.body.querySelectorAll('[class*="hidden"]'))

  let links = document.getElementsByTagName('a')
  for (let i = links.length - 1; i >= 0; i--) {
    let link = links[i]
    let href = link.getAttribute('href')
    if (href && href[0] === '#') {
      link.removeAttribute('href')
    }
  }

  links = document.querySelectorAll('pre a')
  for (let i = links.length - 1; i >= 0; i--) {
    let link = links[i]
    link.outerHTML = link.innerHTML
  }

  let nodes = document.querySelectorAll('a, em, strong, b, i')
  for (let i = nodes.length - 1; i >= 0; i--) {
    let node = nodes[i]
    if (!nonempty(node)) {
      node.outerHTML = node.innerHTML
    }
  }

  let images = document.getElementsByTagName('img')
  for (let i = images.length - 1; i >= 0; i--) {
    let image = images[i]
    if (image.getAttribute('src') === '' || image.width === 1) {
      removeNode(image)
    }
  }

  let tables = document.getElementsByTagName('table')
  for (let i = tables.length - 1; i >= 0; i--) {
    let table = tables[i]
    if (table.rows.length === 1 && table.rows[0].cells.length === 1) {
      table.outerHTML = table.rows[0].cells[0].innerHTML
    }
  }

  let items = document.getElementsByTagName('li')
  for (let i = items.length - 1; i >= 0; i--) {
    items[i].removeAttribute('id')
  }

  let codes = document.getElementsByTagName('code')
  for (let i = codes.length - 1; i >= 0; i--) {
    let code = codes[i]
    if (code.textContent.split('\n').length > 2) {
      code.outerHTML = '
' + code.outerHTML + '
'
    }
  }

  let figs = document.getElementsByTagName('figure')
  for (let i = figs.length - 1; i >= 0; i--) {
    let fig = figs[i]
    if (fig.getElementsByTagName('figcaption').length === 0) {
      fig.outerHTML = '
' + fig.innerHTML + '
'
    }
  }

  if (ignoreTitles) removeNodes(document.getElementsByTagName('title'))

  let breaks = document.querySelectorAll(
    'h1 br, h2 br, h3 br, h4 br, h5 br, h6 br')
  for (let i = breaks.length - 1; i >= 0; i--) {
    breaks[i].outerHTML = ' '
  }

  let waybackToolbar = document.getElementById('wm-ipp')
  if (waybackToolbar) removeNode(waybackToolbar)

  removeNodes(document.getElementsByClassName('mw-editsection'))

  let span
  while ((span = document.querySelector('span'))) {
    span.outerHTML = span.innerHTML
  }

  let content = document.documentElement.outerHTML

  let nextPageLink = findNextPageLink(loc, document.body)
  if (nextPageLink) {
    links = document.getElementsByTagName('a')
    for (let i = links.length - 1; i >= 0; i--) {
      if (links[i].href === nextPageLink + '/') {
        nextPageLink = nextPageLink + '/'
        break
      }
    }
  }

  let readability = new Readability(null, document)
  let article = readability.parse()

  if (article && article.content.replace(/<.*?>/g, '').length > 200) {
    content = ''
    if (pages === 1) content += '
' + escapeHTML(article.title) + '
'
    content += article.content
      .replace(/<(embed|iframe|video|audio) /g, '<img ')
      .replace(/
([^<]*)<\/p>/g, '$1')
  } else if (allowPrerender) {
    allowPrerender = false
    const server = prerender()
    await server.startPrerender()
    let dom = await fromURL('http://localhost:3000/render?' + querystring.stringify({
      url: loc.href,
      renderType: 'html',
      followRedirects: true,
      javascript: ''
    }))
    content = await preprocess(dom.window, loc)
    server.killBrowser()
  }

  if (nextPageLink && pages < 10) {
    pages += 1
    console.error(`Fetching page ${pages} from ${nextPageLink}`)
    let dom = await fromURL(nextPageLink)
    content += await preprocess(dom.window)
  } else if (nextPageLink) {
    content += '
Next Page
'
  }

  return content
}

async function postprocess (content) {
  if (!content.includes('<h2')) {
    content = content
      .replace(/
(.*?)<\/h3>/g, '
$1
')
      .replace(/
(.*?)<\/h4>/g, '
$1
')
      .replace(/
(.*?)<\/h5>/g, '
$1
')
      .replace(/
(.*?)<\/h6>/g, '
$1
')
  }
  content += postscript

  let markdown = [
    'markdown',
    '-bracketed_spans',
    '-citations',
    '-escaped_line_breaks',
    '-fenced_code_attributes',
    '-fenced_divs',
    '-header_attributes',
    '-inline_code_attributes',
    '-link_attributes',
    '-multiline_tables',
    '-native_divs',
    '-native_spans',
    '-pipe_tables',
    '-raw_html',
    '-simple_tables'
  ].join('')

  let convert = spawn('pandoc', [
    '-f', 'html',
    '-t', markdown + '-smart',
    '--reference-links'
  ], {
    stdio: ['pipe', 'pipe', process.stderr]
  })

  let normalise = spawn('pandoc', [
    '-f', markdown,
    '-t', markdown + '-smart',
    '--reference-links'
  ].concat(process.argv.slice(3)), {
    stdio: ['pipe', 'pipe', process.stderr]
  })

  convert.stdin.end(content)
  convert.stdout.pipe(normalise.stdin)
  if (process.stdout.isTTY) {
    let output = await readStream(normalise.stdout)
    if (output.split(/\n/).length >= process.stdout.rows) {
      let less = spawn('less', [], {
        stdio: ['pipe', process.stdout, process.stderr]
      })
      less.stdin.end(output)
      await waitProcess(less)
    } else {
      process.stdout.write(output)
    }
  } else {
    normalise.stdout.pipe(process.stdout)
    await eof(normalise.stdout)
  }
}

const HN_URL = 'https://news.ycombinator.com/item?id='
async function hn (id) {
  return r2('https://hacker-news.firebaseio.com/v0/item/' + id + '.json').json
}

async function main (url) {
  if (url.endsWith('.pdf')) {
    let tmpdir = tmp.dirSync().name
    spawnSync('pdftohtml', ['-c', '-s', '-i', url, tmpdir + '/out'], {stdio: 'ignore'})
    url = tmpdir + '/out-html.html'
    ignoreTitles = true
  }

  if (url.startsWith(HN_URL)) {
    let item = await hn(url.replace(HN_URL, ''))
    if (item.kids.length > 0) postscript += '
Comments
'
    for (let i = 0; i < 3 && i < item.kids.length; i++) {
      let comment = await hn(item.kids[i])
      postscript += '
' + comment.text + '
— ' + comment.by + '
'
    }
    url = item.url
  }

  url = url.replace('#!', '?_escaped_fragment_=')

  let parsed = URL.parse(url)
  if (parsed.protocol && parsed.hostname) {
    let dom = await fromURL(url)
    await peruse(dom.window)
  } else if (url.endsWith('.html') && fs.existsSync(url)) {
    let html = fs.readFileSync(url, 'utf8')
    let dom = new JSDOM(html, options)
    await peruse(dom.window)
  } else {
    console.error(url + ': unrecognised input')
    return 1
  }

  setTimeout(process.exit, 100)
  return 0
}

main.apply(null, process.argv.slice(2)).then(function (code) { process.exitCode = code })
<commit_msg>bugfix<commit_after>#!/usr/bin/env node

const escapeHTML = require('escape-html')
const fs = require('fs')
const {JSDOM, VirtualConsole} = require('jsdom')
const path = require('path')
const prerender = require('prerender')
const querystring = require('querystring')
const r2 = require('r2')
const {Readability} = require('readability/index')
const {spawn, spawnSync} = require('child_process')
const tldjs = require('tldjs')
const tmp = require('tmp')
const URL = require('url')

const {findNextPageLink} = require('./readability.js')

require('prerender/lib/util').log = console.error

function removeNode (node) {
  node.parentNode.removeChild(node)
}

function removeNodes (nodes) {
  for (let i = nodes.length - 1; i >= 0; i--) removeNode(nodes[i])
}

function nonempty (node) {
  return node.textContent.trim() || node.querySelector('img')
}

async function waitProcess (proc) {
  return new Promise(function (resolve, reject) {
    proc.on('close', function (code) {
      if (code === 0) {
        resolve()
      } else {
        reject(new Error(`child process exited with code ${code}`))
      }
    })
  })
}

async function eof (stream) {
  return new Promise(function (resolve, reject) {
    stream.on('end', resolve)
    stream.on('error', reject)
  })
}

async function readStream (stream) {
  let chunks = []
  stream.setEncoding('utf8')
  stream.on('data', [].push.bind(chunks))
  await eof(stream)
  return chunks.join('')
}

function applyFilterList (fname, body, domain) {
  let classes = new Set()
  let ids = new Set()
  let selectors = []
  let domainSelectors = {}

  let lines = fs.readFileSync(path.resolve(__dirname, fname), 'ascii').split('\n')
  for (const line of lines) {
    let rule = line.split('##') // cosmetic filters
    if (rule.length !== 2) continue
    let [domains, selector] = rule

    // skip rules that trigger syntax errors
    if (selector.includes('onclick^') || selector.includes('[body]')) continue

    if (domains === '') { // generic filter
      if (selector.startsWith('.')) {
        classes.add(selector.slice(1))
      } else if (selector.startsWith('#')) {
        ids.add(selector.slice(1))
      } else {
        selectors.push(selector)
      }
      continue
    }

    for (const domain of domains.split(',')) {
      if (!domainSelectors[domain]) domainSelectors[domain] = []
      domainSelectors[domain].push(selector)
    }
  }

  let nodes = body.querySelectorAll('[class]')
  for (let i = nodes.length - 1; i >= 0; i--) {
    const node = nodes[i]
    for (let i = 0; i < node.classList.length; i++) {
      if (classes.has(node.classList[i])) {
        removeNode(node)
        break
      }
    }
  }

  nodes = body.querySelectorAll('[id]')
  for (let i = nodes.length - 1; i >= 0; i--) {
    if (ids.has(nodes[i].id)) removeNode(nodes[i])
  }

  removeNodes(body.querySelectorAll(selectors.join()))

  if (domainSelectors[domain]) {
    removeNodes(body.querySelectorAll(domainSelectors[domain].join()))
  }
}

const virtualConsole = new VirtualConsole()
virtualConsole.sendTo(console, {omitJSDOMErrors: true})
const options = {virtualConsole}

async function fromURL (url) {
  try {
    return await JSDOM.fromURL(url, options)
  } catch (e) {
    console.error('Retrying ' + url)
    return JSDOM.fromURL(url, options)
  }
}

let ignoreTitles = false
let postscript = ''
let allowPrerender = true
let pages = 1

async function peruse (window, loc) {
  let content = await preprocess(window, loc)
  await postprocess(content)
}

async function preprocess (window, loc) {
  if (loc === undefined) loc = window.location
  let document = window.document

  let canonical = document.querySelector('link[rel="canonical"]')
  if (loc.href === 'about:blank' && canonical) loc = URL.parse(canonical.href)
  canonical = document.querySelector('meta[property="og:url"]')
  if (loc.href === 'about:blank' && canonical) loc = URL.parse(canonical.content)

  let base = document.getElementsByTagName('base')[0]
  if (base) {
    base.href = URL.resolve(loc.href, base.href)
  } else {
    base = document.createElement('base')
    base.href = loc.href
    base = document.head.appendChild(base)
  }

  let domain = tldjs.getDomain(loc.href)
  applyFilterList('easylist/fanboy-annoyance.txt', document.body, domain)

  removeNodes(document.body.querySelectorAll('[class*="hidden"]'))

  let links = document.getElementsByTagName('a')
  for (let i = links.length - 1; i >= 0; i--) {
    let link = links[i]
    let href = link.getAttribute('href')
    if (href && href[0] === '#') {
      link.removeAttribute('href')
    }
  }

  links = document.querySelectorAll('pre a')
  for (let i = links.length - 1; i >= 0; i--) {
    let link = links[i]
    link.outerHTML = link.innerHTML
  }

  let nodes = document.querySelectorAll('a, em, strong, b, i')
  for (let i = nodes.length - 1; i >= 0; i--) {
    let node = nodes[i]
    if (!nonempty(node)) {
      node.outerHTML = node.innerHTML
    }
  }

  let images = document.getElementsByTagName('img')
  for (let i = images.length - 1; i >= 0; i--) {
    let image = images[i]
    if (image.getAttribute('src') === '' || image.width === 1) {
      removeNode(image)
    }
  }

  let tables = document.getElementsByTagName('table')
  for (let i = tables.length - 1; i >= 0; i--) {
    let table = tables[i]
    if (table.rows.length === 1 && table.rows[0].cells.length === 1) {
      table.outerHTML = table.rows[0].cells[0].innerHTML
    }
  }

  let items = document.getElementsByTagName('li')
  for (let i = items.length - 1; i >= 0; i--) {
    items[i].removeAttribute('id')
  }

  let codes = document.getElementsByTagName('code')
  for (let i = codes.length - 1; i >= 0; i--) {
    let code = codes[i]
    if (code.textContent.split('\n').length > 2) {
      code.outerHTML = '
' + code.outerHTML + '
'
    }
  }

  let figs = document.getElementsByTagName('figure')
  for (let i = figs.length - 1; i >= 0; i--) {
    let fig = figs[i]
    if (fig.getElementsByTagName('figcaption').length === 0) {
      fig.outerHTML = '
' + fig.innerHTML + '
'
    }
  }

  if (ignoreTitles) removeNodes(document.getElementsByTagName('title'))

  let breaks = document.querySelectorAll(
    'h1 br, h2 br, h3 br, h4 br, h5 br, h6 br')
  for (let i = breaks.length - 1; i >= 0; i--) {
    breaks[i].outerHTML = ' '
  }

  let waybackToolbar = document.getElementById('wm-ipp')
  if (waybackToolbar) removeNode(waybackToolbar)

  removeNodes(document.getElementsByClassName('mw-editsection'))

  let span
  while ((span = document.querySelector('span'))) {
    span.outerHTML = span.innerHTML
  }

  let content = document.documentElement.outerHTML

  let nextPageLink = findNextPageLink(loc, document.body)
  if (nextPageLink) {
    links = document.getElementsByTagName('a')
    for (let i = links.length - 1; i >= 0; i--) {
      if (links[i].href === nextPageLink + '/') {
        nextPageLink = nextPageLink + '/'
        break
      }
    }
  }

  let readability = new Readability(null, document)
  let article = readability.parse()

  if (article && article.content.replace(/<.*?>/g, '').length > 200) {
    content = ''
    if (pages === 1) content += '
' + escapeHTML(article.title) + '
'
    content += article.content
      .replace(/<(embed|iframe|video|audio) /g, '<img ')
      .replace(/
([^<]*)<\/p>/g, '$1')
  } else if (allowPrerender) {
    allowPrerender = false
    const server = prerender()
    await server.startPrerender()
    let dom = await fromURL('http://localhost:3000/render?' + querystring.stringify({
      url: loc.href,
      renderType: 'html',
      followRedirects: true,
      javascript: ''
    }))
    content = await preprocess(dom.window, loc)
    server.killBrowser()
  }

  if (nextPageLink && pages < 10) {
    pages += 1
    console.error(`Fetching page ${pages} from ${nextPageLink}`)
    let dom = await fromURL(nextPageLink)
    content += await preprocess(dom.window)
  } else if (nextPageLink) {
    content += '
Next Page
'
  }

  return content
}

async function postprocess (content) {
  if (!content.includes('<h2')) {
    content = content
      .replace(/
(.*?)<\/h3>/g, '
$1
')
      .replace(/
(.*?)<\/h4>/g, '
$1
')
      .replace(/
(.*?)<\/h5>/g, '
$1
')
      .replace(/
(.*?)<\/h6>/g, '
$1
')
  }
  content += postscript

  let markdown = [
    'markdown',
    '-bracketed_spans',
    '-citations',
    '-escaped_line_breaks',
    '-fenced_code_attributes',
    '-fenced_divs',
    '-header_attributes',
    '-inline_code_attributes',
    '-link_attributes',
    '-multiline_tables',
    '-native_divs',
    '-native_spans',
    '-pipe_tables',
    '-raw_html',
    '-simple_tables'
  ].join('')

  let convert = spawn('pandoc', [
    '-f', 'html',
    '-t', markdown + '-smart',
    '--reference-links'
  ], {
    stdio: ['pipe', 'pipe', process.stderr]
  })

  let normalise = spawn('pandoc', [
    '-f', markdown,
    '-t', markdown + '-smart',
    '--reference-links'
  ].concat(process.argv.slice(3)), {
    stdio: ['pipe', 'pipe', process.stderr]
  })

  convert.stdin.end(content)
  convert.stdout.pipe(normalise.stdin)
  if (process.stdout.isTTY) {
    let output = await readStream(normalise.stdout)
    if (output.split(/\n/).length >= process.stdout.rows) {
      let less = spawn('less', [], {
        stdio: ['pipe', process.stdout, process.stderr]
      })
      less.stdin.end(output)
      await waitProcess(less)
    } else {
      process.stdout.write(output)
    }
  } else {
    normalise.stdout.pipe(process.stdout)
    await eof(normalise.stdout)
  }
}

const HN_URL = 'https://news.ycombinator.com/item?id='
async function hn (id) {
  return r2('https://hacker-news.firebaseio.com/v0/item/' + id + '.json').json
}

async function main (url) {
  if (url.endsWith('.pdf')) {
    let tmpdir = tmp.dirSync().name
    spawnSync('pdftohtml', ['-c', '-s', '-i', url, tmpdir + '/out'], {stdio: 'ignore'})
    url = tmpdir + '/out-html.html'
    ignoreTitles = true
  }

  if (url.startsWith(HN_URL)) {
    let item = await hn(url.replace(HN_URL, ''))
    if (item.kids.length > 0) postscript += '
Comments
'
    for (let i = 0; i < 3 && i < item.kids.length; i++) {
      let comment = await hn(item.kids[i])
      postscript += '
' + comment.text + '
— ' + comment.by + '
'
    }
    url = item.url
  }

  url = url.replace('#!', '?_escaped_fragment_=')

  let parsed = URL.parse(url)
  if (parsed.protocol && parsed.hostname) {
    let dom = await fromURL(url)
    await peruse(dom.window)
  } else if (url.endsWith('.html') && fs.existsSync(url)) {
    let html = fs.readFileSync(url, 'utf8')
    let dom = new JSDOM(html, options)
    await peruse(dom.window)
  } else {
    console.error(url + ': unrecognised input')
    return 1
  }

  setTimeout(process.exit, 100)
  return 0
}

main.apply(null, process.argv.slice(2)).then(function (code) { process.exitCode = code })
<|endoftext|>


<commit_before>var Buffer = require('buffer').Buffer
var rng = require('./rng')

function error () {
  var m = [].slice.call(arguments).join(' ')
  throw new Error([
    m,
    'we accept pull requests',
    'http://github.com/dominictarr/crypto-browserify'
    ].join('\n'))
}

exports.createHash = require('./create-hash')

exports.createHmac = require('./create-hmac')

exports.randomBytes = function(size, callback) {
  if (callback && callback.call) {
    try {
      callback.call(this, undefined, new Buffer(rng(size)))
    } catch (err) { callback(err) }
  } else {
    return new Buffer(rng(size))
  }
}

function each(a, f) {
  for(var i in a)
    f(a[i], i)
}

var p = require('./pbkdf2')(exports.createHmac)
exports.pbkdf2 = p.pbkdf2
exports.pbkdf2Sync = p.pbkdf2Sync

// the least I can do is make error messages for the rest of the node.js/crypto api.
each(['createCredentials'
, 'createCipher'
, 'createCipheriv'
, 'createDecipher'
, 'createDecipheriv'
, 'createSign'
, 'createVerify'
, 'createDiffieHellman'
], function (name) {
  exports[name] = function () {
    error('sorry,', name, 'is not implemented yet')
  }
})
<commit_msg>support getHashes() method<commit_after>var Buffer = require('buffer').Buffer
var rng = require('./rng')

function error () {
  var m = [].slice.call(arguments).join(' ')
  throw new Error([
    m,
    'we accept pull requests',
    'http://github.com/dominictarr/crypto-browserify'
    ].join('\n'))
}

exports.createHash = require('./create-hash')

exports.createHmac = require('./create-hmac')

exports.randomBytes = function(size, callback) {
  if (callback && callback.call) {
    try {
      callback.call(this, undefined, new Buffer(rng(size)))
    } catch (err) { callback(err) }
  } else {
    return new Buffer(rng(size))
  }
}

function each(a, f) {
  for(var i in a)
    f(a[i], i)
}

exports.getHashes = function () {
  return ['sha1', 'sha256', 'md5', 'rmd160']

}

var p = require('./pbkdf2')(exports.createHmac)
exports.pbkdf2 = p.pbkdf2
exports.pbkdf2Sync = p.pbkdf2Sync

// the least I can do is make error messages for the rest of the node.js/crypto api.
each(['createCredentials'
, 'createCipher'
, 'createCipheriv'
, 'createDecipher'
, 'createDecipheriv'
, 'createSign'
, 'createVerify'
, 'createDiffieHellman'
], function (name) {
  exports[name] = function () {
    error('sorry,', name, 'is not implemented yet')
  }
})
<|endoftext|>


<commit_before>/*
	Copyright 2013-2014, Marten de Vries

	Licensed under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
*/

"use strict";

var couchdb_objects = require("couchdb-objects");
var render = require("couchdb-render");
var nodify = require("promise-nodify");
var httpQuery = require("pouchdb-req-http-query");
var PouchPluginError = require("pouchdb-plugin-error");

var Promise = require("pouchdb-promise");

exports.show = function (showPath, options, callback) {
  //options: values to end up in the request object of the show
  //function (next to their defaults).

  if (typeof options === "function") {
    callback = options;
    options = {};
  }
  var db = this;

  var splitted = showPath.split("/");
  var designDocName = splitted[0];
  var showName = splitted[1];
  var docId = splitted[2];
  if (docId === "_design" && splitted.length > 3) {
    docId += "/" + splitted[3];
  }

  //build request object
  var pathEnd = ["_design", designDocName, "_show", showName];
  if (docId) {
    pathEnd.push.apply(pathEnd, docId.split("/"));
  }
  var reqPromise = couchdb_objects.buildRequestObject(db, pathEnd, options);
  var promise = reqPromise.then(function (req) {
    if (["http", "https"].indexOf(db.type()) === -1) {
      return offlineQuery(db, designDocName, showName, docId, req, options);
    } else {
      return httpQuery(db, req);
    }
  });
  nodify(promise, callback);
  return promise;
};

function offlineQuery(db, designDocName, showName, docId, req, options) {
  //get the documents involved.
  var ddocPromise = db.get("_design/" + designDocName).then(function (designDoc) {
    if (!(designDoc.shows || {}).hasOwnProperty(showName)) {
      throw new PouchPluginError({
        status: 404,
        name: "not_found",
        message: "missing show function " + showName + " on design doc _design/" + designDocName
      });
    }
    return designDoc;
  });
  var docPromise = db.get(docId, options).catch(function () {
    //doc might not exist - that's ok and expected.
    return null;
  });
  return Promise.all([ddocPromise, docPromise])
    .then(Function.prototype.apply.bind(function (designDoc, doc) {
      //all data collected - do the magic that is a show function
      var source = designDoc.shows[showName];

      return render(source, designDoc, doc, req);
    }, null));
}
<commit_msg>Improved tests; JS & PY code coverage is back at 100%<commit_after>/*
	Copyright 2013-2014, Marten de Vries

	Licensed under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
*/

"use strict";

var couchdb_objects = require("couchdb-objects");
var render = require("couchdb-render");
var nodify = require("promise-nodify");
var httpQuery = require("pouchdb-req-http-query");
var PouchPluginError = require("pouchdb-plugin-error");

var Promise = require("pouchdb-promise");

exports.show = function (showPath, options, callback) {
  //options: values to end up in the request object of the show
  //function (next to their defaults).

  if (["function", "undefined"].indexOf(typeof options) !== -1) {
    callback = options;
    options = {};
  }
  var db = this;

  var splitted = showPath.split("/");
  var designDocName = splitted[0];
  var showName = splitted[1];
  var docId = splitted[2];
  if (docId === "_design" && splitted.length > 3) {
    docId += "/" + splitted[3];
  }

  //build request object
  var pathEnd = ["_design", designDocName, "_show", showName];
  if (docId) {
    pathEnd.push.apply(pathEnd, docId.split("/"));
  }
  var reqPromise = couchdb_objects.buildRequestObject(db, pathEnd, options);
  var promise = reqPromise.then(function (req) {
    if (["http", "https"].indexOf(db.type()) === -1) {
      return offlineQuery(db, designDocName, showName, docId, req, options);
    } else {
      return httpQuery(db, req);
    }
  });
  nodify(promise, callback);
  return promise;
};

function offlineQuery(db, designDocName, showName, docId, req, options) {
  //get the documents involved.
  var ddocPromise = db.get("_design/" + designDocName).then(function (designDoc) {
    if (!(designDoc.shows || {}).hasOwnProperty(showName)) {
      throw new PouchPluginError({
        status: 404,
        name: "not_found",
        message: "missing show function " + showName + " on design doc _design/" + designDocName
      });
    }
    return designDoc;
  });
  var docPromise = db.get(docId, options).catch(function (err) {
    //doc might not exist - that's ok and expected.
    return null;
  });
  return Promise.all([ddocPromise, docPromise])
    .then(Function.prototype.apply.bind(function (designDoc, doc) {
      //all data collected - do the magic that is a show function
      var source = designDoc.shows[showName];

      return render(source, designDoc, doc, req);
    }, null));
}
<|endoftext|>


<commit_before>var lexint = require('lexicographic-integer')
var collect = require('stream-collector')
var through = require('through2')
var from = require('from2')
var pump = require('pump')

var noop = function () {}

var Logs = function (db, opts) {
  if (!(this instanceof Logs)) return new Logs(db, opts)
  if (!opts) opts = {}

  this.db = db
  this.sep = opts.separator || opts.sep || '!'
  this.prefix = opts.prefix ? this.sep + opts.prefix + this.sep : ''
  this.valueEncoding = opts.valueEncoding
}

Logs.prototype.key = function (log, seq) {
  return this.prefix + log + this.sep + (seq === -1 ? '\xff' : lexint.pack(seq, 'hex'))
}

Logs.prototype.list = function (cb) {
  var self = this
  var prev = this.prefix
  var end = prev + '\xff'

  var rs = from.obj(function (size, cb) {
    collect(self.db.createKeyStream({gt: prev, lt: end, limit: 1}), function (err, keys) {
      if (err) return cb(err)

      if (!keys.length) return cb(null, null)
      var log = keys[0].slice(self.prefix.length, keys[0].lastIndexOf(self.sep))
      prev = self.key(log, -1)

      cb(null, log)
    })
  })

  return collect(rs, cb)
}

Logs.prototype.get = function (log, seq, cb) {
  this.db.get(this.key(log, seq), {valueEncoding: this.valueEncoding}, cb)
}

Logs.prototype.put = function (log, seq, value, cb) {
  this.db.put(this.key(log, seq), value, {valueEncoding: this.valueEncoding}, cb)
}

Logs.prototype.append = function (log, value, cb) {
  if (!cb) cb = noop
  var self = this

  this.head(log, function (err, seq) {
    if (err) return cb(err)
    self.put(log, seq + 1, value, function (err) {
      if (err) return cb(err)
      cb(null, seq + 1)
    })
  })
}

Logs.prototype.head = function (log, cb) {
  var self = this

  var keys = this.db.createKeyStream({
    gt: this.key(log, 0),
    lt: this.key(log, -1),
    reverse: true,
    limit: 1
  })

  collect(keys, function (err, head) {
    if (err) return cb(err)
    cb(null, head.length ? lexint.unpack(head[0].slice(head[0].lastIndexOf(self.sep) + 1), 'hex') : 0)
  })
}

Logs.prototype.createValueStream = function (log, opts) {
  if (!opts) opts = {}
  return this.db.createValueStream({
    gt: this.key(log, opts.since || 0),
    lt: this.key(log, opts.until || -1),
    valueEncoding: this.valueEncoding,
    reverse: opts.reverse
  })
}

Logs.prototype.createReadStream = function (log, opts) {
  if (!opts) opts = {}

  var self = this

  var rs = this.db.createReadStream({
    gt: this.key(log, opts.since || 0),
    lt: this.key(log, opts.until || -1),
    valueEncoding: this.valueEncoding,
    reverse: opts.reverse
  })

  var format = through.obj(function (data, enc, cb) {
    var key = data.key
    var log = key.slice(0, key.lastIndexOf(self.sep))
    var seq = lexint.unpack(key.slice(log.length + 1), 'hex')
    cb(null, {log: log, seq: seq, value: data.value})
  })

  return pump(rs, format)
}

module.exports = Logs
<commit_msg>fix readstream when using prefix<commit_after>var lexint = require('lexicographic-integer')
var collect = require('stream-collector')
var through = require('through2')
var from = require('from2')
var pump = require('pump')

var noop = function () {}

var Logs = function (db, opts) {
  if (!(this instanceof Logs)) return new Logs(db, opts)
  if (!opts) opts = {}

  this.db = db
  this.sep = opts.separator || opts.sep || '!'
  this.prefix = opts.prefix ? this.sep + opts.prefix + this.sep : ''
  this.valueEncoding = opts.valueEncoding
}

Logs.prototype.key = function (log, seq) {
  return this.prefix + log + this.sep + (seq === -1 ? '\xff' : lexint.pack(seq, 'hex'))
}

Logs.prototype.list = function (cb) {
  var self = this
  var prev = this.prefix
  var end = prev + '\xff'

  var rs = from.obj(function (size, cb) {
    collect(self.db.createKeyStream({gt: prev, lt: end, limit: 1}), function (err, keys) {
      if (err) return cb(err)

      if (!keys.length) return cb(null, null)
      var log = keys[0].slice(self.prefix.length, keys[0].lastIndexOf(self.sep))
      prev = self.key(log, -1)

      cb(null, log)
    })
  })

  return collect(rs, cb)
}

Logs.prototype.get = function (log, seq, cb) {
  this.db.get(this.key(log, seq), {valueEncoding: this.valueEncoding}, cb)
}

Logs.prototype.put = function (log, seq, value, cb) {
  this.db.put(this.key(log, seq), value, {valueEncoding: this.valueEncoding}, cb)
}

Logs.prototype.append = function (log, value, cb) {
  if (!cb) cb = noop
  var self = this

  this.head(log, function (err, seq) {
    if (err) return cb(err)
    self.put(log, seq + 1, value, function (err) {
      if (err) return cb(err)
      cb(null, seq + 1)
    })
  })
}

Logs.prototype.head = function (log, cb) {
  var self = this

  var keys = this.db.createKeyStream({
    gt: this.key(log, 0),
    lt: this.key(log, -1),
    reverse: true,
    limit: 1
  })

  collect(keys, function (err, head) {
    if (err) return cb(err)
    cb(null, head.length ? lexint.unpack(head[0].slice(head[0].lastIndexOf(self.sep) + 1), 'hex') : 0)
  })
}

Logs.prototype.createValueStream = function (log, opts) {
  if (!opts) opts = {}
  return this.db.createValueStream({
    gt: this.key(log, opts.since || 0),
    lt: this.key(log, opts.until || -1),
    valueEncoding: this.valueEncoding,
    reverse: opts.reverse
  })
}

Logs.prototype.createReadStream = function (log, opts) {
  if (!opts) opts = {}

  var self = this

  var rs = this.db.createReadStream({
    gt: this.key(log, opts.since || 0),
    lt: this.key(log, opts.until || -1),
    valueEncoding: this.valueEncoding,
    reverse: opts.reverse
  })

  var format = through.obj(function (data, enc, cb) {
    var key = data.key
    var log = key.slice(self.prefix.length, key.lastIndexOf(self.sep))
    var seq = lexint.unpack(key.slice(self.prefix.length + log.length + 1), 'hex')
    cb(null, {log: log, seq: seq, value: data.value})
  })

  return pump(rs, format)
}

module.exports = Logs
<|endoftext|>


<commit_before>
/* !
 * matrix4
 * JavaScript math library for 4d matrix
 * Copyright (c) 2012 Enrico Marino (http://onirame.no.de)
 * MIT License
 */

 !(function (exports) {

  /**
   * Library namespace.
   */

  var matrix4 = exports.matrix4 = {};

  /**
   * Library version.
   */

  matrix4.version = '0.0.5';

  /**
   * create a 4d matrix
   * @param {Float32Array | Array} values matrix values
   * @return {Float32Array} a 4d matrix
   * @api public
   */

  matrix4.create = function (values) {
    if (values === undefined) {
      values = [
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
      ];
    }

    return new Float32Array(values);
  };

  /**
   * copy `values` to `self` matrix
   * @param {Float32Array | Array} self destination matrix
   * @param {Float32Array | Array} values source matrix
   * @return {Float32Array | Array} updated `self` matrix
   * @api public
   */

  matrix4.copy = function (self, values) {
    self[ 0] = values[ 0];
    self[ 1] = values[ 1];
    self[ 2] = values[ 2];
    self[ 3] = values[ 3];

    self[ 4] = values[ 4];
    self[ 5] = values[ 5];
    self[ 6] = values[ 6];
    self[ 7] = values[ 7];

    self[ 8] = values[ 8];
    self[ 9] = values[ 9];
    self[10] = values[10];
    self[11] = values[11];

    self[12] = values[12];
    self[13] = values[13];
    self[14] = values[14];
    self[15] = values[15];

    return self;
  };

  /**
   * set to zero all values of `self` matrix
   * @param {Float32Array | Array} self matrix destination
   * @return {Float32Array | Array} updated `self` matrix
   * @api public
   */

  matrix4.zero = function (self) {
    self[ 0] = 0;
    self[ 1] = 0;
    self[ 2] = 0;
    self[ 3] = 0;

    self[ 4] = 0;
    self[ 5] = 0;
    self[ 6] = 0;
    self[ 7] = 0;

    self[ 8] = 0;
    self[ 9] = 0;
    self[10] = 0;
    self[11] = 0;

    self[12] = 0;
    self[13] = 0;
    self[14] = 0;
    self[15] = 0;

    return self;
  };

  /**
   * set 'self' matrix to the identity matrix
   * @param {Float32Array | Array} self destination matrix
   * @return {Float32Array | Array} updated `self` matrix
   * @api public
   */

  matrix4.identity = function (self) {
    self[ 0] = 1;
    self[ 1] = 0;
    self[ 2] = 0;
    self[ 3] = 0;

    self[ 4] = 0;
    self[ 5] = 1;
    self[ 6] = 0;
    self[ 7] = 0;

    self[ 8] = 0;
    self[ 9] = 0;
    self[10] = 1;
    self[11] = 0;

    self[12] = 0;
    self[13] = 0;
    self[14] = 0;
    self[15] = 1;

    return self;
  };

  /**
   * set
   * Set the `i`-th value (from `0` to `15`) of the matrix
   *
   * @param {Float32Array | Array} self destination matrix
   * @param {Number} the `i`-th value of `self` matrix
   * @return {Float32Array | Array} matrix
   * @api public
   */

  matrix4.set = function (self, i, value) {
    self[i] = value;

    return self;
  };

  /**
   * set_value
   * Set the value at i-th row, j-th col of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} i index of row
   * @param {Number} j index of col
   * @param {Number} value value to set
   * @return {Float32Array} matrix
   * @api public
   */

  matrix4.set_value = function (self, i, j, value) {
    self[i + 4*j] = value;

    return self;
  };

  /**
   * set_row
   * Set the i-th row of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} i index of row
   * @param {Number} values values to set
   * @return {Float32Array} matrix
   * @api public
   */

  matrix4.set_row = function (self, i, values) {
    self[i +  0] = values[0];
    self[i +  4] = values[1];
    self[i +  8] = values[2];
    self[i + 12] = values[3];

    return self;
  };

  /**
   * set_col
   * Set the j-th col of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} j index of col
   * @param {Number} values values to set
   * @return {Float32Array} matrix
   * @api public
   */

  matrix4.set_col = function (self, j, values) {
    self[0 + 4*j] = values[0];
    self[1 + 4*j] = values[1];
    self[2 + 4*j] = values[2];
    self[3 + 4*j] = values[3];

    return self;
  };

  /**
   * set_upper3
   * Set the upper 3x3 matrix.
   *
   * @param {Float32Array} self destination matrix
   * @param {Float32Array} m source matrix
   * @return {Float32Array} destination matrix
   * @api public
   */

  matrix4.set_upper3 = function (self, m) {
    self[0] = m[ 0];
    self[1] = m[ 1];
    self[2] = m[ 2];

    self[3] = m[ 4];
    self[4] = m[ 5];
    self[5] = m[ 6];

    self[6] = m[ 8];
    self[7] = m[ 9];
    self[8] = m[10];

    return self;
  };

  /**
   * get
   * Get the i-th value of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} i index (from 0 to 15)
   * @return {Number} the i-th value of the matrix
   * @api public
   */

  matrix4.get = function (self, i) {
    return self[i];
  };

  /**
   * get_value
   * Get the value at the i-th row, j-th col of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} i index of row
   * @param {Number} j index of col
   * @return {Number} the value of the matrix
   * @api public
   */

  matrix4.get_value = function (self, i, j) {
    return self[i + 4*j];
  };

  /**
   * get_row
   * Get the i-th row of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} i index of row
   * @param {Number} values values to set
   * @return {Float32Array} the i-th row of the matrix
   * @api public
   */

  matrix4.get_row = function (self, i, values) {
    values[0] = self[row +  0];
    values[1] = self[row +  4];
    values[2] = self[row +  8];
    values[3] = self[row + 12];

    return values;
  };

 }(this));<commit_msg>added matrix4.get_col<commit_after>
/* !
 * matrix4
 * JavaScript math library for 4d matrix
 * Copyright (c) 2012 Enrico Marino (http://onirame.no.de)
 * MIT License
 */

 !(function (exports) {

  /**
   * Library namespace.
   */

  var matrix4 = exports.matrix4 = {};

  /**
   * Library version.
   */

  matrix4.version = '0.0.5';

  /**
   * create a 4d matrix
   * @param {Float32Array | Array} values matrix values
   * @return {Float32Array} a 4d matrix
   * @api public
   */

  matrix4.create = function (values) {
    if (values === undefined) {
      values = [
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
      ];
    }

    return new Float32Array(values);
  };

  /**
   * copy `values` to `self` matrix
   * @param {Float32Array | Array} self destination matrix
   * @param {Float32Array | Array} values source matrix
   * @return {Float32Array | Array} updated `self` matrix
   * @api public
   */

  matrix4.copy = function (self, values) {
    self[ 0] = values[ 0];
    self[ 1] = values[ 1];
    self[ 2] = values[ 2];
    self[ 3] = values[ 3];

    self[ 4] = values[ 4];
    self[ 5] = values[ 5];
    self[ 6] = values[ 6];
    self[ 7] = values[ 7];

    self[ 8] = values[ 8];
    self[ 9] = values[ 9];
    self[10] = values[10];
    self[11] = values[11];

    self[12] = values[12];
    self[13] = values[13];
    self[14] = values[14];
    self[15] = values[15];

    return self;
  };

  /**
   * set to zero all values of `self` matrix
   * @param {Float32Array | Array} self matrix destination
   * @return {Float32Array | Array} updated `self` matrix
   * @api public
   */

  matrix4.zero = function (self) {
    self[ 0] = 0;
    self[ 1] = 0;
    self[ 2] = 0;
    self[ 3] = 0;

    self[ 4] = 0;
    self[ 5] = 0;
    self[ 6] = 0;
    self[ 7] = 0;

    self[ 8] = 0;
    self[ 9] = 0;
    self[10] = 0;
    self[11] = 0;

    self[12] = 0;
    self[13] = 0;
    self[14] = 0;
    self[15] = 0;

    return self;
  };

  /**
   * set 'self' matrix to the identity matrix
   * @param {Float32Array | Array} self destination matrix
   * @return {Float32Array | Array} updated `self` matrix
   * @api public
   */

  matrix4.identity = function (self) {
    self[ 0] = 1;
    self[ 1] = 0;
    self[ 2] = 0;
    self[ 3] = 0;

    self[ 4] = 0;
    self[ 5] = 1;
    self[ 6] = 0;
    self[ 7] = 0;

    self[ 8] = 0;
    self[ 9] = 0;
    self[10] = 1;
    self[11] = 0;

    self[12] = 0;
    self[13] = 0;
    self[14] = 0;
    self[15] = 1;

    return self;
  };

  /**
   * set
   * Set the `i`-th value (from `0` to `15`) of the matrix
   *
   * @param {Float32Array | Array} self destination matrix
   * @param {Number} the `i`-th value of `self` matrix
   * @return {Float32Array | Array} matrix
   * @api public
   */

  matrix4.set = function (self, i, value) {
    self[i] = value;

    return self;
  };

  /**
   * set_value
   * Set the value at i-th row, j-th col of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} i index of row
   * @param {Number} j index of col
   * @param {Number} value value to set
   * @return {Float32Array} matrix
   * @api public
   */

  matrix4.set_value = function (self, i, j, value) {
    self[i + 4*j] = value;

    return self;
  };

  /**
   * set_row
   * Set the i-th row of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} i index of row
   * @param {Number} values values to set
   * @return {Float32Array} matrix
   * @api public
   */

  matrix4.set_row = function (self, i, values) {
    self[i +  0] = values[0];
    self[i +  4] = values[1];
    self[i +  8] = values[2];
    self[i + 12] = values[3];

    return self;
  };

  /**
   * set_col
   * Set the j-th col of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} j index of col
   * @param {Number} values values to set
   * @return {Float32Array} matrix
   * @api public
   */

  matrix4.set_col = function (self, j, values) {
    self[0 + 4*j] = values[0];
    self[1 + 4*j] = values[1];
    self[2 + 4*j] = values[2];
    self[3 + 4*j] = values[3];

    return self;
  };

  /**
   * set_upper3
   * Set the upper 3x3 matrix.
   *
   * @param {Float32Array} self destination matrix
   * @param {Float32Array} m source matrix
   * @return {Float32Array} destination matrix
   * @api public
   */

  matrix4.set_upper3 = function (self, m) {
    self[0] = m[ 0];
    self[1] = m[ 1];
    self[2] = m[ 2];

    self[3] = m[ 4];
    self[4] = m[ 5];
    self[5] = m[ 6];

    self[6] = m[ 8];
    self[7] = m[ 9];
    self[8] = m[10];

    return self;
  };

  /**
   * get
   * Get the i-th value of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} i index (from 0 to 15)
   * @return {Number} the i-th value of the matrix
   * @api public
   */

  matrix4.get = function (self, i) {
    return self[i];
  };

  /**
   * get_value
   * Get the value at the i-th row, j-th col of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} i index of row
   * @param {Number} j index of col
   * @return {Number} the value of the matrix
   * @api public
   */

  matrix4.get_value = function (self, i, j) {
    return self[i + 4*j];
  };

  /**
   * get_row
   * Get the i-th row of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} i index of row
   * @param {Number} values values to set
   * @return {Float32Array} the i-th row of the matrix
   * @api public
   */

  matrix4.get_row = function (self, i, values) {
    values[0] = self[row +  0];
    values[1] = self[row +  4];
    values[2] = self[row +  8];
    values[3] = self[row + 12];

    return values;
  };

  /**
   * get_row
   * Get the j-th col of the matrix.
   *
   * @param {Float32Array} self matrix
   * @param {Number} j index of col
   * @param {Number} values values to set
   * @return {Float32Array} the i-th row of the matrix
   * @api public
   */

  matrix4.get_col = function (self, j, values) {
    values[0] = self[    4*col];
    values[1] = self[1 + 4*col];
    values[2] = self[2 + 4*col];
    values[3] = self[3 + 4*col];

    return values;
  };

}(this));<|endoftext|>


<commit_before>'use strict';

const VersionChecker = require('ember-cli-version-checker');
const satisfies = require('semver').satisfies;
const gte = require('semver').gte;

module.exports = {
  name: 'ember-compatibility-helpers',

  included(parent) {
    this._super.included.apply(this, arguments);

    const host = this._findHost();

    // Create a root level version checker for checking the Ember version later on
    this.emberVersion = new VersionChecker(host).forEmber().version;

    // Create a parent checker for checking the parent app/addons dependencies (for things like polyfills)
    this.parentChecker = new VersionChecker(parent);
    const emberBabelChecker = this.parentChecker.for('ember-cli-babel', 'npm');

    if (!emberBabelChecker.satisfies('^6.0.0-beta.1')) {
      host.project.ui.writeWarnLine(
        'ember-compatibility-helpers: You are using an unsupported ember-cli-babel version, ' +
        'compatibility helper tranforms will not be included automatically'
      );

      this._registeredWithBabel = true;
    }

    this.registerTransformWithParent(parent);
  },

  /**
   * Registers the compatibility transforms with the parent addon or application
   *
   * @param {Addon|EmberAddon|EmberApp} parent
   */
  registerTransformWithParent(parent) {
    if (this._registeredWithBabel) return;

    const parentOptions = parent.options = parent.options || {};

    // Create babel options if they do not exist
    parentOptions.babel = parentOptions.babel || {};

    const plugins = parentOptions.babel.plugins = parentOptions.babel.plugins || [];
    const debugPlugin = this._getDebugPlugin(this.emberVersion, this.parentChecker);

    plugins.push(debugPlugin);

    this._registeredWithBabel = true;
  },

  _getDebugPlugin(emberVersion, parentChecker) {
    const trueEmberVersion = emberVersion.match(/\d+\.\d+\.\d+/)[0];

    const DebugMacros = require('babel-plugin-debug-macros').default;

    let options = {
      envFlags: {
        source: 'ember-compatibility-helpers',
        flags: {
          DEBUG: false
        }
      },

      features: {
        name: 'ember-compatibility-helpers',
        source: 'ember-compatibility-helpers',
        flags: {
          HAS_UNDERSCORE_ACTIONS: !gte(trueEmberVersion, '2.0.0'),
          HAS_MODERN_FACTORY_INJECTIONS: gte(trueEmberVersion, '2.13.0'),
          HAS_DESCRIPTOR_TRAP: satisfies(trueEmberVersion, '~3.0.0'),
          HAS_NATIVE_COMPUTED_GETTERS: gte(trueEmberVersion, '3.1.0-beta.1'),

          GTE_EMBER_1_13: gte(trueEmberVersion, '1.13.0'),
          IS_EMBER_2: gte(trueEmberVersion, '2.0.0'),
          IS_GLIMMER_2: gte(trueEmberVersion, '2.10.0'),

          SUPPORTS_FACTORY_FOR: gte(trueEmberVersion, '2.12.0') || parentChecker.for('ember-factory-for-polyfill', 'npm').gte('1.0.0'),
          SUPPORTS_GET_OWNER: gte(trueEmberVersion, '2.3.0') || parentChecker.for('ember-getowner-polyfill', 'npm').gte('1.1.0'),
          SUPPORTS_SET_OWNER: gte(trueEmberVersion, '2.3.0'),
          SUPPORTS_NEW_COMPUTED: gte(trueEmberVersion, '1.12.0-beta.1'),
          SUPPORTS_INVERSE_BLOCK: gte(trueEmberVersion, '1.13.0'),
          SUPPORTS_CLOSURE_ACTIONS: gte(trueEmberVersion, '1.13.0'),
          SUPPORTS_UNIQ_BY_COMPUTED: gte(trueEmberVersion, '2.7.0')
        }
      },

      externalizeHelpers: {
        global: 'Ember'
      },

      debugTools: {
        source: 'ember-compatibility-helpers',
        assertPredicateIndex: 1
      }
    };

    return [DebugMacros, options];
  },

  _findHost() {
    let current = this;
    let app;

    do {
      app = current.app || app;
    } while (current.parent.parent && (current = current.parent));

    return app;
  }
};
<commit_msg>Fixup VersionChecker usage.<commit_after>'use strict';

const VersionChecker = require('ember-cli-version-checker');
const satisfies = require('semver').satisfies;
const gte = require('semver').gte;

module.exports = {
  name: 'ember-compatibility-helpers',

  included(appOrParentAddon) {
    this._super.included.apply(this, arguments);

    const host = this._findHost();

    // Create a root level version checker for checking the Ember version later on
    this.emberVersion = new VersionChecker(this).forEmber().version;

    // Create a parent checker for checking the parent app/addons dependencies (for things like polyfills)
    this.parentChecker = new VersionChecker(this.parent);
    const emberBabelChecker = this.parentChecker.for('ember-cli-babel', 'npm');

    if (!emberBabelChecker.satisfies('^6.0.0-beta.1')) {
      host.project.ui.writeWarnLine(
        'ember-compatibility-helpers: You are using an unsupported ember-cli-babel version, ' +
        'compatibility helper tranforms will not be included automatically'
      );

      this._registeredWithBabel = true;
    }

    this.registerTransformWithParent(appOrParentAddon);
  },

  /**
   * Registers the compatibility transforms with the parent addon or application
   *
   * @param {Addon|EmberAddon|EmberApp} parent
   */
  registerTransformWithParent(parent) {
    if (this._registeredWithBabel) return;

    const parentOptions = parent.options = parent.options || {};

    // Create babel options if they do not exist
    parentOptions.babel = parentOptions.babel || {};

    const plugins = parentOptions.babel.plugins = parentOptions.babel.plugins || [];
    const debugPlugin = this._getDebugPlugin(this.emberVersion, this.parentChecker);

    plugins.push(debugPlugin);

    this._registeredWithBabel = true;
  },

  _getDebugPlugin(emberVersion, parentChecker) {
    const trueEmberVersion = emberVersion.match(/\d+\.\d+\.\d+/)[0];

    const DebugMacros = require('babel-plugin-debug-macros').default;

    let options = {
      envFlags: {
        source: 'ember-compatibility-helpers',
        flags: {
          DEBUG: false
        }
      },

      features: {
        name: 'ember-compatibility-helpers',
        source: 'ember-compatibility-helpers',
        flags: {
          HAS_UNDERSCORE_ACTIONS: !gte(trueEmberVersion, '2.0.0'),
          HAS_MODERN_FACTORY_INJECTIONS: gte(trueEmberVersion, '2.13.0'),
          HAS_DESCRIPTOR_TRAP: satisfies(trueEmberVersion, '~3.0.0'),
          HAS_NATIVE_COMPUTED_GETTERS: gte(trueEmberVersion, '3.1.0-beta.1'),

          GTE_EMBER_1_13: gte(trueEmberVersion, '1.13.0'),
          IS_EMBER_2: gte(trueEmberVersion, '2.0.0'),
          IS_GLIMMER_2: gte(trueEmberVersion, '2.10.0'),

          SUPPORTS_FACTORY_FOR: gte(trueEmberVersion, '2.12.0') || parentChecker.for('ember-factory-for-polyfill', 'npm').gte('1.0.0'),
          SUPPORTS_GET_OWNER: gte(trueEmberVersion, '2.3.0') || parentChecker.for('ember-getowner-polyfill', 'npm').gte('1.1.0'),
          SUPPORTS_SET_OWNER: gte(trueEmberVersion, '2.3.0'),
          SUPPORTS_NEW_COMPUTED: gte(trueEmberVersion, '1.12.0-beta.1'),
          SUPPORTS_INVERSE_BLOCK: gte(trueEmberVersion, '1.13.0'),
          SUPPORTS_CLOSURE_ACTIONS: gte(trueEmberVersion, '1.13.0'),
          SUPPORTS_UNIQ_BY_COMPUTED: gte(trueEmberVersion, '2.7.0')
        }
      },

      externalizeHelpers: {
        global: 'Ember'
      },

      debugTools: {
        source: 'ember-compatibility-helpers',
        assertPredicateIndex: 1
      }
    };

    return [DebugMacros, options];
  },

  _findHost() {
    let current = this;
    let app;

    do {
      app = current.app || app;
    } while (current.parent.parent && (current = current.parent));

    return app;
  }
};
<|endoftext|>


<commit_before>'use strict';

var proto = Element.prototype;
var vendor = proto.matches
  || proto.matchesSelector
  || proto.webkitMatchesSelector
  || proto.mozMatchesSelector
  || proto.msMatchesSelector
  || proto.oMatchesSelector;

module.exports = match;

/**
 * Match `el` to `selector`.
 *
 * @param {Element} el
 * @param {String} selector
 * @return {Boolean}
 * @api public
 */

function match(el, selector) {
  if (!el || el.nodeType !== 1) return false;
  if (vendor) return vendor.call(el, selector);
  var nodes = el.parentNode.querySelectorAll(selector);
  for (var i = 0; i < nodes.length; i++) {
    if (nodes[i] == el) return true;
  }
  return false;
}
<commit_msg>Do not error if the Element global is not defined, e.g. non-browser environments. (#1)<commit_after>'use strict';

var proto = typeof Element !== 'undefined' ? Element.prototype : {};
var vendor = proto.matches
  || proto.matchesSelector
  || proto.webkitMatchesSelector
  || proto.mozMatchesSelector
  || proto.msMatchesSelector
  || proto.oMatchesSelector;

module.exports = match;

/**
 * Match `el` to `selector`.
 *
 * @param {Element} el
 * @param {String} selector
 * @return {Boolean}
 * @api public
 */

function match(el, selector) {
  if (!el || el.nodeType !== 1) return false;
  if (vendor) return vendor.call(el, selector);
  var nodes = el.parentNode.querySelectorAll(selector);
  for (var i = 0; i < nodes.length; i++) {
    if (nodes[i] == el) return true;
  }
  return false;
}
<|endoftext|>


<commit_before>module.exports = cftemplate

var plaintemplate = require('plaintemplate')
var resolve = require('resolve')
var stringifyForm = require('commonform-markup-stringify')
var parseForm = require('commonform-markup-parse')
var path = require('path')
var fs = require('fs')

var INDENT = /^ {4}/
var EMPTY_LINE = /^(\s*)$/

function cftemplate(template, base, context) {
  return plaintemplate(
    template,
    context,
    function handler(token, context, stringify) {
      var key
      var directive = token.tag.trim()

      function addPosition(error, message) {
        error.message = (
          message + ' at ' +
          'line ' + token.position.line + ', ' +
          'column ' + token.position.column)
        error.position = token.position
        return error }

      if (directive.startsWith('require ')) {
        var split = directive.split(' ')
        var requireTarget = (
          split.length === 2 ?
            ( './' + split[1] + '.json' ) :
            ( '@' + split[1] + '/' + split[2] ) )
        var markupFile = path.resolve(
          base,
          requireTarget.replace(/.json$/, '.cform'))
        var resolved
        if (fs.existsSync(markupFile)) {
          resolved = parseForm(
            fs.readFileSync(markupFile).toString()).form }
        else {
          resolved = resolve.sync(requireTarget, { basedir: base })
          if (resolved) {
            resolved = require(resolved).form } }

        if (resolved) {
          return formToMarkup(resolved)
            // split lines
            .split('\n')
            .map(function(line, index) {
              return (
                EMPTY_LINE.test(line) ?
                  // If the line is empty, leave it empty.
                  line :
                  ( index === 0 ?
                    // On the first line, replace all indentation with spaces
                    // reflectnig the indentation of the template tag in the
                    // template.
                    line.replace(/^ +/, '') :
                    // On subsequent lines with indentation, add spaces to
                    // existing indentation.
                    line.replace(
                      INDENT,
                      ' '.repeat(token.position.column - 1) )) ) })
            .join('\n') }
        else {
          throw addPosition(
            new Error(),
            ( 'Could not find package ' + requireTarget )) } }

      else if (directive.startsWith('if ')) {
        key = directive.substring(3)
        if (context.hasOwnProperty(key)) {
          return (
            !context[key] ?
              '' : stringify(token.content, context, handler) ) }
        else {
          return '' } }

      else if (directive.startsWith('unless ')) {
        key = directive.substring(7)
        if (context.hasOwnProperty(key)) {
          return (
            !context[key] ?
              stringify(token.content, context, handler) : '' ) }
        else {
          return '' } } },

    { open: '((', close: '))', start: 'start', end: 'end' }) }

function formToMarkup(form) {
  return stringifyForm(form)
    .replace(/^\n\n/, '') }
<commit_msg>Do not trim first-line indentation<commit_after>module.exports = cftemplate

var plaintemplate = require('plaintemplate')
var resolve = require('resolve')
var stringifyForm = require('commonform-markup-stringify')
var parseForm = require('commonform-markup-parse')
var path = require('path')
var fs = require('fs')

var EMPTY_LINE = /^( *)$/

function cftemplate(template, base, context) {
  return plaintemplate(
    template,
    context,
    function handler(token, context, stringify) {
      var key
      var directive = token.tag.trim()

      function addPosition(error, message) {
        error.message = (
          message + ' at ' +
          'line ' + token.position.line + ', ' +
          'column ' + token.position.column)
        error.position = token.position
        return error }

      if (directive.startsWith('require ')) {
        var split = directive.split(' ')
        var requireTarget = (
          split.length === 2 ?
            ( './' + split[1] + '.json' ) :
            ( '@' + split[1] + '/' + split[2] ) )
        var markupFile = path.resolve(
          base,
          requireTarget.replace(/.json$/, '.cform'))
        var resolved
        if (fs.existsSync(markupFile)) {
          resolved = parseForm(
            fs.readFileSync(markupFile).toString()).form }
        else {
          resolved = resolve.sync(requireTarget, { basedir: base })
          if (resolved) {
            resolved = require(resolved).form } }

        if (resolved) {
          return formToMarkup(resolved)
            // split lines
            .split('\n')
            .map(function(line, index) {
              return (
                EMPTY_LINE.test(line) ?
                  // If the line is empty, leave it empty.
                  line :
                  ( index === 0 ?
                      line :
                      // On subsequent lines with indentation, add spaces to
                      // existing indentation.
                      ( ' '.repeat(token.position.column - 1) + line) ) ) })
            .join('\n') }
        else {
          throw addPosition(
            new Error(),
            ( 'Could not find package ' + requireTarget )) } }

      else if (directive.startsWith('if ')) {
        key = directive.substring(3)
        if (context.hasOwnProperty(key)) {
          return (
            !context[key] ?
              '' : stringify(token.content, context, handler) ) }
        else {
          return '' } }

      else if (directive.startsWith('unless ')) {
        key = directive.substring(7)
        if (context.hasOwnProperty(key)) {
          return (
            !context[key] ?
              stringify(token.content, context, handler) : '' ) }
        else {
          return '' } } },

    { open: '((', close: '))', start: 'start', end: 'end' }) }

function formToMarkup(form) {
  return stringifyForm(form)
    .replace(/^\n\n/, '') }
<|endoftext|>


<commit_before>
module.exports = function() {

  var PORT       = 8080,
      path       = require("path"),
      express    = require("express"),
      server     = express(),
      scripts    = require("./lib/scripts");

  // Set up sensible logging defaults, etc.

  // Add routers
  server.use(require("./lib/fonts.js")());
  server.use(require("./lib/images.js")());
  server.use(require("./lib/styles.js")());
  server.use(require("./lib/views.js")());

  // Add scripts for in-house and vendor files
  server.use(scripts());
  server.use(scripts({ path : "./vendor", url : "/vendor" }));

  // We must add the data last or it overwrites other paths
  server.use(require("./lib/data.js")());

  // Find next port
  function listen() {
    server.listen(PORT, function(err) {
      console.log("Server listening on port", PORT);
    });
  }

  process.on("uncaughtException", function(err) {
    if ("EADDRINUSE" === err.errno) {
      console.log("Port " + PORT + " is in use, trying next...");
      PORT++;
      listen();
    } else
      process.exit(1);
  });

  listen();

}
<commit_msg>Main: updates to use new database and api modules<commit_after>
module.exports = function() {

  var PORT       = 8080,
      path       = require("path"),
      express    = require("express"),
      server     = express(),
      scripts    = require("./lib/scripts"),
      db;

  // Set up sensible logging defaults, etc.

  // Get JSON data (for database and view-rendering)
  db = require("./lib/database")();

  // Add routers
  server.use(require("./lib/fonts.js")());
  server.use(require("./lib/images.js")());
  server.use(require("./lib/styles.js")());
  server.use(require("./lib/views.js")());

  // Add scripts for in-house and vendor files
  server.use(scripts());
  server.use(scripts({ path : "./vendor", url : "/vendor" }));

  // We must add the data last or it overwrites other paths
  server.use(require("./lib/api.js")({ database : db }));

  // Find next port
  function listen() {
    server.listen(PORT, function(err) {
      console.log("Server listening on port", PORT);
    });
  }

  process.on("uncaughtException", function(err) {
    if ("EADDRINUSE" === err.errno) {
      console.log("Port " + PORT + " is in use, trying next...");
      PORT++;
      listen();
    } else
      process.exit(1);
  });

  listen();

}
<|endoftext|>


<commit_before>const fs = require('fs');
const path = require('path');
const url = require('url');
const dummyjson = require('dummy-json');
const prettyBytes = require('pretty-bytes');
const chalk = require('chalk');
const extendedMockData = require('./mockdata.js');

function MockApiMiddleware (route, options) {
	'use strict';

	const logPrefix = '[' + chalk.blue('MockApi') + '] ';
	const settings = Object.assign({
		mockPath: './mocks/',
		jsonContentType: 'application/json;charset=utf-8',
		dataset: extendedMockData
	}, options);

	return {
		route: route,
		handle: function (req, res, next) {
			let jsonData;
			let mockUrlPath = url.parse(req.url).pathname;
			let mockJsonPath = path.join(settings.mockPath + mockUrlPath + '.' + req.method + '.json');
			let dirCatchAll = path.join(require('path').dirname(mockJsonPath)  + '/_.' + req.method + '.json');

			function showError (message, errors) {
				// Print the error in the nodejs console
				console.log(logPrefix + chalk.red(message));

				// Print the error to the browser
				// Return a 404 header, and give a body with the information.
				// This might not be something everybody wants, but for now it seems like a good idea.
				res.statusCode = 404;
				res.setHeader('Content-Type', settings.jsonContentType);
				res.end(JSON.stringify({
					'Message': message,
					'Error': errors
				}));

				// Continue
				next();
			}

			// Tries to get a mock template, if it does not exist we check for a catch all template
			// This is usefull when you have something like /users/:id and still want to provide a response
			try {
				jsonData = fs.readFileSync(mockJsonPath, 'utf8');
			} catch (error) {
				try {
					jsonData = fs.readFileSync(dirCatchAll, 'utf8');
				} catch (error2) {
					showError('Could not find: ' + mockJsonPath + ' or ' + dirCatchAll, [error, error2]);
					return;
				}
			}

			// Will try to parse dummyjson, if it's not sucessfull show an error
			// This is most likely a mistake with the file, and has to be fixed.
			try {
				jsonData = JSON.parse(dummyjson.parse(jsonData, { mockdata: settings.dataset, helpers: settings.helpers }));
			} catch (error) {
				showError('Could not parse Mock-JSON: ' + mockJsonPath, error);
				return;
			}

			// Log requested url in the nodejs console
			// Will help to see what url's get requested. Might be information overload.
			console.log(logPrefix + 'Serving %s request:', req.method, chalk.green(route + req.url), prettyBytes(Buffer.byteLength(JSON.stringify(jsonData), 'utf8')));

			res.statusCode = 200;
			res.setHeader('Content-Type', settings.jsonContentType);
			res.end(JSON.stringify(jsonData));

			next();
		}
	};
}

module.exports = MockApiMiddleware;
<commit_msg>fix post bug; remove the next() (#3)<commit_after>const fs = require('fs');
const path = require('path');
const url = require('url');
const dummyjson = require('dummy-json');
const prettyBytes = require('pretty-bytes');
const chalk = require('chalk');
const extendedMockData = require('./mockdata.js');

function MockApiMiddleware (route, options) {
	'use strict';

	const logPrefix = '[' + chalk.blue('MockApi') + '] ';
	const settings = Object.assign({
		mockPath: './mocks/',
		jsonContentType: 'application/json;charset=utf-8',
		dataset: extendedMockData
	}, options);

	return {
		route: route,
		handle: function (req, res) {
			let jsonData;
			let mockUrlPath = url.parse(req.url).pathname;
			let mockJsonPath = path.join(settings.mockPath + mockUrlPath + '.' + req.method + '.json');
			let dirCatchAll = path.join(require('path').dirname(mockJsonPath)  + '/_.' + req.method + '.json');

			function showError (message, errors) {
				// Print the error in the nodejs console
				console.log(logPrefix + chalk.red(message));

				// Print the error to the browser
				// Return a 404 header, and give a body with the information.
				// This might not be something everybody wants, but for now it seems like a good idea.
				res.statusCode = 404;
				res.setHeader('Content-Type', settings.jsonContentType);
				res.end(JSON.stringify({
					'Message': message,
					'Error': errors
				}));

				// Continue
				return;
			}

			// Tries to get a mock template, if it does not exist we check for a catch all template
			// This is usefull when you have something like /users/:id and still want to provide a response
			try {
				jsonData = fs.readFileSync(mockJsonPath, 'utf8');
			} catch (error) {
				try {
					jsonData = fs.readFileSync(dirCatchAll, 'utf8');
				} catch (error2) {
					showError('Could not find: ' + mockJsonPath + ' or ' + dirCatchAll, [error, error2]);
					return;
				}
			}

			// Will try to parse dummyjson, if it's not sucessfull show an error
			// This is most likely a mistake with the file, and has to be fixed.
			try {
				jsonData = JSON.parse(dummyjson.parse(jsonData, { mockdata: settings.dataset, helpers: settings.helpers }));
			} catch (error) {
				showError('Could not parse Mock-JSON: ' + mockJsonPath, error);
				return;
			}

			// Log requested url in the nodejs console
			// Will help to see what url's get requested. Might be information overload.
			console.log(logPrefix + 'Serving %s request:', req.method, chalk.green(route + req.url), prettyBytes(Buffer.byteLength(JSON.stringify(jsonData), 'utf8')));

			res.statusCode = 200;
			res.setHeader('Content-Type', settings.jsonContentType);
			res.end(JSON.stringify(jsonData));

		}
	};
}

module.exports = MockApiMiddleware;
<|endoftext|>


<commit_before>var https = require('https'),
    crypto = require('crypto'),
    url = require('url');

// Local memory cache for PEM certificates
var pemCache = {};

// keys required in a valid SNS request
var requiredKeys = [
    'Type', 'MessageId', 'TopicArn', 'Message', 'Timestamp',
    'SignatureVersion', 'Signature', 'SigningCertURL'
];

function validateRequest(options, message, callback) {
    // Let's make sure all keys actually exist to avoid errors
    for (var i = 0; i < requiredKeys.length; i++) {
        var key = requiredKeys[i];
        if (!(key in message)) {
            callback(new Error('Invalid request, required key ' + key + ' does not exist'));
            return;
        }
    }

    // short circuit to be able to bypass validation
    if ('verify' in options && !options.verify) {
        callback();
        return;
    }

    var cert = url.parse(message.SigningCertURL),
        arn = message.TopicArn.split(':'),
        region = arn[3];

    if (options.topic !== message.TopicArn) {
        callback(new Error('Topic ARN does not match, expected ' + options.topic + ' got ' + message.TopicArn));
        return;
    }

    // Make sure the certificate comes from the same region
    if (cert.host !== 'sns.' + region + '.amazonaws.com') {
        callback(new Error('Invalid request, invalid cert host ' + cert.host));
        return;
    }

    // check if certificate has been downloaded before and cached
    if (message.SigningCertURL in pemCache) {
        var pem = pemCache[message.SigningCertURL];
        validateMessage(pem, message, callback);
        return;
    }

    https.get(cert, function(res) {
        var pemData = '';
        res.on('data', function(chunk) {
            pemData += chunk;
        });
        res.on('end', function() {
            pemCache[message.SigningCertURL] = pemData;
            validateMessage(pemData, message, callback);
        });

        res.on('error', callback);
    });
}

function validateMessage(pem, message, callback) {
    var msg = buildSignatureString(message);
    if (!msg) {
        callback(new Error('Invalid request, could not build signature string'));
        return;
    }

    var verifier = crypto.createVerify('RSA-SHA1');
    verifier.update(msg, 'utf8');
    if (!verifier.verify(pem, message.Signature, 'base64')) {
        callback(new Error('Invalid request, could not verify signature'));
        return;
    }

    callback();
}

function buildSignatureString(message) {
    var chunks = [];
    switch (message.Type) {
        case 'Notification':
            chunks.push('Message');
            chunks.push(message.Message);
            chunks.push('MessageId');
            chunks.push(message.MessageId);
            if (message.Subject) {
                chunks.push('Subject');
                chunks.push(message.Subject);
            }
            chunks.push('Timestamp');
            chunks.push(message.Timestamp);
            chunks.push('TopicArn');
            chunks.push(message.TopicArn);
            chunks.push('Type');
            chunks.push(message.Type);
            break;
        case 'SubscriptionConfirmation':
            chunks.push('Message');
            chunks.push(message.Message);
            chunks.push('MessageId');
            chunks.push(message.MessageId);
            chunks.push('SubscribeURL');
            chunks.push(message.SubscribeURL);
            chunks.push('Timestamp');
            chunks.push(message.Timestamp);
            chunks.push('Token');
            chunks.push(message.Token);
            chunks.push('TopicArn');
            chunks.push(message.TopicArn);
            chunks.push('Type');
            chunks.push(message.Type);
            break;
        default:
            return false;
    }

    return chunks.join('\n') + '\n';
}

module.exports = function awsSnsMiddleware(opts) {
    opts = opts || {};
    if (typeof(opts.messageHandler) !== 'function') {
        opts.messageHandler = function(req, res, message) {
            req.snsMessage = message;
        };
    }

    return function(req, res, next) {
        var body = '';
        req.on('data', function(chunk) {
            body += chunk;
        });

        req.on('end', function() {
            var message;
            try {
                message = JSON.parse(body);
            } catch (e) {
                next(e);
                return;
            }

            req.snsMessage = message;
            validateRequest(opts, message, function(err) {
                if (err) {
                    next(err);
                    return;
                }

                next();
            });
        });
    };
};
<commit_msg>removed unused option<commit_after>var https = require('https'),
    crypto = require('crypto'),
    url = require('url');

// Local memory cache for PEM certificates
var pemCache = {};

// keys required in a valid SNS request
var requiredKeys = [
    'Type', 'MessageId', 'TopicArn', 'Message', 'Timestamp',
    'SignatureVersion', 'Signature', 'SigningCertURL'
];

function validateRequest(options, message, callback) {
    // Let's make sure all keys actually exist to avoid errors
    for (var i = 0; i < requiredKeys.length; i++) {
        var key = requiredKeys[i];
        if (!(key in message)) {
            callback(new Error('Invalid request, required key ' + key + ' does not exist'));
            return;
        }
    }

    // short circuit to be able to bypass validation
    if ('verify' in options && !options.verify) {
        callback();
        return;
    }

    var cert = url.parse(message.SigningCertURL),
        arn = message.TopicArn.split(':'),
        region = arn[3];

    if (options.topic !== message.TopicArn) {
        callback(new Error('Topic ARN does not match, expected ' + options.topic + ' got ' + message.TopicArn));
        return;
    }

    // Make sure the certificate comes from the same region
    if (cert.host !== 'sns.' + region + '.amazonaws.com') {
        callback(new Error('Invalid request, invalid cert host ' + cert.host));
        return;
    }

    // check if certificate has been downloaded before and cached
    if (message.SigningCertURL in pemCache) {
        var pem = pemCache[message.SigningCertURL];
        validateMessage(pem, message, callback);
        return;
    }

    https.get(cert, function(res) {
        var pemData = '';
        res.on('data', function(chunk) {
            pemData += chunk;
        });
        res.on('end', function() {
            pemCache[message.SigningCertURL] = pemData;
            validateMessage(pemData, message, callback);
        });

        res.on('error', callback);
    });
}

function validateMessage(pem, message, callback) {
    var msg = buildSignatureString(message);
    if (!msg) {
        callback(new Error('Invalid request, could not build signature string'));
        return;
    }

    var verifier = crypto.createVerify('RSA-SHA1');
    verifier.update(msg, 'utf8');
    if (!verifier.verify(pem, message.Signature, 'base64')) {
        callback(new Error('Invalid request, could not verify signature'));
        return;
    }

    callback();
}

function buildSignatureString(message) {
    var chunks = [];
    switch (message.Type) {
        case 'Notification':
            chunks.push('Message');
            chunks.push(message.Message);
            chunks.push('MessageId');
            chunks.push(message.MessageId);
            if (message.Subject) {
                chunks.push('Subject');
                chunks.push(message.Subject);
            }
            chunks.push('Timestamp');
            chunks.push(message.Timestamp);
            chunks.push('TopicArn');
            chunks.push(message.TopicArn);
            chunks.push('Type');
            chunks.push(message.Type);
            break;
        case 'SubscriptionConfirmation':
            chunks.push('Message');
            chunks.push(message.Message);
            chunks.push('MessageId');
            chunks.push(message.MessageId);
            chunks.push('SubscribeURL');
            chunks.push(message.SubscribeURL);
            chunks.push('Timestamp');
            chunks.push(message.Timestamp);
            chunks.push('Token');
            chunks.push(message.Token);
            chunks.push('TopicArn');
            chunks.push(message.TopicArn);
            chunks.push('Type');
            chunks.push(message.Type);
            break;
        default:
            return false;
    }

    return chunks.join('\n') + '\n';
}

module.exports = function awsSnsMiddleware(opts) {
    opts = opts || {};
    return function(req, res, next) {
        var body = '';
        req.on('data', function(chunk) {
            body += chunk;
        });

        req.on('end', function() {
            var message;
            try {
                message = JSON.parse(body);
            } catch (e) {
                next(e);
                return;
            }

            req.snsMessage = message;
            validateRequest(opts, message, function(err) {
                if (err) {
                    next(err);
                    return;
                }

                next();
            });
        });
    };
};
<|endoftext|>


<commit_before>'use strict';
/* globals module, require */

var winston = module.parent.require('winston'),
    async = module.parent.require('async'),
    nconf = module.parent.require('nconf'),

    Meta = module.parent.require('./meta'),
    User = module.parent.require('./user'),
    Posts = module.parent.require('./posts'),
    Topics = module.parent.require('./topics'),
    Privileges = module.parent.require('./privileges'),
    SocketPosts = module.parent.require('./socket.io/posts'),

    mandrill,
    Emailer = {
        settings: {}
    };

Emailer.init = function(data, callback) {

    var render = function(req, res) {
        res.render('admin/plugins/emailer-mandrill', {
            url: nconf.get('url')
        });
    };

    Meta.settings.get('mandrill', function(err, settings) {
        Emailer.settings = Object.freeze(settings);
        Emailer.receiptRegex = new RegExp('^reply-([\\d]+)@' + Emailer.settings.receive_domain + '$');

        if (!err && settings && settings.apiKey) {
            mandrill = require('node-mandrill')(settings.apiKey || 'Replace Me');
        } else {
            winston.error('[plugins/emailer-mandrill] API key not set!');
        }

        data.router.get('/admin/plugins/emailer-mandrill', data.middleware.admin.buildHeader, render);
        data.router.get('/api/admin/plugins/emailer-mandrill', render);
        data.router.head('/emailer-mandrill/reply', function(req, res) {
            res.sendStatus(200);
        });
        data.router.post('/emailer-mandrill/reply', Emailer.receive);

        if (typeof callback === 'function') {
            callback();
        }
    });
};

Emailer.send = function(data, callback) {
    if (mandrill) {
        var headers = {};

        if (data.pid && Emailer.settings.hasOwnProperty('receive_domain')) {
            headers['Reply-To'] = 'reply-' + data.pid + '@' + Emailer.settings.receive_domain;
        }
        async.waterfall([
            function(next) {
                if (data.fromUid) {
                    next(null, data.fromUid);
                } else if (data.pid) {
                    Posts.getPostField(data.pid, 'uid', next);
                } else {
                    next(null, false);
                }
            },
            function(uid, next) {
                if (uid === false) { return next(null, {}); }
                User.getUserFields(parseInt(uid, 10), ['email', 'username'], function(err, userData) {
                    next(null, userData);
                });
            },
            function(userData, next) {
                mandrill('/messages/send', {
                    message: {
                        to: [{email: data.to, name: data.toName}],
                        subject: data.subject,
                        from_email: userData.email || data.from,
                        from_name: data.from_name || userData.username || undefined,
                        html: data.html,
                        text: data.plaintext,
                        auto_text: !data.plaintext,
                        headers: headers
                    }
                }, next);
            }
        ], function (err, response) {
            if (!err) {
                winston.verbose('[emailer.mandrill] Sent `' + data.template + '` email to uid ' + data.uid);
            } else {
                winston.warn('[emailer.mandrill] Unable to send `' + data.template + '` email to uid ' + data.uid + '!!');
                winston.warn('[emailer.mandrill] Error Stringified:' + JSON.stringify(err));
            }
            callback(err);
        });
    } else {
        winston.warn('[plugins/emailer-mandrill] API key not set, not sending email as Mandrill object is not instantiated.');
    }
};

Emailer.receive = function(req, res) {
    var events;

    try {
        events = JSON.parse(req.body.mandrill_events);
    } catch (err) {
        winston.error('[emailer.mandrill] Error parsing response JSON from Mandrill API "Receive" webhook');
        return res.sendStatus(400);
    }

    winston.debug('POST from Mandrill contained ' + events.length + ' items');

    async.eachLimit(events, 5, function(eventObj, next) {
        async.waterfall([
            async.apply(Emailer.verifyEvent, eventObj),
            Emailer.resolveUserOrGuest,
            Emailer.processEvent,
            Emailer.notifyUsers
        ], function(err) {
            Emailer.handleError(err, eventObj);
            next(); // Don't block the continued handling of received events!
        });
    }, function() {
        res.sendStatus(200);
    });
};

Emailer.verifyEvent = function(eventObj, next) {
    // This method verifies that
    //   - the "replyTo pid" is present in the "to" field
    //   - the pid belongs to a valid tid
    // This method also saves `pid` and `tid` into eventObj, and returns it.
    // Returns false if any of the above fail/do not match.
    var pid, match;
    eventObj.msg.to.forEach(function(recipient) {
        match = recipient[0].match(Emailer.receiptRegex);
        if (match && match[1]) { pid = match[1]; }
    });

    if (pid) {
        eventObj.pid = pid;

        Posts.getPostField(pid, 'tid', function(err, tid) {
            if (!err && tid) {
                eventObj.tid = tid;
                next(null, eventObj);
            } else {
                if (!tid) { winston.warn('[emailer.mandrill.verifyEvent] Could not retrieve tid'); }
                next(new Error('invalid-data'));
            }
        });
    } else {
        winston.warn('[emailer.mandrill.verifyEvent] Could not locate post id');
        next(new Error('invalid-data'));
    }
};

Emailer.resolveUserOrGuest = function(eventObj, callback) {
    // This method takes the event object, reads the sender email and resolves it to a uid
    // if the email is set in the system. If not, and guest posting is enabled, the email
    // is treated as a guest instead.
    User.getUidByEmail(eventObj.msg.from_email, function(err, uid) {
        if (uid) {
            eventObj.uid = uid;
            callback(null, eventObj);
        } else {
            // See if guests can post to the category in question
            async.waterfall([
                async.apply(Topics.getTopicField, eventObj.tid, 'cid'),
                function(cid, next) {
                    Privileges.categories.groupPrivileges(cid, 'guests', next);
                }
            ], function(err, privileges) {
                if (privileges['groups:topics:reply']) {
                    eventObj.uid = 0;

                    if (parseInt(Meta.config.allowGuestHandles, 10) === 1) {
                        if (eventObj.msg.from_name && eventObj.msg.from_name.length) {
                            eventObj.handle = eventObj.msg.from_name;
                        } else {
                            eventObj.handle = eventObj.msg.from_email;
                        }
                    }

                    callback(null, eventObj);
                } else {
                    // Guests can't post here
                    winston.verbose('[emailer.mandrill] Received reply by guest to pid ' + eventObj.pid + ', but guests are not allowed to post here.');
                    callback(null, false);
                }
            });
        }
    });
};

Emailer.processEvent = function(eventObj, callback) {
    if (!eventObj) {
        return callback();
    }

    winston.verbose('[emailer.mandrill] Processing incoming email reply by uid ' + eventObj.uid + ' to pid ' + eventObj.pid);
    Topics.reply({
        uid: eventObj.uid,
        toPid: eventObj.pid,
        tid: eventObj.tid,
        content: eventObj.msg.text,
        handle: (eventObj.uid === 0 && eventObj.hasOwnProperty('handle') ? eventObj.handle : undefined)
    }, callback);
};

Emailer.notifyUsers = function(postData, next) {
    var result = {
            posts: [postData],
            privileges: {
                'topics:reply': true
            },
            'reputation:disabled': parseInt(Meta.config['reputation:disabled'], 10) === 1,
            'downvote:disabled': parseInt(Meta.config['downvote:disabled'], 10) === 1,
        };

    SocketPosts.notifyOnlineUsers(parseInt(postData.uid, 10), result);
    next();
};

Emailer.handleError = function(err, eventObj) {
    if (err) {
        switch(err.message) {
            case 'invalid-data':
                // Bounce a return back to sender
                break;
        }
    }
};

Emailer.admin = {
    menu: function(custom_header, callback) {
        custom_header.plugins.push({
            'route': '/plugins/emailer-mandrill',
            'icon': 'fa-envelope-o',
            'name': 'Emailer (Mandrill)'
        });

        callback(null, custom_header);
    }
};

module.exports = Emailer;
<commit_msg>Fixes issue where emailer wasn't adhering to user settings<commit_after>'use strict';
/* globals module, require */

var winston = module.parent.require('winston'),
    async = module.parent.require('async'),
    nconf = module.parent.require('nconf'),

    Meta = module.parent.require('./meta'),
    User = module.parent.require('./user'),
    Posts = module.parent.require('./posts'),
    Topics = module.parent.require('./topics'),
    Privileges = module.parent.require('./privileges'),
    SocketPosts = module.parent.require('./socket.io/posts'),

    mandrill,
    Emailer = {
        settings: {}
    };

Emailer.init = function(data, callback) {

    var render = function(req, res) {
        res.render('admin/plugins/emailer-mandrill', {
            url: nconf.get('url')
        });
    };

    Meta.settings.get('mandrill', function(err, settings) {
        Emailer.settings = Object.freeze(settings);
        Emailer.receiptRegex = new RegExp('^reply-([\\d]+)@' + Emailer.settings.receive_domain + '$');

        if (!err && settings && settings.apiKey) {
            mandrill = require('node-mandrill')(settings.apiKey || 'Replace Me');
        } else {
            winston.error('[plugins/emailer-mandrill] API key not set!');
        }

        data.router.get('/admin/plugins/emailer-mandrill', data.middleware.admin.buildHeader, render);
        data.router.get('/api/admin/plugins/emailer-mandrill', render);
        data.router.head('/emailer-mandrill/reply', function(req, res) {
            res.sendStatus(200);
        });
        data.router.post('/emailer-mandrill/reply', Emailer.receive);

        if (typeof callback === 'function') {
            callback();
        }
    });
};

Emailer.send = function(data, callback) {
    if (mandrill) {
        var headers = {};

        if (data.pid && Emailer.settings.hasOwnProperty('receive_domain')) {
            headers['Reply-To'] = 'reply-' + data.pid + '@' + Emailer.settings.receive_domain;
        }
        async.waterfall([
            function(next) {
                if (data.fromUid) {
                    next(null, data.fromUid);
                } else if (data.pid) {
                    Posts.getPostField(data.pid, 'uid', next);
                } else {
                    next(null, false);
                }
            },
            function(uid, next) {
                if (uid === false) { return next(null, {}); }
                User.getSettings(uid, function(err, settings) {
                    if (err) {
                        return next(err);
                    }

                    if (settings.showemail) {
                        User.getUserFields(parseInt(uid, 10), ['email', 'username'], function(err, userData) {
                            next(null, userData);
                        });
                    } else {
                        User.getUserFields(parseInt(uid, 10), ['username'], function(err, userData) {
                            next(null, userData);
                        });
                    }
                })
            },
            function(userData, next) {
                mandrill('/messages/send', {
                    message: {
                        to: [{email: data.to, name: data.toName}],
                        subject: data.subject,
                        from_email: userData.email || data.from,
                        from_name: data.from_name || userData.username || undefined,
                        html: data.html,
                        text: data.plaintext,
                        auto_text: !data.plaintext,
                        headers: headers
                    }
                }, next);
            }
        ], function (err, response) {
            if (!err) {
                winston.verbose('[emailer.mandrill] Sent `' + data.template + '` email to uid ' + data.uid);
            } else {
                winston.warn('[emailer.mandrill] Unable to send `' + data.template + '` email to uid ' + data.uid + '!!');
                winston.warn('[emailer.mandrill] Error Stringified:' + JSON.stringify(err));
            }
            callback(err);
        });
    } else {
        winston.warn('[plugins/emailer-mandrill] API key not set, not sending email as Mandrill object is not instantiated.');
    }
};

Emailer.receive = function(req, res) {
    var events;

    try {
        events = JSON.parse(req.body.mandrill_events);
    } catch (err) {
        winston.error('[emailer.mandrill] Error parsing response JSON from Mandrill API "Receive" webhook');
        return res.sendStatus(400);
    }

    winston.debug('POST from Mandrill contained ' + events.length + ' items');

    async.eachLimit(events, 5, function(eventObj, next) {
        async.waterfall([
            async.apply(Emailer.verifyEvent, eventObj),
            Emailer.resolveUserOrGuest,
            Emailer.processEvent,
            Emailer.notifyUsers
        ], function(err) {
            Emailer.handleError(err, eventObj);
            next(); // Don't block the continued handling of received events!
        });
    }, function() {
        res.sendStatus(200);
    });
};

Emailer.verifyEvent = function(eventObj, next) {
    // This method verifies that
    //   - the "replyTo pid" is present in the "to" field
    //   - the pid belongs to a valid tid
    // This method also saves `pid` and `tid` into eventObj, and returns it.
    // Returns false if any of the above fail/do not match.
    var pid, match;
    eventObj.msg.to.forEach(function(recipient) {
        match = recipient[0].match(Emailer.receiptRegex);
        if (match && match[1]) { pid = match[1]; }
    });

    if (pid) {
        eventObj.pid = pid;

        Posts.getPostField(pid, 'tid', function(err, tid) {
            if (!err && tid) {
                eventObj.tid = tid;
                next(null, eventObj);
            } else {
                if (!tid) { winston.warn('[emailer.mandrill.verifyEvent] Could not retrieve tid'); }
                next(new Error('invalid-data'));
            }
        });
    } else {
        winston.warn('[emailer.mandrill.verifyEvent] Could not locate post id');
        next(new Error('invalid-data'));
    }
};

Emailer.resolveUserOrGuest = function(eventObj, callback) {
    // This method takes the event object, reads the sender email and resolves it to a uid
    // if the email is set in the system. If not, and guest posting is enabled, the email
    // is treated as a guest instead.
    User.getUidByEmail(eventObj.msg.from_email, function(err, uid) {
        if (uid) {
            eventObj.uid = uid;
            callback(null, eventObj);
        } else {
            // See if guests can post to the category in question
            async.waterfall([
                async.apply(Topics.getTopicField, eventObj.tid, 'cid'),
                function(cid, next) {
                    Privileges.categories.groupPrivileges(cid, 'guests', next);
                }
            ], function(err, privileges) {
                if (privileges['groups:topics:reply']) {
                    eventObj.uid = 0;

                    if (parseInt(Meta.config.allowGuestHandles, 10) === 1) {
                        if (eventObj.msg.from_name && eventObj.msg.from_name.length) {
                            eventObj.handle = eventObj.msg.from_name;
                        } else {
                            eventObj.handle = eventObj.msg.from_email;
                        }
                    }

                    callback(null, eventObj);
                } else {
                    // Guests can't post here
                    winston.verbose('[emailer.mandrill] Received reply by guest to pid ' + eventObj.pid + ', but guests are not allowed to post here.');
                    callback(null, false);
                }
            });
        }
    });
};

Emailer.processEvent = function(eventObj, callback) {
    if (!eventObj) {
        return callback();
    }

    winston.verbose('[emailer.mandrill] Processing incoming email reply by uid ' + eventObj.uid + ' to pid ' + eventObj.pid);
    Topics.reply({
        uid: eventObj.uid,
        toPid: eventObj.pid,
        tid: eventObj.tid,
        content: eventObj.msg.text,
        handle: (eventObj.uid === 0 && eventObj.hasOwnProperty('handle') ? eventObj.handle : undefined)
    }, callback);
};

Emailer.notifyUsers = function(postData, next) {
    var result = {
            posts: [postData],
            privileges: {
                'topics:reply': true
            },
            'reputation:disabled': parseInt(Meta.config['reputation:disabled'], 10) === 1,
            'downvote:disabled': parseInt(Meta.config['downvote:disabled'], 10) === 1,
        };

    SocketPosts.notifyOnlineUsers(parseInt(postData.uid, 10), result);
    next();
};

Emailer.handleError = function(err, eventObj) {
    if (err) {
        switch(err.message) {
            case 'invalid-data':
                // Bounce a return back to sender
                break;
        }
    }
};

Emailer.admin = {
    menu: function(custom_header, callback) {
        custom_header.plugins.push({
            'route': '/plugins/emailer-mandrill',
            'icon': 'fa-envelope-o',
            'name': 'Emailer (Mandrill)'
        });

        callback(null, custom_header);
    }
};

module.exports = Emailer;
<|endoftext|>