Fully end to end encrypted anonymous chat program. Server only stores public key lookup for users and the encrypted messages. No credentials are transfered to the server, but kept in local browser storage. This allows 100% safe chatting.
https://safechat.ch
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
11 KiB
461 lines
11 KiB
9 years ago
|
|
||
|
/*!
|
||
|
* Express - view
|
||
|
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
||
|
* MIT Licensed
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Module dependencies.
|
||
|
*/
|
||
|
|
||
|
var path = require('path')
|
||
|
, extname = path.extname
|
||
|
, dirname = path.dirname
|
||
|
, basename = path.basename
|
||
|
, utils = require('connect').utils
|
||
|
, View = require('./view/view')
|
||
|
, partial = require('./view/partial')
|
||
|
, union = require('./utils').union
|
||
|
, merge = utils.merge
|
||
|
, http = require('http')
|
||
|
, res = http.ServerResponse.prototype;
|
||
|
|
||
|
/**
|
||
|
* Expose constructors.
|
||
|
*/
|
||
|
|
||
|
exports = module.exports = View;
|
||
|
|
||
|
/**
|
||
|
* Export template engine registrar.
|
||
|
*/
|
||
|
|
||
|
exports.register = View.register;
|
||
|
|
||
|
/**
|
||
|
* Lookup and compile `view` with cache support by supplying
|
||
|
* both the `cache` object and `cid` string,
|
||
|
* followed by `options` passed to `exports.lookup()`.
|
||
|
*
|
||
|
* @param {String} view
|
||
|
* @param {Object} cache
|
||
|
* @param {Object} cid
|
||
|
* @param {Object} options
|
||
|
* @return {View}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
exports.compile = function(view, cache, cid, options){
|
||
|
if (cache && cid && cache[cid]){
|
||
|
options.filename = cache[cid].path;
|
||
|
return cache[cid];
|
||
|
}
|
||
|
|
||
|
// lookup
|
||
|
view = exports.lookup(view, options);
|
||
|
|
||
|
// hints
|
||
|
if (!view.exists) {
|
||
|
if (options.hint) hintAtViewPaths(view.original, options);
|
||
|
var err = new Error('failed to locate view "' + view.original.view + '"');
|
||
|
err.view = view.original;
|
||
|
throw err;
|
||
|
}
|
||
|
|
||
|
// compile
|
||
|
options.filename = view.path;
|
||
|
view.fn = view.templateEngine.compile(view.contents, options);
|
||
|
cache[cid] = view;
|
||
|
|
||
|
return view;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Lookup `view`, returning an instanceof `View`.
|
||
|
*
|
||
|
* Options:
|
||
|
*
|
||
|
* - `root` root directory path
|
||
|
* - `defaultEngine` default template engine
|
||
|
* - `parentView` parent `View` object
|
||
|
* - `cache` cache object
|
||
|
* - `cacheid` optional cache id
|
||
|
*
|
||
|
* Lookup:
|
||
|
*
|
||
|
* - partial `_<name>`
|
||
|
* - any `<name>/index`
|
||
|
* - non-layout `../<name>/index`
|
||
|
* - any `<root>/<name>`
|
||
|
* - partial `<root>/_<name>`
|
||
|
*
|
||
|
* @param {String} view
|
||
|
* @param {Object} options
|
||
|
* @return {View}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
exports.lookup = function(view, options){
|
||
|
var orig = view = new View(view, options)
|
||
|
, partial = options.isPartial
|
||
|
, layout = options.isLayout;
|
||
|
|
||
|
// Try _ prefix ex: ./views/_<name>.jade
|
||
|
// taking precedence over the direct path
|
||
|
if (partial) {
|
||
|
view = new View(orig.prefixPath, options);
|
||
|
if (!view.exists) view = orig;
|
||
|
}
|
||
|
|
||
|
// Try index ex: ./views/user/index.jade
|
||
|
if (!layout && !view.exists) view = new View(orig.indexPath, options);
|
||
|
|
||
|
// Try ../<name>/index ex: ../user/index.jade
|
||
|
// when calling partial('user') within the same dir
|
||
|
if (!layout && !view.exists) view = new View(orig.upIndexPath, options);
|
||
|
|
||
|
// Try root ex: <root>/user.jade
|
||
|
if (!view.exists) view = new View(orig.rootPath, options);
|
||
|
|
||
|
// Try root _ prefix ex: <root>/_user.jade
|
||
|
if (!view.exists && partial) view = new View(view.prefixPath, options);
|
||
|
|
||
|
view.original = orig;
|
||
|
return view;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Partial render helper.
|
||
|
*
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function renderPartial(res, view, options, parentLocals, parent){
|
||
|
var collection, object, locals;
|
||
|
|
||
|
if (options) {
|
||
|
// collection
|
||
|
if (options.collection) {
|
||
|
collection = options.collection;
|
||
|
delete options.collection;
|
||
|
} else if ('length' in options) {
|
||
|
collection = options;
|
||
|
options = {};
|
||
|
}
|
||
|
|
||
|
// locals
|
||
|
if (options.locals) {
|
||
|
locals = options.locals;
|
||
|
delete options.locals;
|
||
|
}
|
||
|
|
||
|
// object
|
||
|
if ('Object' != options.constructor.name) {
|
||
|
object = options;
|
||
|
options = {};
|
||
|
} else if (undefined != options.object) {
|
||
|
object = options.object;
|
||
|
delete options.object;
|
||
|
}
|
||
|
} else {
|
||
|
options = {};
|
||
|
}
|
||
|
|
||
|
// Inherit locals from parent
|
||
|
union(options, parentLocals);
|
||
|
|
||
|
// Merge locals
|
||
|
if (locals) merge(options, locals);
|
||
|
|
||
|
// Partials dont need layouts
|
||
|
options.isPartial = true;
|
||
|
options.layout = false;
|
||
|
|
||
|
// Deduce name from view path
|
||
|
var name = options.as || partial.resolveObjectName(view);
|
||
|
|
||
|
// Render partial
|
||
|
function render(){
|
||
|
if (object) {
|
||
|
if ('string' == typeof name) {
|
||
|
options[name] = object;
|
||
|
} else if (name === global) {
|
||
|
merge(options, object);
|
||
|
}
|
||
|
}
|
||
|
return res.render(view, options, null, parent, true);
|
||
|
}
|
||
|
|
||
|
// Collection support
|
||
|
if (collection) {
|
||
|
var len = collection.length
|
||
|
, buf = ''
|
||
|
, keys
|
||
|
, key
|
||
|
, val;
|
||
|
|
||
|
options.collectionLength = len;
|
||
|
|
||
|
if ('number' == typeof len || Array.isArray(collection)) {
|
||
|
for (var i = 0; i < len; ++i) {
|
||
|
val = collection[i];
|
||
|
options.firstInCollection = i == 0;
|
||
|
options.indexInCollection = i;
|
||
|
options.lastInCollection = i == len - 1;
|
||
|
object = val;
|
||
|
buf += render();
|
||
|
}
|
||
|
} else {
|
||
|
keys = Object.keys(collection);
|
||
|
len = keys.length;
|
||
|
options.collectionLength = len;
|
||
|
options.collectionKeys = keys;
|
||
|
for (var i = 0; i < len; ++i) {
|
||
|
key = keys[i];
|
||
|
val = collection[key];
|
||
|
options.keyInCollection = key;
|
||
|
options.firstInCollection = i == 0;
|
||
|
options.indexInCollection = i;
|
||
|
options.lastInCollection = i == len - 1;
|
||
|
object = val;
|
||
|
buf += render();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return buf;
|
||
|
} else {
|
||
|
return render();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Render `view` partial with the given `options`. Optionally a
|
||
|
* callback `fn(err, str)` may be passed instead of writing to
|
||
|
* the socket.
|
||
|
*
|
||
|
* Options:
|
||
|
*
|
||
|
* - `object` Single object with name derived from the view (unless `as` is present)
|
||
|
*
|
||
|
* - `as` Variable name for each `collection` value, defaults to the view name.
|
||
|
* * as: 'something' will add the `something` local variable
|
||
|
* * as: this will use the collection value as the template context
|
||
|
* * as: global will merge the collection value's properties with `locals`
|
||
|
*
|
||
|
* - `collection` Array of objects, the name is derived from the view name itself.
|
||
|
* For example _video.html_ will have a object _video_ available to it.
|
||
|
*
|
||
|
* @param {String} view
|
||
|
* @param {Object|Array|Function} options, collection, callback, or object
|
||
|
* @param {Function} fn
|
||
|
* @return {String}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
res.partial = function(view, options, fn){
|
||
|
var app = this.app
|
||
|
, options = options || {}
|
||
|
, viewEngine = app.set('view engine')
|
||
|
, parent = {};
|
||
|
|
||
|
// accept callback as second argument
|
||
|
if ('function' == typeof options) {
|
||
|
fn = options;
|
||
|
options = {};
|
||
|
}
|
||
|
|
||
|
// root "views" option
|
||
|
parent.dirname = app.set('views') || process.cwd() + '/views';
|
||
|
|
||
|
// utilize "view engine" option
|
||
|
if (viewEngine) parent.engine = viewEngine;
|
||
|
|
||
|
// render the partial
|
||
|
try {
|
||
|
var str = renderPartial(this, view, options, null, parent);
|
||
|
} catch (err) {
|
||
|
if (fn) {
|
||
|
fn(err);
|
||
|
} else {
|
||
|
this.req.next(err);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// callback or transfer
|
||
|
if (fn) {
|
||
|
fn(null, str);
|
||
|
} else {
|
||
|
this.send(str);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Render `view` with the given `options` and optional callback `fn`.
|
||
|
* When a callback function is given a response will _not_ be made
|
||
|
* automatically, however otherwise a response of _200_ and _text/html_ is given.
|
||
|
*
|
||
|
* Options:
|
||
|
*
|
||
|
* - `scope` Template evaluation context (the value of `this`)
|
||
|
* - `debug` Output debugging information
|
||
|
* - `status` Response status code
|
||
|
*
|
||
|
* @param {String} view
|
||
|
* @param {Object|Function} options or callback function
|
||
|
* @param {Function} fn
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
res.render = function(view, opts, fn, parent, sub){
|
||
|
// support callback function as second arg
|
||
|
if ('function' == typeof opts) {
|
||
|
fn = opts, opts = null;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
return this._render(view, opts, fn, parent, sub);
|
||
|
} catch (err) {
|
||
|
// callback given
|
||
|
if (fn) {
|
||
|
fn(err);
|
||
|
// unwind to root call to prevent multiple callbacks
|
||
|
} else if (sub) {
|
||
|
throw err;
|
||
|
// root template, next(err)
|
||
|
} else {
|
||
|
this.req.next(err);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// private render()
|
||
|
|
||
|
res._render = function(view, opts, fn, parent, sub){
|
||
|
var options = {}
|
||
|
, self = this
|
||
|
, app = this.app
|
||
|
, helpers = app._locals
|
||
|
, dynamicHelpers = app.dynamicViewHelpers
|
||
|
, viewOptions = app.set('view options')
|
||
|
, root = app.set('views') || process.cwd() + '/views';
|
||
|
|
||
|
// cache id
|
||
|
var cid = app.enabled('view cache')
|
||
|
? view + (parent ? ':' + parent.path : '')
|
||
|
: false;
|
||
|
|
||
|
// merge "view options"
|
||
|
if (viewOptions) merge(options, viewOptions);
|
||
|
|
||
|
// merge res._locals
|
||
|
if (this._locals) merge(options, this._locals);
|
||
|
|
||
|
// merge render() options
|
||
|
if (opts) merge(options, opts);
|
||
|
|
||
|
// merge render() .locals
|
||
|
if (opts && opts.locals) merge(options, opts.locals);
|
||
|
|
||
|
// status support
|
||
|
if (options.status) this.statusCode = options.status;
|
||
|
|
||
|
// capture attempts
|
||
|
options.attempts = [];
|
||
|
|
||
|
var partial = options.isPartial
|
||
|
, layout = options.layout;
|
||
|
|
||
|
// Layout support
|
||
|
if (true === layout || undefined === layout) {
|
||
|
layout = 'layout';
|
||
|
}
|
||
|
|
||
|
// Default execution scope to a plain object
|
||
|
options.scope = options.scope || {};
|
||
|
|
||
|
// Populate view
|
||
|
options.parentView = parent;
|
||
|
|
||
|
// "views" setting
|
||
|
options.root = root;
|
||
|
|
||
|
// "view engine" setting
|
||
|
options.defaultEngine = app.set('view engine');
|
||
|
|
||
|
// charset option
|
||
|
if (options.charset) this.charset = options.charset;
|
||
|
|
||
|
// Dynamic helper support
|
||
|
if (false !== options.dynamicHelpers) {
|
||
|
// cache
|
||
|
if (!this.__dynamicHelpers) {
|
||
|
this.__dynamicHelpers = {};
|
||
|
for (var key in dynamicHelpers) {
|
||
|
this.__dynamicHelpers[key] = dynamicHelpers[key].call(
|
||
|
this.app
|
||
|
, this.req
|
||
|
, this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// apply
|
||
|
merge(options, this.__dynamicHelpers);
|
||
|
}
|
||
|
|
||
|
// Merge view helpers
|
||
|
union(options, helpers);
|
||
|
|
||
|
// Always expose partial() as a local
|
||
|
options.partial = function(path, opts){
|
||
|
return renderPartial(self, path, opts, options, view);
|
||
|
};
|
||
|
|
||
|
// View lookup
|
||
|
options.hint = app.enabled('hints');
|
||
|
view = exports.compile(view, app.cache, cid, options);
|
||
|
|
||
|
// layout helper
|
||
|
options.layout = function(path){
|
||
|
layout = path;
|
||
|
};
|
||
|
|
||
|
// render
|
||
|
var str = view.fn.call(options.scope, options);
|
||
|
|
||
|
// layout expected
|
||
|
if (layout) {
|
||
|
options.isLayout = true;
|
||
|
options.layout = false;
|
||
|
options.body = str;
|
||
|
this.render(layout, options, fn, view, true);
|
||
|
// partial return
|
||
|
} else if (partial) {
|
||
|
return str;
|
||
|
// render complete, and
|
||
|
// callback given
|
||
|
} else if (fn) {
|
||
|
fn(null, str);
|
||
|
// respond
|
||
|
} else {
|
||
|
this.send(str);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Hint at view path resolution, outputting the
|
||
|
* paths that Express has tried.
|
||
|
*
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function hintAtViewPaths(view, options) {
|
||
|
console.error();
|
||
|
console.error('failed to locate view "' + view.view + '", tried:');
|
||
|
options.attempts.forEach(function(path){
|
||
|
console.error(' - %s', path);
|
||
|
});
|
||
|
console.error();
|
||
|
}
|