palate 0.3.8

File type detection combining tft and hyperpolyglot
Documentation
dnsserver = require "dnsserver"

exports.Server = class Server extends dnsserver.Server
  NS_T_A            = 1
  NS_T_NS           = 2
  NS_T_CNAME        = 5
  NS_T_SOA          = 6
  NS_C_IN           = 1
  NS_RCODE_NXDOMAIN = 3

  constructor: (domain, @rootAddress) ->
    super
    @domain = domain.toLowerCase()
    @soa = createSOA @domain
    @on "request", @handleRequest

  handleRequest: (req, res) =>
    question  = req.question
    subdomain = @extractSubdomain question.name

    if subdomain? and isARequest question
      res.addRR question.name, NS_T_A, NS_C_IN, 600, subdomain.getAddress()
    else if subdomain?.isEmpty() and isNSRequest question
      res.addRR question.name, NS_T_SOA, NS_C_IN, 600, @soa, true
    else
      res.header.rcode = NS_RCODE_NXDOMAIN

    res.send()

  extractSubdomain: (name) ->
    Subdomain.extract name, @domain, @rootAddress

  isARequest = (question) ->
    question.type is NS_T_A and question.class is NS_C_IN

  isNSRequest = (question) ->
    question.type is NS_T_NS and question.class is NS_C_IN

  createSOA = (domain) ->
    mname   = "ns-1.#{domain}"
    rname   = "hostmaster.#{domain}"
    serial  = parseInt new Date().getTime() / 1000
    refresh = 28800
    retry   = 7200
    expire  = 604800
    minimum = 3600
    dnsserver.createSOA mname, rname, serial, refresh, retry, expire, minimum

exports.createServer = (domain, address = "127.0.0.1") ->
  new Server domain, address

exports.Subdomain = class Subdomain
  @extract: (name, domain, address) ->
    return unless name
    name = name.toLowerCase()
    offset = name.length - domain.length

    if domain is name.slice offset
      subdomain = if 0 >= offset then null else name.slice 0, offset - 1
      new constructor subdomain, address if constructor = @for subdomain

  @for: (subdomain = "") ->
    if IPAddressSubdomain.pattern.test subdomain
      IPAddressSubdomain
    else if EncodedSubdomain.pattern.test subdomain
      EncodedSubdomain
    else
      Subdomain

  constructor: (@subdomain, @address) ->
    @labels = subdomain?.split(".") ? []
    @length = @labels.length

  isEmpty: ->
    @length is 0

  getAddress: ->
    @address

class IPAddressSubdomain extends Subdomain
  @pattern = /// (^|\.)
    ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
    (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
  $ ///

  getAddress: ->
    @labels.slice(-4).join "."

class EncodedSubdomain extends Subdomain
  @pattern = /(^|\.)[a-z0-9]{1,7}$/

  getAddress: ->
    decode @labels[@length - 1]

exports.encode = encode = (ip) ->
  value = 0
  for byte, index in ip.split "."
    value += parseInt(byte, 10) << (index * 8)
  (value >>> 0).toString 36

PATTERN = /^[a-z0-9]{1,7}$/

exports.decode = decode = (string) ->
  return unless PATTERN.test string
  value = parseInt string, 36
  ip = []
  for i in [1..4]
    ip.push value & 0xFF
    value >>= 8
  ip.join "."