<commit_before>
var Cookies = require("cookies");
var Proxy = require("node-proxy");
var Handler = require("./ProxyHandler.js");
var crypto = require("crypto");

function base64urlencode(arg) {
  var s = new Buffer(arg).toString('base64');
  s = s.split('=')[0]; // Remove any trailing '='s
  s = s.replace(/\+/g, '-'); // 62nd char of encoding
  s = s.replace(/\//g, '_'); // 63rd char of encoding
  // TODO optimize this; we can do much better
  return s;
}

function base64urldecode(arg) {
  var s = arg;
  s = s.replace(/-/g, '+'); // 62nd char of encoding
  s = s.replace(/_/g, '/'); // 63rd char of encoding
  switch (s.length % 4) // Pad with trailing '='s
  {
  case 0: break; // No pad chars in this case
  case 2: s += "=="; break; // Two pad chars
  case 3: s += "="; break; // One pad char
  default: throw new InputException("Illegal base64url string!");
  }
  return new Buffer(s, 'base64').toString('ascii'); // Standard base64 decoder
}

/*
 * Session object
 *
 * this should be implemented with proxies at some point
 */
function Session(req, res, cookies, opts) {
  this.req = req;
  this.res = res;
  this.cookies = cookies;
  this.opts = opts;
  this.content = {};
  this.loaded = false;
  this.dirty = false;

  // support for maxAge
  if (opts.cookie.maxAge) {
    this.expires = new Date(new Date().getTime() + opts.cookie.maxAge);
  }
}

Session.prototype = {
  clearContent: function(keysToPreserve) {
    var self = this;
    Object.keys(this.content).forEach(function(k) {
      // exclude this key if it's meant to be preserved
      if (keysToPreserve && (keysToPreserve.indexOf(k) > -1))
        return;

      delete self.content[k];
    });
  },

  reset: function(keysToPreserve) {
    this.clearContent(keysToPreserve);
    this.dirty = true;
    this.updateCookie();
  },

  // take the content and do the encrypt-and-sign
  box: function() {
    // format will be:
    // iv.ciphertext.hmac

    // generate iv
    var iv = crypto.randomBytes(16).toString('binary');

    // encrypt with encryption key
    var cipher = crypto.createCipheriv('aes256', this.opts.encryptionKey, iv);
    var ciphertext = cipher.update(JSON.stringify(this.content));
    ciphertext += cipher.final();

    // hmac it
    var hmacAlg = crypto.createHmac('sha256', this.opts.signatureKey);
    hmacAlg.update(ciphertext);
    var hmac = hmacAlg.digest();

    return base64urlencode(iv) + "." + base64urlencode(ciphertext) + "." +base64urlencode(hmac);
  },

  unbox: function(content) {
    this.clearContent();

    // stop at any time if there's an issue

    var components = content.split(".");
    if (components.length != 3)
      return;

    // check hmac
    var ciphertext = base64urldecode(components[1]);
    var hmac = base64urldecode(components[2]);
    var hmacAlg = crypto.createHmac('sha256', this.opts.signatureKey);
    hmacAlg.update(ciphertext);
    var expected_hmac = hmacAlg.digest();
    if (hmac != expected_hmac)
      return;

    // decrypt
    var iv = base64urldecode(components[0]);
    var cipher = crypto.createDecipheriv('aes256', this.opts.encryptionKey, iv);
    var plaintext = cipher.update(ciphertext);
    plaintext += cipher.final();

    try {
      var new_content = JSON.parse(plaintext);
      var self = this;
      Object.keys(new_content).forEach(function(k) {
        self.content[k] = new_content[k];
      });
    } catch (x) {
      // do nothing
    }
  },

  updateCookie: function() {
    if (this.dirty) {
      // support for expires
      if (this.expires) {
        this.opts.cookie.expires = this.expires;
      }
      this.cookies.set(this.opts.cookieName, this.box(), this.opts.cookie);
    }
  },

  loadFromCookie: function() {
    var cookie = this.cookies.get(this.opts.cookieName);
    if (cookie) {
      this.unbox(cookie);
    } else {
      this.clearContent();
    }

    this.loaded = true;
  },

  // called to create a proxy that monitors the session
  // for new properties being set
  monitor: function() {
    // the target for the proxy will be the content
    // variable, to simplify the proxying
    var sessionHandler = new Handler(this.content);
    var raw_session = this;

    // all values from content except special values
    sessionHandler.get = function(rcvr, name) {
      if (['reset'].indexOf(name) > -1) {
        return raw_session[name].bind(raw_session);
      } else {
        if (!raw_session.loaded)
          raw_session.loadFromCookie();
        return this.target[name];
      }
    };

    // set all values to content
    sessionHandler.set = function(rcvr, name, value) {
      // we have to load existing content, otherwise it will later override
      // the content that is written.
      if (!raw_session.loaded)
        raw_session.loadFromCookie();

      this.target[name] = value;
      raw_session.dirty = true;
    };

    // if key is deleted
    sessionHandler.delete = function(name) {
      // we have to load existing content, otherwise it will later override
      // the content that is written.
      if (!raw_session.loaded)
        raw_session.loadFromCookie();

      delete this.target[name];
      raw_session.dirty = true;
    };

    var proxySession = Proxy.create(sessionHandler);
    return proxySession;
  }
};

function deriveKey(master, type) {
  var hmac = crypto.createHmac('sha256', master);
  hmac.update(type);
  return hmac.digest('binary');
}

var cookieSession = function(opts) {
  if (!opts)
    throw "no options provided, some are required"; // XXX rename opts?

  if (!opts.secret)
    throw "cannot set up sessions without a secret";

  // defaults
  opts.cookieName = opts.cookieName || "session_state";
  opts.duration = opts.duration || 24*60*60*1000;

  // set up cookie defaults
  opts.cookie = opts.cookie || {};
  if (typeof(opts.cookie.httpOnly) == 'undefined')
    opts.cookie.httpOnly = true;

  // let's not default to secure just yet,
  // as this depends on the socket being secure,
  // which is tricky to determine if proxied.
  /*
  if (typeof(opts.cookie.secure) == 'undefined')
    opts.cookie.secure = true;
    */

  // derive two keys, one for signing one for encrypting, from the secret.
  // eventually we want to use HKDF. For now we'll do something simpler.
  opts.encryptionKey = deriveKey(opts.secret, 'cookiesession-encryption');
  opts.signatureKey = deriveKey(opts.secret, 'cookiesession-signature');

  return function(req, res, next) {
    var cookies = new Cookies(req, res);
    var raw_session = new Session(req, res, cookies, opts);
    req.session = raw_session.monitor();

    // I wish we didn't have to do things this way, but
    // I can find no other way of delaying the setting of
    // the cookie until the end
    var oldWriteHead = res.writeHead;
    res.writeHead = function() {
      raw_session.updateCookie();

      oldWriteHead.apply(this, arguments);
    };

    next();
  };
};

module.exports = cookieSession;<commit_msg>make sure IV is part of the HMAC, we don't want a partial decryption oracle in our midst.<commit_after>
var Cookies = require("cookies");
var Proxy = require("node-proxy");
var Handler = require("./ProxyHandler.js");
var crypto = require("crypto");

function base64urlencode(arg) {
  var s = new Buffer(arg).toString('base64');
  s = s.split('=')[0]; // Remove any trailing '='s
  s = s.replace(/\+/g, '-'); // 62nd char of encoding
  s = s.replace(/\//g, '_'); // 63rd char of encoding
  // TODO optimize this; we can do much better
  return s;
}

function base64urldecode(arg) {
  var s = arg;
  s = s.replace(/-/g, '+'); // 62nd char of encoding
  s = s.replace(/_/g, '/'); // 63rd char of encoding
  switch (s.length % 4) // Pad with trailing '='s
  {
  case 0: break; // No pad chars in this case
  case 2: s += "=="; break; // Two pad chars
  case 3: s += "="; break; // One pad char
  default: throw new InputException("Illegal base64url string!");
  }
  return new Buffer(s, 'base64').toString('ascii'); // Standard base64 decoder
}

/*
 * Session object
 *
 * this should be implemented with proxies at some point
 */
function Session(req, res, cookies, opts) {
  this.req = req;
  this.res = res;
  this.cookies = cookies;
  this.opts = opts;
  this.content = {};
  this.loaded = false;
  this.dirty = false;

  // support for maxAge
  if (opts.cookie.maxAge) {
    this.expires = new Date(new Date().getTime() + opts.cookie.maxAge);
  }
}

Session.prototype = {
  clearContent: function(keysToPreserve) {
    var self = this;
    Object.keys(this.content).forEach(function(k) {
      // exclude this key if it's meant to be preserved
      if (keysToPreserve && (keysToPreserve.indexOf(k) > -1))
        return;

      delete self.content[k];
    });
  },

  reset: function(keysToPreserve) {
    this.clearContent(keysToPreserve);
    this.dirty = true;
    this.updateCookie();
  },

  // take the content and do the encrypt-and-sign
  box: function() {
    // format will be:
    // iv.ciphertext.hmac

    // generate iv
    var iv = crypto.randomBytes(16).toString('binary');

    // encrypt with encryption key
    var cipher = crypto.createCipheriv('aes256', this.opts.encryptionKey, iv);
    var ciphertext = cipher.update(JSON.stringify(this.content));
    ciphertext += cipher.final();

    // hmac it
    var hmacAlg = crypto.createHmac('sha256', this.opts.signatureKey);
    hmacAlg.update(iv);
    hmacAlg.update(ciphertext);
    var hmac = hmacAlg.digest();

    return base64urlencode(iv) + "." + base64urlencode(ciphertext) + "." +base64urlencode(hmac);
  },

  unbox: function(content) {
    this.clearContent();

    // stop at any time if there's an issue

    var components = content.split(".");
    if (components.length != 3)
      return;

    var iv = base64urldecode(components[0]);
    var ciphertext = base64urldecode(components[1]);
    var hmac = base64urldecode(components[2]);

    // make sure IV is right length
    if (iv.length != 16)
      return;

    // check hmac
    var hmacAlg = crypto.createHmac('sha256', this.opts.signatureKey);
    hmacAlg.update(iv);
    hmacAlg.update(ciphertext);
    var expected_hmac = hmacAlg.digest();
    if (hmac != expected_hmac)
      return;

    // decrypt
    var cipher = crypto.createDecipheriv('aes256', this.opts.encryptionKey, iv);
    var plaintext = cipher.update(ciphertext);
    plaintext += cipher.final();

    try {
      var new_content = JSON.parse(plaintext);
      var self = this;
      Object.keys(new_content).forEach(function(k) {
        self.content[k] = new_content[k];
      });
    } catch (x) {
      // do nothing
    }
  },

  updateCookie: function() {
    if (this.dirty) {
      // support for expires
      if (this.expires) {
        this.opts.cookie.expires = this.expires;
      }
      this.cookies.set(this.opts.cookieName, this.box(), this.opts.cookie);
    }
  },

  loadFromCookie: function() {
    var cookie = this.cookies.get(this.opts.cookieName);
    if (cookie) {
      this.unbox(cookie);
    } else {
      this.clearContent();
    }

    this.loaded = true;
  },

  // called to create a proxy that monitors the session
  // for new properties being set
  monitor: function() {
    // the target for the proxy will be the content
    // variable, to simplify the proxying
    var sessionHandler = new Handler(this.content);
    var raw_session = this;

    // all values from content except special values
    sessionHandler.get = function(rcvr, name) {
      if (['reset'].indexOf(name) > -1) {
        return raw_session[name].bind(raw_session);
      } else {
        if (!raw_session.loaded)
          raw_session.loadFromCookie();
        return this.target[name];
      }
    };

    // set all values to content
    sessionHandler.set = function(rcvr, name, value) {
      // we have to load existing content, otherwise it will later override
      // the content that is written.
      if (!raw_session.loaded)
        raw_session.loadFromCookie();

      this.target[name] = value;
      raw_session.dirty = true;
    };

    // if key is deleted
    sessionHandler.delete = function(name) {
      // we have to load existing content, otherwise it will later override
      // the content that is written.
      if (!raw_session.loaded)
        raw_session.loadFromCookie();

      delete this.target[name];
      raw_session.dirty = true;
    };

    var proxySession = Proxy.create(sessionHandler);
    return proxySession;
  }
};

function deriveKey(master, type) {
  // eventually we want to use HKDF. For now we'll do something simpler.
  var hmac = crypto.createHmac('sha256', master);
  hmac.update(type);
  return hmac.digest('binary');
}

var cookieSession = function(opts) {
  if (!opts)
    throw "no options provided, some are required"; // XXX rename opts?

  if (!opts.secret)
    throw "cannot set up sessions without a secret";

  // defaults
  opts.cookieName = opts.cookieName || "session_state";
  opts.duration = opts.duration || 24*60*60*1000;

  // set up cookie defaults
  opts.cookie = opts.cookie || {};
  if (typeof(opts.cookie.httpOnly) == 'undefined')
    opts.cookie.httpOnly = true;

  // let's not default to secure just yet,
  // as this depends on the socket being secure,
  // which is tricky to determine if proxied.
  /*
  if (typeof(opts.cookie.secure) == 'undefined')
    opts.cookie.secure = true;
    */

  // derive two keys, one for signing one for encrypting, from the secret.
  opts.encryptionKey = deriveKey(opts.secret, 'cookiesession-encryption');
  opts.signatureKey = deriveKey(opts.secret, 'cookiesession-signature');

  return function(req, res, next) {
    var cookies = new Cookies(req, res);
    var raw_session = new Session(req, res, cookies, opts);
    req.session = raw_session.monitor();

    // I wish we didn't have to do things this way, but
    // I can find no other way of delaying the setting of
    // the cookie until the end
    var oldWriteHead = res.writeHead;
    res.writeHead = function() {
      raw_session.updateCookie();

      oldWriteHead.apply(this, arguments);
    };

    next();
  };
};

module.exports = cookieSession;<|endoftext|>


<commit_before>import { sort } from "ramda"

import List from "./list"
import Redis from "./redis"

import Game from "./game"

const UNIVERSE_CHANNEL = "universe"
const USERS_KEY = "universe:users"

export { UNIVERSE_CHANNEL }

export default class Universe {
  static async init() {
    this.lobby = null
    this.redis = new Redis()
    this.games = new List("games")

    await this.redis.setAsync(USERS_KEY, 0)

    return this
  }

  static async match(client) {
    if (this.lobby === null) {
      this.lobby = client
      return false

    } else if (this.lobby.uuid === client.uuid) {
      return false

    } else {
      const opponent = this.lobby
      this.lobby = null

      const users = sort(() => Math.random, [opponent.user, client.user])
      const game = await Game.create(users[0], users[1])

      await game.refresh()
      this.games.push(game.get("uuid"))

      return { opponent, game }
    }
  }

  static async addClient(client) {
    await client.redis.subscribe(UNIVERSE_CHANNEL)

    this.redis.multi()
      .incr(USERS_KEY)
      .publish(UNIVERSE_CHANNEL, "")
      .exec()

    // ensure message is sent at least once
    process.nextTick(client.sendUniverse.bind(client))
  }

  static removeClient(client) {
    client.redis.end(true)

    this.redis.multi()
      .decr(USERS_KEY)
      .publish(UNIVERSE_CHANNEL, "")
      .exec()

    if (this.lobby && this.lobby.uuid === client.uuid) {
      this.lobby = null
    }
  }

  static async users() {
    return await this.redis.getAsync(USERS_KEY)
  }

  static async serialize() {
    return {
      activeUsers: parseInt(await this.users(), 10),
      games: await this.games.length()
    }
  }
}
<commit_msg>publish event when game count changes<commit_after>import { sort } from "ramda"

import List from "./list"
import Redis from "./redis"

import Game from "./game"

const UNIVERSE_CHANNEL = "universe"
const USERS_KEY = "universe:users"

export { UNIVERSE_CHANNEL }

export default class Universe {
  static async init() {
    this.lobby = null
    this.redis = new Redis()
    this.games = new List("games")

    await this.redis.setAsync(USERS_KEY, 0)

    return this
  }

  static async match(client) {
    if (this.lobby === null) {
      this.lobby = client
      return false

    } else if (this.lobby.uuid === client.uuid) {
      return false

    } else {
      const opponent = this.lobby
      this.lobby = null

      const users = sort(() => Math.random, [opponent.user, client.user])
      const game = await Game.create(users[0], users[1])

      await game.refresh()
      this.games.push(game.get("uuid"))

      this.redis.publish(UNIVERSE_CHANNEL, "")

      return { opponent, game }
    }
  }

  static async addClient(client) {
    await client.redis.subscribe(UNIVERSE_CHANNEL)

    this.redis.multi()
      .incr(USERS_KEY)
      .publish(UNIVERSE_CHANNEL, "")
      .exec()

    // ensure message is sent at least once
    process.nextTick(client.sendUniverse.bind(client))
  }

  static removeClient(client) {
    client.redis.end(true)

    this.redis.multi()
      .decr(USERS_KEY)
      .publish(UNIVERSE_CHANNEL, "")
      .exec()

    if (this.lobby && this.lobby.uuid === client.uuid) {
      this.lobby = null
    }
  }

  static async users() {
    return await this.redis.getAsync(USERS_KEY)
  }

  static async serialize() {
    return {
      activeUsers: parseInt(await this.users(), 10),
      games: await this.games.length()
    }
  }
}
<|endoftext|>


<commit_before>import assert from 'assert'

import { specificResourceToUri, uriToSpecificResource } from '../src'

// Examples from the spec:
// https://www.w3.org/TR/selectors-states/#json-examples-converted-to-fragment-identifiers
// One difference with the spec: we percent-encode the characters < > [ ] inside fragments.
const pairs = {

    'Example 2 <=> 16: Fragment selector': {
        uri: 'http://example.org/video1#selector(type=FragmentSelector,conformsTo=http://www.w3.org/TR/media-frags/,value=t%3D30%2C60)',
        obj: {
            "source": "http://example.org/video1",
            "selector": {
              "type": "FragmentSelector",
              "conformsTo": "http://www.w3.org/TR/media-frags/",
              "value": "t=30,60"
            }
        }
    },

    'Example 3 <=> 17: CSS selector': {
        uri: 'http://example.org/page1.html#selector(type=CssSelector,value=%23elemid%20%3E%20.elemclass%20+%20p)',
        obj: {
            "source": "http://example.org/page1.html",
            "selector": {
              "type": "CssSelector",
              "value": "#elemid > .elemclass + p"
            }
        }
    },

    'Example 4 <=> 18: XPath selector': {
        uri: 'http://example.org/page1.html#selector(type=XPathSelector,value=/html/body/p%5B2%5D/table/tr%5B2%5D/td%5B3%5D/span)',
        obj: {
            "source": "http://example.org/page1.html",
            "selector": {
              "type": "XPathSelector",
              "value": "/html/body/p[2]/table/tr[2]/td[3]/span"
            }
        }
    },

    'Example 5 <=> 19: TextQuote selector': {
        uri: 'http://example.org/page1#selector(type=TextQuoteSelector,exact=annotation,prefix=this%20is%20an%20,suffix=%20that%20has%20some)',
        obj: {
            "source": "http://example.org/page1",
            "selector": {
              "type": "TextQuoteSelector",
              "exact": "annotation",
              "prefix": "this is an ",
              "suffix": " that has some"
            }
        }
    },

    'Example 6 <=> 20: TextPosition selector': {
        uri: 'http://example.org/ebook1#selector(type=TextPositionSelector,start=412,end=795)',
        obj: {
            "source": "http://example.org/ebook1",
            "selector": {
              "type": "TextPositionSelector",
              "start": 412,
              "end": 795
            }
        }
    },

    'Example 7 <=> 21: Data position selector': {
        uri: 'http://example.org/diskimg1#selector(type=DataPositionSelector,start=4096,end=4104)',
        obj: {
            "source": "http://example.org/diskimg1",
            "selector": {
              "type": "DataPositionSelector",
              "start": 4096,
              "end": 4104
            }
        }
    },

    'Example 8 <=> 22: SVG selector; external SVG': {
        uri: 'http://example.org/map1#selector(type=SvgSelector,id=http://example.org/svg1)',
        obj: {
            "source": "http://example.org/map1",
            "selector": {
              "type": "SvgSelector",
              "id": "http://example.org/svg1"
            }
        }
    },

    'Example 9 <=> 23: SVG selector; embedded SVG': {
        uri: 'http://example.org/map1#selector(type=SvgSelector,value=%3Csvg:svg%3E%20...%20%3C/svg:svg%3E)',
        obj: {
            "source": "http://example.org/map1",
            "selector": {
              "type": "SvgSelector",
              "value": "<svg:svg> ... </svg:svg>"
            }
        }
    },

    'Example 10 <=> 24: Range selector': {
        uri: 'http://example.org/page1.html#selector(type=RangeSelector,startSelector=selector(type=XPathSelector,value=//table%5B1%5D/tr%5B1%5D/td%5B2%5D),endSelector=selector(type=XPathSelector,value=//table%5B1%5D/tr%5B1%5D/td%5B4%5D))',
        obj: {
            "source": "http://example.org/page1.html",
            "selector": {
              "type": "RangeSelector",
              "startSelector": {
                "type": "XPathSelector",
                "value": "//table[1]/tr[1]/td[2]"
              },
              "endSelector": {
                "type": "XPathSelector",
                "value": "//table[1]/tr[1]/td[4]"
              }
            }
        }
    },

    'Example 11 <=> 25: Selector refinement': {
        uri: 'http://example.org/page1#selector(type=FragmentSelector,value=para5,refinedBy=selector(type=TextQuoteSelector,exact=Selected%20Text,prefix=text%20before%20the%20,suffix=%20and%20text%20after%20it))',
        obj: {
            "source": "http://example.org/page1",
            "selector": {
              "type": "FragmentSelector",
              "value": "para5",
              "refinedBy": {
                "type": "TextQuoteSelector",
                "exact": "Selected Text",
                "prefix": "text before the ",
                "suffix": " and text after it"
              }
            }
        }
    },

    'Example 13 <=> 26: Time state': {
        uri: 'http://example.org/page1#state(type=TimeState,cached=http://archive.example.org/copy1,sourceDate=2015-07-20T13:30:00Z)',
        obj: {
            "source": "http://example.org/page1",
            "state": {
              "type": "TimeState",
              "cached": "http://archive.example.org/copy1",
              "sourceDate": "2015-07-20T13:30:00Z"
            }
        }
    },

    'Example 14 <=> 27: HTTP request state': {
        uri: 'http://example.org/resource1#state(type=HttpRequestState,value=Accept:%20application/pdf)',
        obj: {
            "source": "http://example.org/resource1",
            "state": {
              "type": "HttpRequestState",
              "value": "Accept: application/pdf"
            }
        }
    },

    'Example 15 <=> 28: Refinement of states': {
        uri: 'http://example.org/ebook1#state(type=TimeState,sourceDate=2016-02-01T12:05:23Z,refinedBy=state(type=HttpRequestState,value=Accept:%20application/epub+zip))',
        obj: {
            "source": "http://example.org/ebook1",
            "state": {
              "type": "TimeState",
              "sourceDate": "2016-02-01T12:05:23Z",
              "refinedBy": {
                "type": "HttpRequestState",
                "value": "Accept: application/epub+zip"
              }
            }
        }
    },

    'Example 29: Serializing IRI to URL (and vice versa)': {
        uri: 'http://jp.example.org/page1#selector(type=TextQuoteSelector,exact=%E3%83%9A%E3%83%B3%E3%82%92,prefix=%E7%A7%81%E3%81%AF%E3%80%81,suffix=%E6%8C%81%E3%81%A3%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99)',
        obj: {
            selector: {
                type: 'TextQuoteSelector',
                exact: 'ペンを',
                prefix: '私は、',
                suffix: '持っています',
            },
            source: "http://jp.example.org/page1",
        }
    },

    'Fragment identifier without URI': {
        uri: '#selector(type=TextPositionSelector,start=12,end=795)',
        obj: {
            "selector": {
                "type": "TextPositionSelector",
                "start": 12,
                "end": 795
            },
            "source": ""
        }
    },

    'URI without fragment identifier': {
        uri: 'https://example.org',
        obj: {
            "source": "https://example.org"
        }
    },
}

const specialCasesToStringify = {
    'Value with parentheses (to be percent-encoded)': {
        uri: '#selector(type=TextQuoteSelector,exact=example%20%28with%20parentheses%29)',
        obj: {
            selector: {
                type: 'TextQuoteSelector',
                exact: 'example (with parentheses)',
            },
        },
    },
}

const specialCasesToParse = {
    'One closing parenthesis inside a value': {
        uri: '#selector(type=TextQuoteSelector,exact=(not)%20a%20problem)',
        obj: {
            source: '',
            selector: {
                type: 'TextQuoteSelector',
                exact: '(not) a problem',
            },
        },
    },
}

const specialCasesToParseThatFail = {
    'Two closing parentheses inside a value': {
        uri: '#selector(type=TextQuoteSelector,exact=Hey))%20this%20breaks)',
        obj: {
            source: '',
            selector: {
                type: 'TextQuoteSelector',
                exact: 'Hey)) this breaks',
            },
        },
    },

    'Two closing parentheses: one of value, one of selector': {
        uri: '#selector(type=TextQuoteSelector,exact=example%20(that%20fails))',
        obj: {
            source: '',
            selector: {
                type: 'TextQuoteSelector',
                exact: 'example (that fails)',
            },
        },
    },

    'Three closing parentheses: one of the value, two of nested selectors': {
        uri: `
            #selector(
                type=RangeSelector,
                startSelector=selector(type=TextQuoteSelector,exact=(but),
                endSelector=selector(type=TextQuoteSelector,exact=crazy))
            )
            `.replace(/\s/g, ''),
        obj: {
            source: '',
            selector: {
                type: 'RangeSelector',
                startSelector: {
                    type: 'TextQuoteSelector',
                    exact: '(but',
                },
                endSelector: {
                    type: 'TextQuoteSelector',
                    exact: 'crazy)',
                },
            },
        },
    },
}

describe('specificResourceToUri', () => {
    const examples = { ...pairs, ...specialCasesToStringify }
    for (const [name, example] of Object.entries(examples)) {
        it(`should properly convert: '${name}'`, () => {
            const uri = specificResourceToUri(example.obj)
            assert.equal(uri, example.uri)
        })
    }
})

describe('uriToSpecificResource', () => {
    for (const [name, example] of Object.entries(pairs)) {
        it(`should properly convert: '${name}'`, () => {
            const obj = uriToSpecificResource(example.uri)
            assert.deepEqual(obj, example.obj)
        })
    }

    it('should cleanly handle other types of fragment identifiers', () => {
        const obj = uriToSpecificResource('https://example.com/page#section4')
        const expected = {
            source: 'https://example.com/page',
            selector: {
                type: 'FragmentSelector',
                value: 'section4',
            }
        }
        assert.deepEqual(obj, expected)
    })

    for (const [name, example] of Object.entries(specialCasesToParse)) {
        it(`should parse special case: '${name}'`, () => {
            const obj = uriToSpecificResource(example.uri)
            assert.deepEqual(obj, example.obj)
        })
    }

    // FIXME These tests are known to fail. Seems impossible to parse
    // them with PEG.js. See <https://github.com/w3c/web-annotation/issues/443>
    for (const [name, example] of Object.entries(specialCasesToParseThatFail)) {
        it.skip(`should parse special case: '${name}'`, () => {
            const obj = uriToSpecificResource(example.uri)
            assert.deepEqual(obj, example.obj)
        })
    }
})
<commit_msg>Add test for special characters<commit_after>import assert from 'assert'

import { specificResourceToUri, uriToSpecificResource } from '../src'

// Examples from the spec:
// https://www.w3.org/TR/selectors-states/#json-examples-converted-to-fragment-identifiers
// One difference with the spec: we percent-encode the characters < > [ ] inside fragments.
const pairs = {

    'Example 2 <=> 16: Fragment selector': {
        uri: 'http://example.org/video1#selector(type=FragmentSelector,conformsTo=http://www.w3.org/TR/media-frags/,value=t%3D30%2C60)',
        obj: {
            "source": "http://example.org/video1",
            "selector": {
              "type": "FragmentSelector",
              "conformsTo": "http://www.w3.org/TR/media-frags/",
              "value": "t=30,60"
            }
        }
    },

    'Example 3 <=> 17: CSS selector': {
        uri: 'http://example.org/page1.html#selector(type=CssSelector,value=%23elemid%20%3E%20.elemclass%20+%20p)',
        obj: {
            "source": "http://example.org/page1.html",
            "selector": {
              "type": "CssSelector",
              "value": "#elemid > .elemclass + p"
            }
        }
    },

    'Example 4 <=> 18: XPath selector': {
        uri: 'http://example.org/page1.html#selector(type=XPathSelector,value=/html/body/p%5B2%5D/table/tr%5B2%5D/td%5B3%5D/span)',
        obj: {
            "source": "http://example.org/page1.html",
            "selector": {
              "type": "XPathSelector",
              "value": "/html/body/p[2]/table/tr[2]/td[3]/span"
            }
        }
    },

    'Example 5 <=> 19: TextQuote selector': {
        uri: 'http://example.org/page1#selector(type=TextQuoteSelector,exact=annotation,prefix=this%20is%20an%20,suffix=%20that%20has%20some)',
        obj: {
            "source": "http://example.org/page1",
            "selector": {
              "type": "TextQuoteSelector",
              "exact": "annotation",
              "prefix": "this is an ",
              "suffix": " that has some"
            }
        }
    },

    'Example 6 <=> 20: TextPosition selector': {
        uri: 'http://example.org/ebook1#selector(type=TextPositionSelector,start=412,end=795)',
        obj: {
            "source": "http://example.org/ebook1",
            "selector": {
              "type": "TextPositionSelector",
              "start": 412,
              "end": 795
            }
        }
    },

    'Example 7 <=> 21: Data position selector': {
        uri: 'http://example.org/diskimg1#selector(type=DataPositionSelector,start=4096,end=4104)',
        obj: {
            "source": "http://example.org/diskimg1",
            "selector": {
              "type": "DataPositionSelector",
              "start": 4096,
              "end": 4104
            }
        }
    },

    'Example 8 <=> 22: SVG selector; external SVG': {
        uri: 'http://example.org/map1#selector(type=SvgSelector,id=http://example.org/svg1)',
        obj: {
            "source": "http://example.org/map1",
            "selector": {
              "type": "SvgSelector",
              "id": "http://example.org/svg1"
            }
        }
    },

    'Example 9 <=> 23: SVG selector; embedded SVG': {
        uri: 'http://example.org/map1#selector(type=SvgSelector,value=%3Csvg:svg%3E%20...%20%3C/svg:svg%3E)',
        obj: {
            "source": "http://example.org/map1",
            "selector": {
              "type": "SvgSelector",
              "value": "<svg:svg> ... </svg:svg>"
            }
        }
    },

    'Example 10 <=> 24: Range selector': {
        uri: 'http://example.org/page1.html#selector(type=RangeSelector,startSelector=selector(type=XPathSelector,value=//table%5B1%5D/tr%5B1%5D/td%5B2%5D),endSelector=selector(type=XPathSelector,value=//table%5B1%5D/tr%5B1%5D/td%5B4%5D))',
        obj: {
            "source": "http://example.org/page1.html",
            "selector": {
              "type": "RangeSelector",
              "startSelector": {
                "type": "XPathSelector",
                "value": "//table[1]/tr[1]/td[2]"
              },
              "endSelector": {
                "type": "XPathSelector",
                "value": "//table[1]/tr[1]/td[4]"
              }
            }
        }
    },

    'Example 11 <=> 25: Selector refinement': {
        uri: 'http://example.org/page1#selector(type=FragmentSelector,value=para5,refinedBy=selector(type=TextQuoteSelector,exact=Selected%20Text,prefix=text%20before%20the%20,suffix=%20and%20text%20after%20it))',
        obj: {
            "source": "http://example.org/page1",
            "selector": {
              "type": "FragmentSelector",
              "value": "para5",
              "refinedBy": {
                "type": "TextQuoteSelector",
                "exact": "Selected Text",
                "prefix": "text before the ",
                "suffix": " and text after it"
              }
            }
        }
    },

    'Example 13 <=> 26: Time state': {
        uri: 'http://example.org/page1#state(type=TimeState,cached=http://archive.example.org/copy1,sourceDate=2015-07-20T13:30:00Z)',
        obj: {
            "source": "http://example.org/page1",
            "state": {
              "type": "TimeState",
              "cached": "http://archive.example.org/copy1",
              "sourceDate": "2015-07-20T13:30:00Z"
            }
        }
    },

    'Example 14 <=> 27: HTTP request state': {
        uri: 'http://example.org/resource1#state(type=HttpRequestState,value=Accept:%20application/pdf)',
        obj: {
            "source": "http://example.org/resource1",
            "state": {
              "type": "HttpRequestState",
              "value": "Accept: application/pdf"
            }
        }
    },

    'Example 15 <=> 28: Refinement of states': {
        uri: 'http://example.org/ebook1#state(type=TimeState,sourceDate=2016-02-01T12:05:23Z,refinedBy=state(type=HttpRequestState,value=Accept:%20application/epub+zip))',
        obj: {
            "source": "http://example.org/ebook1",
            "state": {
              "type": "TimeState",
              "sourceDate": "2016-02-01T12:05:23Z",
              "refinedBy": {
                "type": "HttpRequestState",
                "value": "Accept: application/epub+zip"
              }
            }
        }
    },

    'Example 29: Serializing IRI to URL (and vice versa)': {
        uri: 'http://jp.example.org/page1#selector(type=TextQuoteSelector,exact=%E3%83%9A%E3%83%B3%E3%82%92,prefix=%E7%A7%81%E3%81%AF%E3%80%81,suffix=%E6%8C%81%E3%81%A3%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99)',
        obj: {
            selector: {
                type: 'TextQuoteSelector',
                exact: 'ペンを',
                prefix: '私は、',
                suffix: '持っています',
            },
            source: "http://jp.example.org/page1",
        }
    },

    'Fragment identifier without URI': {
        uri: '#selector(type=TextPositionSelector,start=12,end=795)',
        obj: {
            "selector": {
                "type": "TextPositionSelector",
                "start": 12,
                "end": 795
            },
            "source": ""
        }
    },

    'URI without fragment identifier': {
        uri: 'https://example.org',
        obj: {
            "source": "https://example.org"
        }
    },
}

const specialCasesToStringify = {
    'Value with parentheses (to be percent-encoded)': {
        uri: '#selector(type=TextQuoteSelector,exact=example%20%28with%20parentheses%29)',
        obj: {
            selector: {
                type: 'TextQuoteSelector',
                exact: 'example (with parentheses)',
            },
        },
    },
}

const specialCasesToParse = {
    'One closing parenthesis inside a value': {
        uri: '#selector(type=TextQuoteSelector,exact=(not)%20a%20problem)',
        obj: {
            source: '',
            selector: {
                type: 'TextQuoteSelector',
                exact: '(not) a problem',
            },
        },
    },

    'Other valid special characters inside a value': {
        uri: `#selector(type=TextQuoteSelector,exact=these%20/:+@.\-!$&;*_~';?(%20are%20valid.)`,
        obj: {
            source: '',
            selector: {
                type: 'TextQuoteSelector',
                exact: `these /:+@.\-!$&;*_~';?( are valid.`,
            },
        },
    }
}

const specialCasesToParseThatFail = {
    'Two closing parentheses inside a value': {
        uri: '#selector(type=TextQuoteSelector,exact=Hey))%20this%20breaks)',
        obj: {
            source: '',
            selector: {
                type: 'TextQuoteSelector',
                exact: 'Hey)) this breaks',
            },
        },
    },

    'Two closing parentheses: one of value, one of selector': {
        uri: '#selector(type=TextQuoteSelector,exact=example%20(that%20fails))',
        obj: {
            source: '',
            selector: {
                type: 'TextQuoteSelector',
                exact: 'example (that fails)',
            },
        },
    },

    'Three closing parentheses: one of the value, two of nested selectors': {
        uri: `
            #selector(
                type=RangeSelector,
                startSelector=selector(type=TextQuoteSelector,exact=(but),
                endSelector=selector(type=TextQuoteSelector,exact=crazy))
            )
            `.replace(/\s/g, ''),
        obj: {
            source: '',
            selector: {
                type: 'RangeSelector',
                startSelector: {
                    type: 'TextQuoteSelector',
                    exact: '(but',
                },
                endSelector: {
                    type: 'TextQuoteSelector',
                    exact: 'crazy)',
                },
            },
        },
    },
}

describe('specificResourceToUri', () => {
    const examples = { ...pairs, ...specialCasesToStringify }
    for (const [name, example] of Object.entries(examples)) {
        it(`should properly convert: '${name}'`, () => {
            const uri = specificResourceToUri(example.obj)
            assert.equal(uri, example.uri)
        })
    }
})

describe('uriToSpecificResource', () => {
    for (const [name, example] of Object.entries(pairs)) {
        it(`should properly convert: '${name}'`, () => {
            const obj = uriToSpecificResource(example.uri)
            assert.deepEqual(obj, example.obj)
        })
    }

    it('should cleanly handle other types of fragment identifiers', () => {
        const obj = uriToSpecificResource('https://example.com/page#section4')
        const expected = {
            source: 'https://example.com/page',
            selector: {
                type: 'FragmentSelector',
                value: 'section4',
            }
        }
        assert.deepEqual(obj, expected)
    })

    for (const [name, example] of Object.entries(specialCasesToParse)) {
        it(`should parse special case: '${name}'`, () => {
            const obj = uriToSpecificResource(example.uri)
            assert.deepEqual(obj, example.obj)
        })
    }

    // FIXME These tests are known to fail. Seems impossible to parse
    // them with PEG.js. See <https://github.com/w3c/web-annotation/issues/443>
    for (const [name, example] of Object.entries(specialCasesToParseThatFail)) {
        it.skip(`should parse special case: '${name}'`, () => {
            const obj = uriToSpecificResource(example.uri)
            assert.deepEqual(obj, example.obj)
        })
    }
})
<|endoftext|>


<commit_before>'use strict'

/* eslint-disable no-multi-spaces */
import Vue  from 'vue'
import Vuex from 'vuex'

import api  from '@/api'
import auth from '@/auth/AuthService'

import mutations from './mutation-types'
import actions   from './action-types'
/* eslint-enable no-multi-spaces */

Vue.use(Vuex)

/* eslint-disable no-console */
const logError = function (error) {
  if (error.response) {
    // The request was made and the server responded with a status code
    // that falls out of the range of 2xx
    console.error(error.response.data)
    console.error(error.response.status)
    console.error(error.response.headers)
  } else if (error.request) {
    // The request was made but no response was received
    // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
    // http.ClientRequest in node.js
    console.error(error.request)
  } else {
    // Something happened in setting up the request that triggered an Error
    console.error('Error', error.message)
  }
  console.error(`config: ${error.config}`)
}

/**
 * Set the "Bearer" authorization header with the current access token
 */
const setBearer = async function () {
  try {
    await auth.getAccessToken()
    api.setBearer(auth.session.id.token)
  } catch (err) {
    if (err === 'Not logged in') {
      console.warn('API request attempted when user was not logged in')
    } else {
      console.error(err)
    }
  }
}
/* eslint-enable no-console */

/**
 * Sort journal requests either by asOf or showAfter
 */
const journalSort = function (a, b) {
  const sortValue = x => x.showAfter === 0 ? x.asOf : x.showAfter
  return sortValue(a) - sortValue(b)
}

export default new Vuex.Store({
  state: {
    user: auth.session.profile,
    isAuthenticated: auth.isAuthenticated(),
    journal: {},
    isLoadingJournal: false
  },
  mutations: {
    [mutations.LOADING_JOURNAL] (state, flag) {
      state.isLoadingJournal = flag
    },
    [mutations.LOADED_JOURNAL] (state, journal) {
      state.journal = journal.sort(journalSort)
    },
    [mutations.REQUEST_ADDED] (state, newRequest) {
      state.journal.push(newRequest)
    },
    [mutations.REQUEST_UPDATED] (state, request) {
      let jrnl = state.journal.filter(it => it.requestId !== request.requestId)
      if (request.lastStatus !== 'Answered') jrnl.push(request)
      state.journal = jrnl
    },
    [mutations.SET_AUTHENTICATION] (state, value) {
      state.isAuthenticated = value
    },
    [mutations.USER_LOGGED_OFF] (state) {
      state.user = {}
      api.removeBearer()
      state.isAuthenticated = false
    },
    [mutations.USER_LOGGED_ON] (state, user) {
      state.user = user
      state.isAuthenticated = true
    }
  },
  actions: {
    async [actions.ADD_REQUEST] ({ commit }, { progress, requestText, recurType, recurCount }) {
      progress.$emit('show', 'indeterminate')
      try {
        await setBearer()
        const newRequest = await api.addRequest(requestText, recurType, recurCount)
        commit(mutations.REQUEST_ADDED, newRequest.data)
        progress.$emit('done')
      } catch (err) {
        logError(err)
        progress.$emit('done')
      }
    },
    async [actions.CHECK_AUTHENTICATION] ({ commit }) {
      try {
        await auth.getAccessToken()
        commit(mutations.SET_AUTHENTICATION, auth.isAuthenticated())
      } catch (_) {
        commit(mutations.SET_AUTHENTICATION, false)
      }
    },
    async [actions.LOAD_JOURNAL] ({ commit }, progress) {
      commit(mutations.LOADED_JOURNAL, {})
      progress.$emit('show', 'query')
      commit(mutations.LOADING_JOURNAL, true)
      await setBearer()
      try {
        const jrnl = await api.journal()
        commit(mutations.LOADED_JOURNAL, jrnl.data)
        progress.$emit('done')
      } catch (err) {
        logError(err)
        progress.$emit('done')
      } finally {
        commit(mutations.LOADING_JOURNAL, false)
      }
    },
    async [actions.UPDATE_REQUEST] ({ commit, state }, { progress, requestId, status, updateText, recurType, recurCount }) {
      progress.$emit('show', 'indeterminate')
      try {
        await setBearer()
        let oldReq = (state.journal.filter(req => req.requestId === requestId) || [])[0] || {}
        if (!(status === 'Prayed' && updateText === '')) {
          if (status !== 'Answered' && (oldReq.recurType !== recurType || oldReq.recurCount !== recurCount)) {
            await api.updateRecurrence(requestId, recurType, recurCount)
          }
        }
        if (status !== 'Updated' || oldReq.text !== updateText) {
          await api.updateRequest(requestId, status, oldReq.text !== updateText ? updateText : '')
        }
        const request = await api.getRequest(requestId)
        commit(mutations.REQUEST_UPDATED, request.data)
        progress.$emit('done')
      } catch (err) {
        logError(err)
        progress.$emit('done')
      }
    },
    async [actions.SHOW_REQUEST_NOW] ({ commit }, { progress, requestId, showAfter }) {
      progress.$emit('show', 'indeterminate')
      try {
        await setBearer()
        await api.showRequest(requestId, showAfter)
        const request = await api.getRequest(requestId)
        commit(mutations.REQUEST_UPDATED, request.data)
        progress.$emit('done')
      } catch (err) {
        logError(err)
        progress.$emit('done')
      }
    },
    async [actions.SNOOZE_REQUEST] ({ commit }, { progress, requestId, until }) {
      progress.$emit('show', 'indeterminate')
      try {
        await setBearer()
        await api.snoozeRequest(requestId, until)
        const request = await api.getRequest(requestId)
        commit(mutations.REQUEST_UPDATED, request.data)
        progress.$emit('done')
      } catch (err) {
        logError(err)
        progress.$emit('done')
      }
    }
  },
  getters: {},
  modules: {}
})
<commit_msg>Fix journal ordering fix (#28 again)<commit_after>'use strict'

/* eslint-disable no-multi-spaces */
import Vue  from 'vue'
import Vuex from 'vuex'

import api  from '@/api'
import auth from '@/auth/AuthService'

import mutations from './mutation-types'
import actions   from './action-types'
/* eslint-enable no-multi-spaces */

Vue.use(Vuex)

/* eslint-disable no-console */
const logError = function (error) {
  if (error.response) {
    // The request was made and the server responded with a status code
    // that falls out of the range of 2xx
    console.error(error.response.data)
    console.error(error.response.status)
    console.error(error.response.headers)
  } else if (error.request) {
    // The request was made but no response was received
    // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
    // http.ClientRequest in node.js
    console.error(error.request)
  } else {
    // Something happened in setting up the request that triggered an Error
    console.error('Error', error.message)
  }
  console.error(`config: ${error.config}`)
}

/**
 * Set the "Bearer" authorization header with the current access token
 */
const setBearer = async function () {
  try {
    await auth.getAccessToken()
    api.setBearer(auth.session.id.token)
  } catch (err) {
    if (err === 'Not logged in') {
      console.warn('API request attempted when user was not logged in')
    } else {
      console.error(err)
    }
  }
}
/* eslint-enable no-console */

/**
 * Get the sort value for a prayer request
 * @param x The prayer request
 */
const sortValue = x => x.showAfter === 0 ? x.asOf : x.showAfter

/**
 * Sort journal requests either by asOf or showAfter
 */
const journalSort = (a, b) => sortValue(a) - sortValue(b)

export default new Vuex.Store({
  state: {
    user: auth.session.profile,
    isAuthenticated: auth.isAuthenticated(),
    journal: [],
    isLoadingJournal: false
  },
  mutations: {
    [mutations.LOADING_JOURNAL] (state, flag) {
      state.isLoadingJournal = flag
    },
    [mutations.LOADED_JOURNAL] (state, journal) {
      state.journal = journal.sort(journalSort)
    },
    [mutations.REQUEST_ADDED] (state, newRequest) {
      state.journal.push(newRequest)
    },
    [mutations.REQUEST_UPDATED] (state, request) {
      let jrnl = state.journal.filter(it => it.requestId !== request.requestId)
      if (request.lastStatus !== 'Answered') jrnl.push(request)
      state.journal = jrnl
    },
    [mutations.SET_AUTHENTICATION] (state, value) {
      state.isAuthenticated = value
    },
    [mutations.USER_LOGGED_OFF] (state) {
      state.user = {}
      api.removeBearer()
      state.isAuthenticated = false
    },
    [mutations.USER_LOGGED_ON] (state, user) {
      state.user = user
      state.isAuthenticated = true
    }
  },
  actions: {
    async [actions.ADD_REQUEST] ({ commit }, { progress, requestText, recurType, recurCount }) {
      progress.$emit('show', 'indeterminate')
      try {
        await setBearer()
        const newRequest = await api.addRequest(requestText, recurType, recurCount)
        commit(mutations.REQUEST_ADDED, newRequest.data)
        progress.$emit('done')
      } catch (err) {
        logError(err)
        progress.$emit('done')
      }
    },
    async [actions.CHECK_AUTHENTICATION] ({ commit }) {
      try {
        await auth.getAccessToken()
        commit(mutations.SET_AUTHENTICATION, auth.isAuthenticated())
      } catch (_) {
        commit(mutations.SET_AUTHENTICATION, false)
      }
    },
    async [actions.LOAD_JOURNAL] ({ commit }, progress) {
      commit(mutations.LOADED_JOURNAL, [])
      progress.$emit('show', 'query')
      commit(mutations.LOADING_JOURNAL, true)
      await setBearer()
      try {
        const jrnl = await api.journal()
        commit(mutations.LOADED_JOURNAL, jrnl.data)
        progress.$emit('done')
      } catch (err) {
        logError(err)
        progress.$emit('done')
      } finally {
        commit(mutations.LOADING_JOURNAL, false)
      }
    },
    async [actions.UPDATE_REQUEST] ({ commit, state }, { progress, requestId, status, updateText, recurType, recurCount }) {
      progress.$emit('show', 'indeterminate')
      try {
        await setBearer()
        let oldReq = (state.journal.filter(req => req.requestId === requestId) || [])[0] || {}
        if (!(status === 'Prayed' && updateText === '')) {
          if (status !== 'Answered' && (oldReq.recurType !== recurType || oldReq.recurCount !== recurCount)) {
            await api.updateRecurrence(requestId, recurType, recurCount)
          }
        }
        if (status !== 'Updated' || oldReq.text !== updateText) {
          await api.updateRequest(requestId, status, oldReq.text !== updateText ? updateText : '')
        }
        const request = await api.getRequest(requestId)
        commit(mutations.REQUEST_UPDATED, request.data)
        progress.$emit('done')
      } catch (err) {
        logError(err)
        progress.$emit('done')
      }
    },
    async [actions.SHOW_REQUEST_NOW] ({ commit }, { progress, requestId, showAfter }) {
      progress.$emit('show', 'indeterminate')
      try {
        await setBearer()
        await api.showRequest(requestId, showAfter)
        const request = await api.getRequest(requestId)
        commit(mutations.REQUEST_UPDATED, request.data)
        progress.$emit('done')
      } catch (err) {
        logError(err)
        progress.$emit('done')
      }
    },
    async [actions.SNOOZE_REQUEST] ({ commit }, { progress, requestId, until }) {
      progress.$emit('show', 'indeterminate')
      try {
        await setBearer()
        await api.snoozeRequest(requestId, until)
        const request = await api.getRequest(requestId)
        commit(mutations.REQUEST_UPDATED, request.data)
        progress.$emit('done')
      } catch (err) {
        logError(err)
        progress.$emit('done')
      }
    }
  },
  getters: {},
  modules: {}
})
<|endoftext|>


<commit_before>var assert = require('assert'),
    _ = require('lodash'),
    fs = require('fs');

if (!fs.existsSync(__dirname +'/node_modules')) {
  fs.mkdirSync(__dirname + '/node_modules');
  fs.symlinkSync(__dirname + '/..', __dirname +'/node_modules/apostrophe', 'dir');
}

require('./bootstraping.js');

// ------------------------------------------------------------------- //
// MODULES  ---------------------------------------------------------- //

describe('Modules', function(){

  // Clear out uploads after tests wrap up
  after(function() {
    deleteFolderRecursive(__dirname + '/public/uploads');

    function deleteFolderRecursive (path) {
      var files = [];
      if( fs.existsSync(path) ) {
        files = fs.readdirSync(path);
        files.forEach(function(file,index){
          var curPath = path + "/" + file;
          if(fs.lstatSync(curPath).isDirectory()) { // recurse
            deleteFolderRecursive(curPath);
          } else { // delete file
            fs.unlinkSync(curPath);
          }
        });
        fs.rmdirSync(path);
      }
    };
  });

  //                   //
  //    BASE MODULE    //
  //                   //

  require('./base-module.js');

  //            //
  //    UTIL    //
  //            //

  require('./utils.js');

  //            //
  //     DB     //
  //            //

  require('./db.js');

  //             //
  //   CACHES    //
  //             //

  require('./caches.js');

  //               //
  //    EXPRESS    //
  //               //

  require('./express.js');

  //             //
  //             //
  //  TEMPLATES  //
  //             //
  //             //

  require('./templates.js');

  //             //
  //  LAUNDER    //
  //             //

  require('./launder.js');

  //             //
  // PERMISSIONS //
  //             //

  require('./permissions.js');

  //             //
  //    FILES    //
  //             //

  require('./files.js');

});
<commit_msg>remove file reset stuff from root test file<commit_after>var assert = require('assert'),
    _ = require('lodash'),
    fs = require('fs');

if (!fs.existsSync(__dirname +'/node_modules')) {
  fs.mkdirSync(__dirname + '/node_modules');
  fs.symlinkSync(__dirname + '/..', __dirname +'/node_modules/apostrophe', 'dir');
}

require('./bootstraping.js');

// ------------------------------------------------------------------- //
// MODULES  ---------------------------------------------------------- //

describe('Modules', function(){

  //                   //
  //    BASE MODULE    //
  //                   //

  require('./base-module.js');

  //            //
  //    UTIL    //
  //            //

  require('./utils.js');

  //            //
  //     DB     //
  //            //

  require('./db.js');

  //             //
  //   CACHES    //
  //             //

  require('./caches.js');

  //               //
  //    EXPRESS    //
  //               //

  require('./express.js');

  //             //
  //             //
  //  TEMPLATES  //
  //             //
  //             //

  require('./templates.js');

  //             //
  //  LAUNDER    //
  //             //

  require('./launder.js');

  //             //
  // PERMISSIONS //
  //             //

  require('./permissions.js');

  //             //
  //    FILES    //
  //             //

  require('./files.js');

});
<|endoftext|>


<commit_before>'use strict';

var FnObj = require('./fn-obj');
var executeGroups = require('./execute-groups');

function createHarness() {
  var groups = [];

  function createTest() {
    var tests = [];
    var beforeEachFns = [];
    var afterEachFns = [];

    function test(/* name_, _opts, _cb */) {
      var t = FnObj.apply(null, arguments);
      t.isTest = true;
      tests.push(t);
    }

    test.beforeEach = function beforeEach(/* name_, _opts, _cb */) {
      var t = FnObj.apply(null, arguments);
      beforeEachFns.push(t);
    };

    test.afterEach = function afterEach(/* name_, _opts, _cb */) {
      var t = FnObj.apply(null, arguments);
      afterEachFns.push(t);
    };

    groups.push({
      beforeEach: beforeEachFns,
      afterEach: afterEachFns,
      tests: tests
    });

    return test;
  }

  function run() {
    return executeGroups(groups);
  }

  return {
    createTest,
    run
  };
}

module.exports = createHarness;
<commit_msg>Removed object shorthand<commit_after>'use strict';

var FnObj = require('./fn-obj');
var executeGroups = require('./execute-groups');

function createHarness() {
  var groups = [];

  function createTest() {
    var tests = [];
    var beforeEachFns = [];
    var afterEachFns = [];

    function test(/* name_, _opts, _cb */) {
      var t = FnObj.apply(null, arguments);
      t.isTest = true;
      tests.push(t);
    }

    test.beforeEach = function beforeEach(/* name_, _opts, _cb */) {
      var t = FnObj.apply(null, arguments);
      beforeEachFns.push(t);
    };

    test.afterEach = function afterEach(/* name_, _opts, _cb */) {
      var t = FnObj.apply(null, arguments);
      afterEachFns.push(t);
    };

    groups.push({
      beforeEach: beforeEachFns,
      afterEach: afterEachFns,
      tests: tests
    });

    return test;
  }

  function run() {
    return executeGroups(groups);
  }

  return {
    createTest: createTest,
    run: run
  };
}

module.exports = createHarness;
<|endoftext|>


<commit_before>var xml = require('xml');

/**
 * Convert a step from Cucumber.JS into <testcase> XML
 *
 * @method convertStep
 * @param  {Object}    stepJson     Step output from Cucumber.JS
 * @param  {Object}    scenarioJson Scenario output from Cucumber.JS
 * @return {Array}                  Array of elements for an XML element <testcase>
 */
function convertStep (stepJson, scenarioJson) {
    var stepOutput = [{
            _attr: {
                name: stepJson.keyword + stepJson.name,
                classname: scenarioJson.id
            }
        }];

    if (stepJson.result.duration) {
        // Convert from nanosecond to seconds
        stepOutput[0]._attr.time = stepJson.result.duration / 1000000000;
    }
    switch (stepJson.result.status) {
        case 'passed':
            break;
        case 'failed':
            stepOutput.push({
                error: [
                    {
                        _attr: {
                            message: stepJson.result.error_message.split("\n").shift()
                        }
                    }, stepJson.result.error_message
                ]
            });
            break;
        case 'skipped':
        case 'undefined':
            stepOutput.push({
                skipped: [
                    {
                        _attr: {
                            message: ""
                        }
                    }
                ]
            });
            break;
    }
    return stepOutput;
}

/**
 * Convert a scenario from Cucumber.JS into an XML element <testsuite>
 *
 * @method convertScenario
 * @param  {Object}    scenarioJson Scenario output from Cucumber.JS
 * @return {Array}                  Array of elements for an XML element <testsuite>
 */
function convertScenario (scenarioJson) {
    var scenarioOutput = [{
            _attr: {
                name: scenarioJson.id,
                tests: scenarioJson.steps.length,
                failures: 0,
                skipped: 0
            }
        }];

    scenarioJson.steps.forEach(function (stepJson) {
        var testcase = convertStep(stepJson, scenarioJson);
        // Check for errors and increment the failure rate
        if (testcase[1] && testcase[1].error) {
            scenarioOutput[0]._attr.failures += 1;
        }
        if (testcase[1] && testcase[1].skipped) {
            scenarioOutput[0]._attr.skipped += 1;
        }
        scenarioOutput.push({ testcase: testcase });
    });

    return { testsuite: scenarioOutput };
}

/**
 * [convertFeature description]
 * @method convertFeature
 * @param  {[type]}       featureJson [description]
 * @return {[type]}                   [description]
 */
function convertFeature(featureJson) {
    return featureJson.elements.map(function (scenarioJson) {
        return convertScenario(scenarioJson);
    });
}

/**
 * [exports description]
 * @method exports
 * @param  {[type]} cucumberRaw [description]
 * @return {[type]}             [description]
 */
function cucumberJunit (cucumberRaw) {
    var cucumberJson,
        output = [];

    if (cucumberRaw && cucumberRaw.toString().trim() !== '') {
        cucumberJson = JSON.parse(cucumberRaw);
        cucumberJson.forEach(function (featureJson) {
            output = output.concat(convertFeature(featureJson));
        });

        // If no items, provide something
        if (output.length === 0) {
            output.push( { testsuite: [] } );
        }
    }

    // wrap all <testsuite> elements in <testsuites> element
    return xml({ testsuites: output }, { indent: '    ' });
};

module.exports = cucumberJunit;
<commit_msg>fixed bug occurring when there aren't steps in a scenario<commit_after>var xml = require('xml');

/**
 * Convert a step from Cucumber.JS into <testcase> XML
 *
 * @method convertStep
 * @param  {Object}    stepJson     Step output from Cucumber.JS
 * @param  {Object}    scenarioJson Scenario output from Cucumber.JS
 * @return {Array}                  Array of elements for an XML element <testcase>
 */
function convertStep (stepJson, scenarioJson) {
    var stepOutput = [{
            _attr: {
                name: stepJson.keyword + stepJson.name,
                classname: scenarioJson.id
            }
        }];

    if (stepJson.result.duration) {
        // Convert from nanosecond to seconds
        stepOutput[0]._attr.time = stepJson.result.duration / 1000000000;
    }
    switch (stepJson.result.status) {
        case 'passed':
            break;
        case 'failed':
            stepOutput.push({
                error: [
                    {
                        _attr: {
                            message: stepJson.result.error_message.split("\n").shift()
                        }
                    }, stepJson.result.error_message
                ]
            });
            break;
        case 'skipped':
        case 'undefined':
            stepOutput.push({
                skipped: [
                    {
                        _attr: {
                            message: ""
                        }
                    }
                ]
            });
            break;
    }
    return stepOutput;
}

/**
 * Convert a scenario from Cucumber.JS into an XML element <testsuite>
 *
 * @method convertScenario
 * @param  {Object}    scenarioJson Scenario output from Cucumber.JS
 * @return {Array}                  Array of elements for an XML element <testsuite>
 */
function convertScenario (scenarioJson) {
    var scenarioOutput = [{
            _attr: {
                name: scenarioJson.id,
                tests: (scenarioJson.steps) ? scenarioJson.steps.length : 0,
                failures: 0,
                skipped: 0
            }
        }];
    if(scenarioJson.steps) {
        scenarioJson.steps.forEach(function (stepJson) {
            var testcase = convertStep(stepJson, scenarioJson);
            // Check for errors and increment the failure rate
            if (testcase[1] && testcase[1].error) {
                scenarioOutput[0]._attr.failures += 1;
            }
            if (testcase[1] && testcase[1].skipped) {
                scenarioOutput[0]._attr.skipped += 1;
            }
            scenarioOutput.push({ testcase: testcase });
        });
    }

    return { testsuite: scenarioOutput };
}

/**
 * [convertFeature description]
 * @method convertFeature
 * @param  {[type]}       featureJson [description]
 * @return {[type]}                   [description]
 */
function convertFeature(featureJson) {
    return featureJson.elements.map(function (scenarioJson) {
        return convertScenario(scenarioJson);
    });
}

/**
 * [exports description]
 * @method exports
 * @param  {[type]} cucumberRaw [description]
 * @return {[type]}             [description]
 */
function cucumberJunit (cucumberRaw) {
    var cucumberJson,
        output = [];

    if (cucumberRaw && cucumberRaw.toString().trim() !== '') {
        cucumberJson = JSON.parse(cucumberRaw);
        cucumberJson.forEach(function (featureJson) {
            output = output.concat(convertFeature(featureJson));
        });

        // If no items, provide something
        if (output.length === 0) {
            output.push( { testsuite: [] } );
        }
    }

    // wrap all <testsuite> elements in <testsuites> element
    return xml({ testsuites: output }, { indent: '    ' });
};

module.exports = cucumberJunit;
<|endoftext|>


<commit_before>/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/*!
 * Next JS
 * Copyright (c)2011 Xenophy.CO.,LTD All rights Reserved.
 * http://www.xenophy.com
 */

// {{{ NX.view.Web

NX.define('NX.view.Web', {

    // {{{ alternateClassName

    alternateClassName: 'NX.WebView',

    // }}}
    // {{{ extend

    extend: 'NX.view.AbstractView',

    // }}}
    // {{{ mimetype

    mimetype: {
        dwg : 'application/acad',
        arj : 'application/arj',
        asd : 'application/astound',
        asn : 'application/astound',
        css : 'text/css',
        ccad : 'application/clariscad',
        drw : 'application/drafting',
        dxf : 'application/dxf',
        ico : 'image/vnd.microsoft.icon',
        unv : 'application/i-deas',
        iges : 'application/iges',
        iges : 'application/iges',
        jar : 'application/java-archive',
        hqx : 'application/mac-binhex40',
        mdb : 'application/msaccess',
        xla : 'application/msexcel',
        xls : 'application/msexcel',
        xlt : 'application/msexcel',
        xlw : 'application/msexcel',
        pot : 'application/mspowerpoint',
        pps : 'application/mspowerpoint',
        ppt : 'application/mspowerpoint',
        mpp : 'application/msproject',
        doc : 'application/msword',
        word : 'application/msword',
        w6w : 'application/msword',
        wri : 'application/mswrite',
        bin : 'application/octet-stream',
        oda : 'application/oda',
        pdf : 'application/pdf',
        ai : 'application/postscript',
        eps : 'application/postscript',
        ps : 'application/postscript',
        part : 'application/pro_eng',
        part : 'application/pro_eng',
        rtf : 'application/rtf',
        set : 'application/set',
        stl : 'application/sla',
        sol : 'application/solids',
        st : 'application/STEP',
        step : 'application/STEP',
        stp : 'application/STEP',
        vda : 'application/vda',
        bcpio : 'application/x-bcpio',
        cpio : 'application/x-cpio',
        csh : 'application/x-csh',
        dcr : 'application/x-director',
        dir : 'application/x-director',
        dxr : 'application/x-director',
        dvi : 'application/x-dvi',
        dwf : 'application/x-dwf',
        gtar : 'application/x-gtar',
        gz : 'application/x-gzip',
        gzip : 'application/x-gzip',
        hdf : 'application/x-hdf',
        js : 'application/x-javascript',
        latex : 'application/x-latex',
        bin : 'application/x-macbinary',
        mid : 'application/x-midi',
        mif : 'application/x-mif',
        cdf : 'application/x-netcdf',
        nc : 'application/x-netcdf',
        sh : 'application/x-sh',
        shar : 'application/x-shar',
        swf : 'application/x-shockwave-flash',
        sit : 'application/x-stuffit',
        sv4cpio : 'application/x-sv4cpio',
        sv4crc : 'application/x-sv4crc',
        tar : 'application/x-tar',
        tcl : 'application/x-tcl',
        tex : 'application/x-tex',
        texi : 'application/x-texinfo',
        texinfo : 'application/x-texinfo',
        roff : 'application/x-troff',
        t : 'application/x-troff',
        tr : 'application/x-troff',
        man : 'application/x-troff-man',
        me : 'application/x-troff-me',
        ms : 'application/x-troff-ms',
        ustar : 'application/x-ustar',
        src : 'application/x-wais-source',
        hlp : 'application/x-winhelp',
        zip : 'application/zip',
        au : 'audio/basic',
        snd : 'audio/basic',
        mid : 'audio/midi',
        midi : 'audio/midi',
        aif : 'audio/x-aiff',
        aifc : 'audio/x-aiff',
        aiff : 'audio/x-aiff',
        mp3 : 'audio/x-mpeg',
        ra : 'audio/x-pn-realaudio',
        ram : 'audio/x-pn-realaudio',
        rpm : 'audio/x-pn-realaudio-plugin',
        voc : 'audio/x-voice',
        wav : 'audio/x-wav',
        bmp : 'image/bmp',
        gif : 'image/gif',
        ief : 'image/ief',
        jpe : 'image/jpeg',
        jpeg : 'image/jpeg',
        jpg : 'image/jpeg',
        pict : 'image/pict',
        png : 'image/png',
        tif : 'image/tiff',
        tiff : 'image/tiff',
        ras : 'image/x-cmu-raster',
        pnm : 'image/x-portable-anymap',
        pbm : 'image/x-portable-bitmap',
        pgm : 'image/x-portable-graymap',
        ppm : 'image/x-portable-pixmap',
        rgb : 'image/x-rgb',
        xbm : 'image/x-xbitmap',
        xpm : 'image/x-xpixmap',
        xwd : 'image/x-xwindowdump',
        gzip : 'multipart/x-gzip',
        zip : 'multipart/x-zip',
        htm : 'text/html',
        html : 'text/html',
        C : 'text/plain',
        cc : 'text/plain',
        h : 'text/plain',
        txt : 'text/plain',
        rtx : 'text/richtext',
        tsv : 'text/tab-separated-values',
        etx : 'text/x-setext',
        sgm : 'text/x-sgml',
        sgml : 'text/x-sgml',
        mpe : 'video/mpeg',
        mpeg : 'video/mpeg',
        mpg : 'video/mpeg',
        avi : 'video/msvideo',
        mov : 'video/quicktime',
        qt : 'video/quicktime',
        vdo : 'video/vdo',
        viv : 'video/vivo',
        vivo : 'video/vivo',
        movie : 'video/x-sgi-movie',
        ice : 'x-conference/x-cooltalk',
        svr : 'x-world/x-svr',
        wrl : 'x-world/x-vrml',
        vrt : 'x-world/x-vrt',
        rdf : 'text/xml',
        xml : 'text/xml',
        json : 'text/plane',
        docm : 'application/vnd.ms-word.document.macroEnabled.12',
        docx : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        dotm : 'application/vnd.ms-word.template.macroEnabled.12',
        dotx : 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
        potm : 'application/vnd.ms-powerpoint.template.macroEnabled.12',
        potx : 'application/vnd.openxmlformats-officedocument.presentationml.template',
        ppam : 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
        ppsm : 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
        ppsx : 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
        pptm : 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
        pptx : 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        xlam : 'application/vnd.ms-excel.addin.macroEnabled.12',
        xlsb : 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
        xlsm : 'application/vnd.ms-excel.sheet.macroEnabled.12',
        xlsx : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        xltm : 'application/vnd.ms-excel.template.macroEnabled.12',
        xltx : 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
    },

    // }}}
    // {{{ errorDocument

    errorDocument: {
        401: process.NXEnv.libdir + '/resources/error/HTTP_UNAUTHORIZED.html',
        403: process.NXEnv.libdir + '/resources/error/HTTP_FORBIDDEN.html',
        404: process.NXEnv.libdir + '/resources/error/HTTP_NOT_FOUND.html',
        500: process.NXEnv.libdir + '/resources/error/HTTP_INTERNAL_SERVER_ERROR.html'
    },

    // }}}
    // {{{ tplExts

    tplExts: [
        'html',
        'htm'
    ],

    // }}}
    // {{{ constructor

    constructor: function(paths, req, res) {

        var querystring = require('querystring');
        var url = NX.Url.parse(req.url);

        // テンプレートファイル初期化
        paths.template = NX.Path.join(paths.contents, querystring.unescape(url.pathname));
        paths.favicon = process.NXEnv.libdir + '/resources/favicon.ico';

        if(paths.template[paths.template.length - 1] === '/') {
            paths.template += "index.html";
        }

        NX.apply(this, {
            res: res,
            req: res,
            head: (req.method == 'HEAD'),
            url: url,
            favicon: (querystring.unescape(url.pathname) === '/favicon.ico'),
            paths: paths
        })

    },

    // }}}
    // {{{ loadFile

    loadFile: function(file, callback) {

        (function(file, callback) {

            callback = callback || NX.emptyFn;

            // テンプレートファイル確認
            NX.Path.exists(tpl, function(exists) {

                if(!exists) {
                    callback({type: 'not found'});
                    return;
                }

            });

        })(file, callback);

    },

    // }}}
    // {{{ render

    render: function(info) {

        var me = this, res = me.res, req = me.req;

        // スーパークラスメソッドコール
        me.callParent(arguments);

        if(me.paths.template === false) {
            return;
        }

        me.loadFile(me.paths.template, function(err, data, stat) {

        });

        /*

        (function(tpl, req, res, favicon) {

            // テンプレートファイル確認
            NX.Path.exists(tpl, function(exists) {

                if(!exists) {

                    if(!favicon) {

                        // 404エラー
                        me.error(404);
                        return false;

                    }

                    // {{{ デフォルトfavicon転送処理

                    var file = me.paths.favicon;

                    NX.Path.exists(file, function(exists) {

                        if(exists) {

                            NX.Fs.stat(file, function(err, stat) {

                                if(err) {

                                    // 404エラー
                                    me.error(404);
                                    return false;

                                }

                                var status, maxAge = 0;
                                status = stat;

                                NX.Fs.readFile(file, function(err, data) {

                                    if(err) {

                                        // 404エラー
                                        me.error(404);
                                        return false;

                                    }

                                    var stat = status;
                                    var headers = {
                                        "Content-Type": me.mimetype.html,
                                        "Content-Length": stat.size,
                                        "Last-Modified": stat.mtime.toUTCString(),
                                        "Cache-Control": "public max-age=" + (maxAge / 1000),
                                        "ETag": me.etag(stat)
                                    };

                                    res.writeHead(200, headers);
                                    res.end(data);

                                });
                            });

                        } else {

                            // 404エラー
                            me.error(404);
                            return false;

                        }

                    });

                    // }}}

                } else {

                    NX.Fs.stat(tpl, function(err, stat) {

                        if(err) {

                            // 500エラー
                            me.error(500);
                            return false;

                        }

                        var status, maxAge = 0;
                        status = stat;

                        NX.Fs.readFile(tpl, function(err, data) {

                            if(err) {

                                // 404エラー
                                me.error(404);
                                return false;

                            }

                            // テンプレート拡張子の場合は、テンプレート処理

                            var headers = {
                                "Content-Type": me.mimetype[NX.Path.extname(me.paths.template).substr(1)] + '; charset=utf-8' ,
                                "Content-Length": data.length,
                                "Last-Modified": stat.mtime.toUTCString(),
                                "Cache-Control": "public max-age=" + (maxAge / 1000),
                                "ETag": me.etag(stat)
                            };

                            res.writeHead(200, headers);

                            if(!me.head) {
                                res.write(data)
                            }
                            res.end();

                        });

                    });

                }

            });

        })(me.paths.template, req, res, me.favicon);
        */

    },

    // }}}
    // {{{ error

    error : function(code) {

        var me = this, res = me.res, req = me.req,

        file = me.errorDocument[code];

        // ファイル存在確認
        NX.Path.exists(file, function(exists) {

            if(exists) {

                NX.Fs.stat(file, function(err, stat) {

                    if(err) {

                        res.writeHead(code);
                        res.end();

                    }

                    var status = stat;
                    var maxAge = 0;

                    NX.Fs.readFile(file, function(err, data) {

                        if(err) {

                            res.writeHead(code);
                            res.end();

                        }

                        var stat = status;
                        var headers = {
                            "Content-Type": me.mimetype.html,
                            "Content-Length": stat.size,
                            "Last-Modified": stat.mtime.toUTCString(),
                            "Cache-Control": "public max-age=" + (maxAge / 1000),
                            "ETag": me.etag(stat)
                        };

                        res.writeHead(code, headers);
                        res.end(data);

                    });
                });

            } else {

                res.writeHead(code);
                res.end();

            }

        });

    },

    // }}}
    // {{{ etag

    etag : function(stat) {
        return stat.size + '-' + Number(stat.mtime);
    }

    // }}}

});

// }}}

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * c-hanging-comment-ender-p: nil
 * End:
 */
<commit_msg>ファイル転送機能実装<commit_after>/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/*!
 * Next JS
 * Copyright (c)2011 Xenophy.CO.,LTD All rights Reserved.
 * http://www.xenophy.com
 */

// {{{ NX.view.Web

NX.define('NX.view.Web', {

    // {{{ alternateClassName

    alternateClassName: 'NX.WebView',

    // }}}
    // {{{ extend

    extend: 'NX.view.AbstractView',

    // }}}
    // {{{ statics

    statics: {

        // {{{ mimetype

        mimetype: {
            dwg : 'application/acad',
            arj : 'application/arj',
            asd : 'application/astound',
            asn : 'application/astound',
            css : 'text/css',
            ccad : 'application/clariscad',
            drw : 'application/drafting',
            dxf : 'application/dxf',
            ico : 'image/vnd.microsoft.icon',
            unv : 'application/i-deas',
            iges : 'application/iges',
            iges : 'application/iges',
            jar : 'application/java-archive',
            hqx : 'application/mac-binhex40',
            mdb : 'application/msaccess',
            xla : 'application/msexcel',
            xls : 'application/msexcel',
            xlt : 'application/msexcel',
            xlw : 'application/msexcel',
            pot : 'application/mspowerpoint',
            pps : 'application/mspowerpoint',
            ppt : 'application/mspowerpoint',
            mpp : 'application/msproject',
            doc : 'application/msword',
            word : 'application/msword',
            w6w : 'application/msword',
            wri : 'application/mswrite',
            bin : 'application/octet-stream',
            oda : 'application/oda',
            pdf : 'application/pdf',
            ai : 'application/postscript',
            eps : 'application/postscript',
            ps : 'application/postscript',
            part : 'application/pro_eng',
            part : 'application/pro_eng',
            rtf : 'application/rtf',
            set : 'application/set',
            stl : 'application/sla',
            sol : 'application/solids',
            st : 'application/STEP',
            step : 'application/STEP',
            stp : 'application/STEP',
            vda : 'application/vda',
            bcpio : 'application/x-bcpio',
            cpio : 'application/x-cpio',
            csh : 'application/x-csh',
            dcr : 'application/x-director',
            dir : 'application/x-director',
            dxr : 'application/x-director',
            dvi : 'application/x-dvi',
            dwf : 'application/x-dwf',
            gtar : 'application/x-gtar',
            gz : 'application/x-gzip',
            gzip : 'application/x-gzip',
            hdf : 'application/x-hdf',
            js : 'application/x-javascript',
            latex : 'application/x-latex',
            bin : 'application/x-macbinary',
            mid : 'application/x-midi',
            mif : 'application/x-mif',
            cdf : 'application/x-netcdf',
            nc : 'application/x-netcdf',
            sh : 'application/x-sh',
            shar : 'application/x-shar',
            swf : 'application/x-shockwave-flash',
            sit : 'application/x-stuffit',
            sv4cpio : 'application/x-sv4cpio',
            sv4crc : 'application/x-sv4crc',
            tar : 'application/x-tar',
            tcl : 'application/x-tcl',
            tex : 'application/x-tex',
            texi : 'application/x-texinfo',
            texinfo : 'application/x-texinfo',
            roff : 'application/x-troff',
            t : 'application/x-troff',
            tr : 'application/x-troff',
            man : 'application/x-troff-man',
            me : 'application/x-troff-me',
            ms : 'application/x-troff-ms',
            ustar : 'application/x-ustar',
            src : 'application/x-wais-source',
            hlp : 'application/x-winhelp',
            zip : 'application/zip',
            au : 'audio/basic',
            snd : 'audio/basic',
            mid : 'audio/midi',
            midi : 'audio/midi',
            aif : 'audio/x-aiff',
            aifc : 'audio/x-aiff',
            aiff : 'audio/x-aiff',
            mp3 : 'audio/x-mpeg',
            ra : 'audio/x-pn-realaudio',
            ram : 'audio/x-pn-realaudio',
            rpm : 'audio/x-pn-realaudio-plugin',
            voc : 'audio/x-voice',
            wav : 'audio/x-wav',
            bmp : 'image/bmp',
            gif : 'image/gif',
            ief : 'image/ief',
            jpe : 'image/jpeg',
            jpeg : 'image/jpeg',
            jpg : 'image/jpeg',
            pict : 'image/pict',
            png : 'image/png',
            tif : 'image/tiff',
            tiff : 'image/tiff',
            ras : 'image/x-cmu-raster',
            pnm : 'image/x-portable-anymap',
            pbm : 'image/x-portable-bitmap',
            pgm : 'image/x-portable-graymap',
            ppm : 'image/x-portable-pixmap',
            rgb : 'image/x-rgb',
            xbm : 'image/x-xbitmap',
            xpm : 'image/x-xpixmap',
            xwd : 'image/x-xwindowdump',
            gzip : 'multipart/x-gzip',
            zip : 'multipart/x-zip',
            htm : 'text/html',
            html : 'text/html',
            C : 'text/plain',
            cc : 'text/plain',
            h : 'text/plain',
            txt : 'text/plain',
            rtx : 'text/richtext',
            tsv : 'text/tab-separated-values',
            etx : 'text/x-setext',
            sgm : 'text/x-sgml',
            sgml : 'text/x-sgml',
            mpe : 'video/mpeg',
            mpeg : 'video/mpeg',
            mpg : 'video/mpeg',
            avi : 'video/msvideo',
            mov : 'video/quicktime',
            qt : 'video/quicktime',
            vdo : 'video/vdo',
            viv : 'video/vivo',
            vivo : 'video/vivo',
            movie : 'video/x-sgi-movie',
            ice : 'x-conference/x-cooltalk',
            svr : 'x-world/x-svr',
            wrl : 'x-world/x-vrml',
            vrt : 'x-world/x-vrt',
            rdf : 'text/xml',
            xml : 'text/xml',
            json : 'text/plane',
            docm : 'application/vnd.ms-word.document.macroEnabled.12',
            docx : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            dotm : 'application/vnd.ms-word.template.macroEnabled.12',
            dotx : 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
            potm : 'application/vnd.ms-powerpoint.template.macroEnabled.12',
            potx : 'application/vnd.openxmlformats-officedocument.presentationml.template',
            ppam : 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
            ppsm : 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
            ppsx : 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
            pptm : 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
            pptx : 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            xlam : 'application/vnd.ms-excel.addin.macroEnabled.12',
            xlsb : 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
            xlsm : 'application/vnd.ms-excel.sheet.macroEnabled.12',
            xlsx : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            xltm : 'application/vnd.ms-excel.template.macroEnabled.12',
            xltx : 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
        },

        // }}}
        // {{{ errorDocument

        errorDocument: {
            401: process.NXEnv.libdir + '/resources/error/HTTP_UNAUTHORIZED.html',
            403: process.NXEnv.libdir + '/resources/error/HTTP_FORBIDDEN.html',
            404: process.NXEnv.libdir + '/resources/error/HTTP_NOT_FOUND.html',
            500: process.NXEnv.libdir + '/resources/error/HTTP_INTERNAL_SERVER_ERROR.html'
        },

        // }}}
        // {{{ etag

        etag : function(stat) {
            return stat.size + '-' + Number(stat.mtime);
        }

        // }}}

    },

    // }}}
    // {{{ tplExts

    tplExts: [
        'html',
        'htm'
    ],

    // }}}
    // {{{ constructor

    constructor: function(paths, req, res) {

        var querystring = require('querystring');
        var url = NX.Url.parse(req.url);

        // テンプレートファイル初期化
        paths.template = NX.Path.join(paths.contents, querystring.unescape(url.pathname));
        paths.favicon = process.NXEnv.libdir + '/resources/favicon.ico';

        if(paths.template[paths.template.length - 1] === '/') {
            paths.template += "index.html";
        }

        NX.apply(this, {
            res: res,
            req: res,
            head: (req.method == 'HEAD'),
            url: url,
            favicon: (querystring.unescape(url.pathname) === '/favicon.ico'),
            paths: paths
        })

    },

    // }}}
    // {{{ loadFile

    loadFile: function(file, callback) {

        (function(file, callback) {

            callback = callback || NX.emptyFn;

            // テンプレートファイル確認
            NX.Path.exists(file, function(exists) {

                if(!exists) {
                    callback({type: 'not found'});
                    return;
                }

                NX.Fs.stat(file, function(err, stat) {

                    if(err) {
                        callback({type: 'not found'});
                        return;
                    }

                    NX.Fs.readFile(file, function(err, data) {

                        if(err) {
                            callback({type: 'not found'});
                            return;
                        }

                        var ext = NX.Path.extname(file).substr(1);

                        callback(null, data, stat, ext);

                    });

                });

            });

        })(file, callback);

    },

    // }}}
    // {{{ render

    render: function(info) {

        var me = this, res = me.res, req = me.req;

        // スーパークラスメソッドコール
        me.callParent(arguments);

        if(me.paths.template === false) {
            return;
        }

        me.loadFile(me.paths.template, function(err, data, stat, ext) {

            if(err) {

                switch(err.type) {
                    case 'not found':

                        // 404エラー
                        me.error(404);
                        return false;

                        break;
                }
            }

            var maxAge = 0;
            var headers = {
                "Content-Type": NX.WebView.mimetype[ext],
                "Content-Length": stat.size,
                "Last-Modified": stat.mtime.toUTCString(),
                "Cache-Control": "public max-age=" + (maxAge / 1000),
                "ETag": NX.WebView.etag(stat)
            };

            res.writeHead(200, headers);
            res.end(data);

        });
    },

    // }}}
    // {{{ error

    error : function(code) {

        var me = this, res = me.res, req = me.req,

        file = NX.WebView.errorDocument[code];

        me.loadFile(file, function(err, data, stat) {

            if(err) {

                switch(err.type) {
                    case 'not found':

                        res.writeHead(500);
                        res.write('Error 404 Default Template is not found.');
                        res.end();

                        break;
                }

                return;
            }

            var maxAge = 0;
            var headers = {
                "Content-Type": NX.WebView.mimetype.html,
                "Content-Length": stat.size,
                "Last-Modified": stat.mtime.toUTCString(),
                "Cache-Control": "public max-age=" + (maxAge / 1000),
                "ETag": NX.WebView.etag(stat)
            };

            res.writeHead(code, headers);
            res.end(data);

        });

    },

    // }}}

});

// }}}

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * c-hanging-comment-ender-p: nil
 * End:
 */
<|endoftext|>


<commit_before>module.exports = {
    src: [
       'bower_components/bootstrap/dist/css/bootstrap.css',
       'vendor/bower_components/open-sans/css/open-sans.css',
       'vendor/bower_components/jquery-minicolors/jquery.minicolors.css',
       'web/bundles/openorchestrabackoffice/smartadmin/css/bootstrap.css',
       'vendor/bower_components/font-awesome/css/font-awesome.css',
       'vendor/bower_components/datatables.net-bs/css/dataTables.bootstrap.css',
       'vendor/bower_components/datatables.net-buttons-bs/js/buttons.bootstrap.css'
    ],
    dest: 'web/built/lib.css'
};
<commit_msg>Remove multiple bootstrap loading<commit_after>module.exports = {
    src: [
       'vendor/bower_components/bootstrap/dist/css/bootstrap.css',
       'vendor/bower_components/open-sans/css/open-sans.css',
       'vendor/bower_components/jquery-minicolors/jquery.minicolors.css',
       'vendor/bower_components/font-awesome/css/font-awesome.css',
       'vendor/bower_components/datatables.net-bs/css/dataTables.bootstrap.css',
       'vendor/bower_components/datatables.net-buttons-bs/js/buttons.bootstrap.css'
    ],
    dest: 'web/built/lib.css'
};
<|endoftext|>


<commit_before>'use strict'
const TestRunner = require('test-runner')
const Lws = require('../')
const a = require('assert')
const request = require('req-then')

const runner = new TestRunner()

runner.test('stack: one feature', async function () {
  const port = 9000 + this.index
  class Feature {
    middleware (options) {
      return (ctx, next) => {
        ctx.body = 'one'
      }
    }
  }
  const lws = new Lws({
    stack: Feature,
    port: port
  })
  lws.start()
  const response = await request(`http://localhost:${port}`)
  a.strictEqual(response.data.toString(), 'one')
  lws.server.close()
})

runner.test('stack: one feature, one feature path', async function () {
  const port = 9000 + this.index
  class Feature {
    middleware (options) {
      return (ctx, next) => {
        ctx.body = 'one'
        next()
      }
    }
  }
  const lws = new Lws({
    stack: [ Feature, 'test/fixture/two.js' ],
    port: port
  })
  lws.start()
  const response = await request(`http://localhost:${port}`)
  a.strictEqual(response.data.toString(), 'onetwo')
  lws.server.close()
})

runner.test('stack: one feature, one cli feature path', async function () {
  const port = 9000 + this.index
  class Feature {
    middleware (options) {
      return (ctx, next) => {
        ctx.body = 'one'
        next()
      }
    }
  }
  const lws = new Lws({
    stack: [ Feature ],
    port: port
  })
  process.argv = [ 'node', 'example.js', '--stack', 'test/fixture/two.js' ]
  lws.start()
  process.argv = [ 'node', 'example.js' ]
  const response = await request(`http://localhost:${port}`)
  lws.server.close()
  a.strictEqual(response.data.toString(), 'onetwo')
})

runner.test('stack: Two features', async function () {
  const port = 9000 + this.index
  class Feature {
    middleware (options) {
      return (ctx, next) => {
        ctx.body = 'one'
        next()
      }
    }
  }
  class Feature2 {
    middleware (options) {
      return (ctx, next) => {
        ctx.body += 'two'
        next()
      }
    }
  }
  const lws = new Lws({
    stack: [ Feature, Feature2 ],
    port: port
  })
  lws.start()
  const response = await request(`http://localhost:${port}`)
  lws.server.close()
  a.strictEqual(response.data.toString(), 'onetwo')
})

runner.skip('--help', function () {
  const lws = new Lws()
  process.argv = [ 'node', 'script.js', '--help' ]
  // need to be able to pass in the output stream in order to test it
})
<commit_msg>https test<commit_after>'use strict'
const TestRunner = require('test-runner')
const Lws = require('../')
const a = require('assert')
const request = require('req-then')

const runner = new TestRunner()

runner.test('stack: one feature', async function () {
  const port = 9000 + this.index
  class Feature {
    middleware (options) {
      return (ctx, next) => {
        ctx.body = 'one'
      }
    }
  }
  const lws = new Lws({
    stack: Feature,
    port: port
  })
  lws.start()
  const response = await request(`http://localhost:${port}`)
  a.strictEqual(response.data.toString(), 'one')
  lws.server.close()
})

runner.test('stack: one feature, one feature path', async function () {
  const port = 9000 + this.index
  class Feature {
    middleware (options) {
      return (ctx, next) => {
        ctx.body = 'one'
        next()
      }
    }
  }
  const lws = new Lws({
    stack: [ Feature, 'test/fixture/two.js' ],
    port: port
  })
  lws.start()
  const response = await request(`http://localhost:${port}`)
  a.strictEqual(response.data.toString(), 'onetwo')
  lws.server.close()
})

runner.test('stack: one feature, one cli feature path', async function () {
  const port = 9000 + this.index
  class Feature {
    middleware (options) {
      return (ctx, next) => {
        ctx.body = 'one'
        next()
      }
    }
  }
  const lws = new Lws({
    stack: [ Feature ],
    port: port
  })
  process.argv = [ 'node', 'example.js', '--stack', 'test/fixture/two.js' ]
  lws.start()
  process.argv = [ 'node', 'example.js' ]
  const response = await request(`http://localhost:${port}`)
  lws.server.close()
  a.strictEqual(response.data.toString(), 'onetwo')
})

runner.test('stack: Two features', async function () {
  const port = 9000 + this.index
  class Feature {
    middleware (options) {
      return (ctx, next) => {
        ctx.body = 'one'
        next()
      }
    }
  }
  class Feature2 {
    middleware (options) {
      return (ctx, next) => {
        ctx.body += 'two'
        next()
      }
    }
  }
  const lws = new Lws({
    stack: [ Feature, Feature2 ],
    port: port
  })
  lws.start()
  const response = await request(`http://localhost:${port}`)
  lws.server.close()
  a.strictEqual(response.data.toString(), 'onetwo')
})

runner.skip('--help', function () {
  const lws = new Lws()
  process.argv = [ 'node', 'script.js', '--help' ]
  // need to be able to pass in the output stream in order to test it
})

runner.test('https', async function () {
  const port = 9000 + this.index
  class Feature {
    middleware (options) {
      return (ctx, next) => {
        ctx.body = 'one'
        next()
      }
    }
  }
  const lws = new Lws({
    stack: [ Feature ],
    https: true,
    port: port
  })
  lws.start()
  const url = require('url')
  const reqOptions = url.parse(`https://127.0.0.1:${port}`)
  reqOptions.rejectUnauthorized = false
  const response = await request(reqOptions)
  lws.server.close()
  a.strictEqual(response.res.statusCode, 200)
  a.strictEqual(response.data.toString(), 'one')
})
<|endoftext|>