mirror of https://github.com/gophish/gophish
193 lines
6.7 KiB
JavaScript
193 lines
6.7 KiB
JavaScript
|
|
// The purpose of the `Content` object is to abstract away the data conversions
|
|
// to and from raw content entities as strings. For example, you want to be able
|
|
// to pass in a Javascript object and have it be automatically converted into a
|
|
// JSON string if the `content-type` is set to a JSON-based media type.
|
|
// Conversely, you want to be able to transparently get back a Javascript object
|
|
// in the response if the `content-type` is a JSON-based media-type.
|
|
|
|
// One limitation of the current implementation is that it [assumes the `charset` is UTF-8](https://github.com/spire-io/shred/issues/5).
|
|
|
|
// The `Content` constructor takes an options object, which *must* have either a
|
|
// `body` or `data` property and *may* have a `type` property indicating the
|
|
// media type. If there is no `type` attribute, a default will be inferred.
|
|
var Content = function(options) {
|
|
this.body = options.body;
|
|
this.data = options.data;
|
|
this.type = options.type;
|
|
};
|
|
|
|
Content.prototype = {
|
|
// Treat `toString()` as asking for the `content.body`. That is, the raw content entity.
|
|
//
|
|
// toString: function() { return this.body; }
|
|
//
|
|
// Commented out, but I've forgotten why. :/
|
|
};
|
|
|
|
|
|
// `Content` objects have the following attributes:
|
|
Object.defineProperties(Content.prototype,{
|
|
|
|
// - **type**. Typically accessed as `content.type`, reflects the `content-type`
|
|
// header associated with the request or response. If not passed as an options
|
|
// to the constructor or set explicitly, it will infer the type the `data`
|
|
// attribute, if possible, and, failing that, will default to `text/plain`.
|
|
type: {
|
|
get: function() {
|
|
if (this._type) {
|
|
return this._type;
|
|
} else {
|
|
if (this._data) {
|
|
switch(typeof this._data) {
|
|
case "string": return "text/plain";
|
|
case "object": return "application/json";
|
|
}
|
|
}
|
|
}
|
|
return "text/plain";
|
|
},
|
|
set: function(value) {
|
|
this._type = value;
|
|
return this;
|
|
},
|
|
enumerable: true
|
|
},
|
|
|
|
// - **data**. Typically accessed as `content.data`, reflects the content entity
|
|
// converted into Javascript data. This can be a string, if the `type` is, say,
|
|
// `text/plain`, but can also be a Javascript object. The conversion applied is
|
|
// based on the `processor` attribute. The `data` attribute can also be set
|
|
// directly, in which case the conversion will be done the other way, to infer
|
|
// the `body` attribute.
|
|
data: {
|
|
get: function() {
|
|
if (this._body) {
|
|
return this.processor.parser(this._body);
|
|
} else {
|
|
return this._data;
|
|
}
|
|
},
|
|
set: function(data) {
|
|
if (this._body&&data) Errors.setDataWithBody(this);
|
|
this._data = data;
|
|
return this;
|
|
},
|
|
enumerable: true
|
|
},
|
|
|
|
// - **body**. Typically accessed as `content.body`, reflects the content entity
|
|
// as a UTF-8 string. It is the mirror of the `data` attribute. If you set the
|
|
// `data` attribute, the `body` attribute will be inferred and vice-versa. If
|
|
// you attempt to set both, an exception is raised.
|
|
body: {
|
|
get: function() {
|
|
if (this._data) {
|
|
return this.processor.stringify(this._data);
|
|
} else {
|
|
return this._body.toString();
|
|
}
|
|
},
|
|
set: function(body) {
|
|
if (this._data&&body) Errors.setBodyWithData(this);
|
|
this._body = body;
|
|
return this;
|
|
},
|
|
enumerable: true
|
|
},
|
|
|
|
// - **processor**. The functions that will be used to convert to/from `data` and
|
|
// `body` attributes. You can add processors. The two that are built-in are for
|
|
// `text/plain`, which is basically an identity transformation and
|
|
// `application/json` and other JSON-based media types (including custom media
|
|
// types with `+json`). You can add your own processors. See below.
|
|
processor: {
|
|
get: function() {
|
|
var processor = Content.processors[this.type];
|
|
if (processor) {
|
|
return processor;
|
|
} else {
|
|
// Return the first processor that matches any part of the
|
|
// content type. ex: application/vnd.foobar.baz+json will match json.
|
|
var main = this.type.split(";")[0];
|
|
var parts = main.split(/\+|\//);
|
|
for (var i=0, l=parts.length; i < l; i++) {
|
|
processor = Content.processors[parts[i]]
|
|
}
|
|
return processor || {parser:identity,stringify:toString};
|
|
}
|
|
},
|
|
enumerable: true
|
|
},
|
|
|
|
// - **length**. Typically accessed as `content.length`, returns the length in
|
|
// bytes of the raw content entity.
|
|
length: {
|
|
get: function() {
|
|
if (typeof Buffer !== 'undefined') {
|
|
return Buffer.byteLength(this.body);
|
|
}
|
|
return this.body.length;
|
|
}
|
|
}
|
|
});
|
|
|
|
Content.processors = {};
|
|
|
|
// The `registerProcessor` function allows you to add your own processors to
|
|
// convert content entities. Each processor consists of a Javascript object with
|
|
// two properties:
|
|
// - **parser**. The function used to parse a raw content entity and convert it
|
|
// into a Javascript data type.
|
|
// - **stringify**. The function used to convert a Javascript data type into a
|
|
// raw content entity.
|
|
Content.registerProcessor = function(types,processor) {
|
|
|
|
// You can pass an array of types that will trigger this processor, or just one.
|
|
// We determine the array via duck-typing here.
|
|
if (types.forEach) {
|
|
types.forEach(function(type) {
|
|
Content.processors[type] = processor;
|
|
});
|
|
} else {
|
|
// If you didn't pass an array, we just use what you pass in.
|
|
Content.processors[types] = processor;
|
|
}
|
|
};
|
|
|
|
// Register the identity processor, which is used for text-based media types.
|
|
var identity = function(x) { return x; }
|
|
, toString = function(x) { return x.toString(); }
|
|
Content.registerProcessor(
|
|
["text/html","text/plain","text"],
|
|
{ parser: identity, stringify: toString });
|
|
|
|
// Register the JSON processor, which is used for JSON-based media types.
|
|
Content.registerProcessor(
|
|
["application/json; charset=utf-8","application/json","json"],
|
|
{
|
|
parser: function(string) {
|
|
return JSON.parse(string);
|
|
},
|
|
stringify: function(data) {
|
|
return JSON.stringify(data); }});
|
|
|
|
var qs = require('querystring');
|
|
// Register the post processor, which is used for JSON-based media types.
|
|
Content.registerProcessor(
|
|
["application/x-www-form-urlencoded"],
|
|
{ parser : qs.parse, stringify : qs.stringify });
|
|
|
|
// Error functions are defined separately here in an attempt to make the code
|
|
// easier to read.
|
|
var Errors = {
|
|
setDataWithBody: function(object) {
|
|
throw new Error("Attempt to set data attribute of a content object " +
|
|
"when the body attributes was already set.");
|
|
},
|
|
setBodyWithData: function(object) {
|
|
throw new Error("Attempt to set body attribute of a content object " +
|
|
"when the data attributes was already set.");
|
|
}
|
|
}
|
|
module.exports = Content; |