Skip to content

Commit

Permalink
New API
Browse files Browse the repository at this point in the history
  • Loading branch information
akaspin committed Nov 10, 2010
1 parent c6e9741 commit af92818
Show file tree
Hide file tree
Showing 6 changed files with 470 additions and 157 deletions.
6 changes: 6 additions & 0 deletions ChangeLog
@@ -1,3 +1,9 @@
In work, Version 0.2

* Change API (in work)

* Docs (in work)

Version 0.1 Version 0.1


* Initial Version * Initial Version
8 changes: 0 additions & 8 deletions README.md

This file was deleted.

192 changes: 192 additions & 0 deletions chain/cookie.js
@@ -0,0 +1,192 @@
var crypto = require('crypto');
var Buffer = require('buffer').Buffer;

exports.secret = hex_hmac_sha1(Math.random(), Math.random());

/**
* Kaph stack Cookie operation.
*/
Op = {
DEFAULT: function() {
this.cookies = new Proc(this.request, this.response);
return this.next();
}
};
exports.Op = Op;

/**
* Cookie processor.
* @param {http.ServerRequest} request
* @param {http.ServerResponse} response
* @returns {Proc}
*/
function Proc(request, response) {
this._request = request;
this._response = response;
}
exports.Proc = Proc;
/**
* Set cookie. At first call decorates method `writeHead` of instance
* `http.ServerResponse`.
* @param {String} name
* @param value Cookie value
* @param {Object} options Optional options. See readme.
* @param {Boolean} encrypt Encrypt cookie value
*/
Proc.prototype.set = function(name, value, options, encrypt) {
options = options || {};
if (!this._outgoing) {
this._outgoing = {};

// Decorate original method
var _writeHead = this._response.writeHead;
var COOKIE_KEY = 'Set-Cookie', slice = Array.prototype.slice;
var self = this;
this._response.writeHead = function() {
// Honor the passed args and method signature
// (see http.writeHead docs)
var args = slice.call(arguments), headers = args[args.length - 1];
if (!headers || typeof (headers) != 'object') {
// No header arg - create and append to args list
args.push(headers = []);
}

// Merge cookie values
var prev = headers[COOKIE_KEY], cookies = self.deploy() || [];
if (prev) cookies.push(prev);
if (cookies.length > 0)
headers[COOKIE_KEY] = cookies;

// Invoke original writeHead()
_writeHead.apply(this, args);
};
}

// determine expiration date
if (options.days) {
options.expires = options.expires || new Date();
options.expires.setDate(options.expires.getDate() + options.days);
}

// Serve value
value = (value !== null && typeof value !== 'undefined') ?
value.toString() : '';

if (encrypt) {
// If value is secure - encrypt
value = [value.length, encode(value), options.expires ];
var signature = hex_hmac_sha1(value.join("|"), exports.secret);
value.push(signature);
value = value.join('|').replace(/=/g, '*');
}

// Form cookie
var cookie = name + '=' + escape(value) + ';';
options.expires && (cookie += ' expires=' +
options.expires.toUTCString() + ";");
options.path && (cookie += ' path=' + options.path + ';');
options.domain && (cookie += ' domain=' + options.domain + ';');
options.secure && (cookie += ' secure;');
options.httpOnly && (cookie += ' httponly');

this._outgoing[name] = cookie;
};

/**
* Get cookie by name
* @param {String} name
* @param {Boolean} decrypt
*/
Proc.prototype.get = function(name, decrypt) {
// Parse cookies if not yet parsed
if (!this._incoming) {
var header = this._request.headers["cookie"] || "";
var self = this;
this._incoming = {};

header.split(";").forEach( function( cookie ) {
var parts = cookie.split("="),
name = (parts[0] ? parts[0].trim() : ''),
value = (parts[1] ? parts[1].trim() : '');
self._incoming[name] = unescape(value);
});
}

var value = this._incoming[name];

// Decript value if needed
if (decrypt && value) {
var parts = value.replace(/\*/g, '=').split("|");
if (parts.length !== 4) {
return;
}

var len = parts[0];
value = decode(parts[1]).substr(0, len);
var expires = new Date(+parts[2]);
var remoteSig = parts[3];

if ( expires < new Date ) {
return;
}

var localSig = hex_hmac_sha1(parts.slice(0, 3).join("|"),
exports.secret);

if ( localSig !== remoteSig ) {
throw new Error("invalid cookie signature: " + name);
}
return value;
}
return value;
};

/**
* Clears cookie
* @param {String} name
*/
Proc.prototype.clear = function(name) {
options = {expires: new Date( +new Date - 30 * 24 * 60 * 60 * 1000) };
this.set(name, '', options);
};

/**
* Generate "Set-Cookie" header value
* @returns {Array}
*/
Proc.prototype.deploy = function() {
if (this._outgoing) return;
var stream = [];
for (var k in this._outgoing) {
stream.push(this._outgoing[k]);
}
return stream;
};

/**
* Generate hash.
* @param data data
* @returns {String} hash
*/
function hex_hmac_sha1(data, key) {
var hmac = crypto.createHmac('sha1', key);
hmac.update(data);
return hmac.digest('hex');
}

/**
* Encode data to base64
* @param data
* @returns {String}
*/
function encode(data) {
return (new Buffer(data)).toString('base64');
}
/**
* Decode data from base64
* @param data
* @returns {String}
*/
function decode(data) {
return (new Buffer(data, 'base64')).toString('utf8');
}
128 changes: 128 additions & 0 deletions chain/writer.js
@@ -0,0 +1,128 @@
var url = require('url');
var Buffer = require('buffer').Buffer;
var crypto = require('crypto');

/**
* Kaph stack Writer operation. Just makes writer object in Handler.
*/
Op = {
DEFAULT: function() {
this.writer = new Writer(this.request, this.response);
return this.next();
}
};
exports.Op = Op;

/**
*
* @param request
* @param response
* @returns {Writer}
*/
function Writer(request, response) {
this._request = request;
this._response = response;

this._headersWritten = false;
this._encoding = 'utf8';
this._statusCode = 200;
this._headers = {'Content-Type': "text/html; charset=UTF-8"};
}
exports.Writer = Writer;

/**
* Sets response status code.
* @param {Number} code HTTP Status code
*/
Handler.prototype.setStatus = function(code) {
if (!this.headersWritten && code in http.STATUS_CODES)
this.statusCode = code;
};

/**
* Set response header.
* @param {String} name
* @param value
*/
Writer.prototype.setHeader = function(name, value) {
value = value.toString();
if (value != value.replace(/[\x00-\x1f]/, " ").substring(0, 4000))
throw new Error('Unsafe header value ' + value);
this._headers[name] = value;
};

/**
* Write to response
* @param data
* @param encoding
*/
Writer.prototype.write = function(data, encoding) {
if (!data || data == null) return; // If no data - do nothing

if (!this._headersWritten) this._sendHeaders();
this._response.write(data, (encoding || this._encoding));
};

/**
*
* @param data
* @param encoding
*/
Writer.prototype.end = function(data, encoding) {
if (!this._headersWritten) {
// if any data present - add some info in headers:
if (!!data && this._statusCode == 200
&& this._request.method == 'GET') {
// ETag
if (!('ETag' in this._headers)) {
etag = '"' + crypto.createHash("sha1").
update(data).digest("hex") + '"';

// Check if-none-match
var inm = this._request.headers["if-none-match"];
if (inm && inm.indexOf(etag) != -1) {
// Not modified - just send 304
this._response.writeHead(304);
this._response.end();
return;
} else {
this.setHeader("ETag", etag);
}
}

// Content-Length
if (!("Content-Length" in this._headers)) {
var l = Buffer.isBuffer(data) ? data.length
: Buffer.byteLength(data, encoding || this._encoding);
this.setHeader("Content-Length", l);
}
}
this._sendHeaders();
}
this._response.end(data, encoding || this._encoding);
};

/**
* Sends a redirect to the given (optionally relative) URL.
* @param redirectUrl Redirect URL
* @param permanent Permanent. Default = false
* @throws HttpError
*/
Writer.prototype.redirect = function(redirectUrl, permanent) {
permanent = permanent || false;
if (this._headersWritten) {
throw new Error('Cannot redirect after headers have been written');
}
this.setStatus(permanent ? 301 : 302);
redirectUrl = redirectUrl.replace(/[\x00-\x1f]/, "");
this.setHeader('Location', url.resolve(this.request.url, redirectUrl));
this.end();
};

/**
* Sends all headers to client.
*/
Writer.prototype._sendHeaders = function() {
this._headersWritten = true;
this._response.writeHead(this._statusCode, this._headers);
};

0 comments on commit af92818

Please sign in to comment.