Improve user agent API

This commit is contained in:
Kagami Hiiragi 2015-01-24 15:13:21 +03:00
parent 4a0e4f3709
commit aa691b4d1f
3 changed files with 68 additions and 48 deletions

View File

@ -52,7 +52,7 @@ exports.version = {
remotePort: addrRecv.port, remotePort: addrRecv.port,
port: addrFrom.port, port: addrFrom.port,
nonce: nonce, nonce: nonce,
software: decodedUa.software, userAgent: decodedUa.str,
streamNumbers: decodedStreamNumbers.list, streamNumbers: decodedStreamNumbers.list,
// NOTE(Kagami): Real data length. It may be some gap between end // NOTE(Kagami): Real data length. It may be some gap between end
// of stream numbers list and end of payload: // of stream numbers list and end of payload:
@ -74,7 +74,7 @@ exports.version = {
var time = opts.time || new Date(); var time = opts.time || new Date();
var nonce = opts.nonce || exports.version.NONCE; var nonce = opts.nonce || exports.version.NONCE;
assert(nonce.length === 8, "Bad nonce"); assert(nonce.length === 8, "Bad nonce");
var software = opts.software || UserAgent.SELF; var userAgent = opts.userAgent || UserAgent.SELF;
var streamNumbers = opts.streamNumbers || [1]; var streamNumbers = opts.streamNumbers || [1];
// Start encoding. // Start encoding.
var protoVersion = new Buffer(4); var protoVersion = new Buffer(4);
@ -98,7 +98,7 @@ exports.version = {
addrRecv, addrRecv,
addrFrom, addrFrom,
nonce, nonce,
UserAgent.encode(software), UserAgent.encode(userAgent),
structs.var_int_list.encode(streamNumbers), structs.var_int_list.encode(streamNumbers),
]); ]);
}, },

View File

@ -14,19 +14,24 @@ var BM_VERSION = require("../package.json").version;
var SELF = exports.SELF = [{name: BM_NAME, version: BM_VERSION}]; var SELF = exports.SELF = [{name: BM_NAME, version: BM_VERSION}];
/** /**
* Decode user agent stack. * Decode user agent `var_str`. Just an alias for
* {@link var_str.decode}
* @function
*/
exports.decode = var_str.decode;
/**
* Parse raw user agent into software stack list. Most underlying
* software comes first.
* NOTE: Decoding is rather loose and non-strict, it won't fail on bad * NOTE: Decoding is rather loose and non-strict, it won't fail on bad
* user agent format because it's not that important. * user agent format because it's not that important.
* Also note that `rest` references input buffer. * @param {string} str - Raw user agent string
* @param {Buffer} buf - A buffer that starts with encoded user agent * @return {Object[]} Parsed user agent.
* @return {{software: Object[], length: number, rest: Buffer}}
* Decoded user agent structure.
*/ */
exports.decode = function(buf) { exports.parse = function(str) {
var decoded = var_str.decode(buf);
var software = []; var software = [];
if (decoded.str) { if (str.length > 2 && str[0] === "/" && str[str.length - 1] === "/") {
software = decoded.str.slice(1, -1).split("/"); software = str.slice(1, -1).split("/");
software = software.map(function(str) { software = software.map(function(str) {
// That's more readable than /([^:]*)(?::([^(]*)(?:\(([^)]*))?)?/ // That's more readable than /([^:]*)(?::([^(]*)(?:\(([^)]*))?)?/
var soft = {name: str}; var soft = {name: str};
@ -47,31 +52,35 @@ exports.decode = function(buf) {
return soft; return soft;
}); });
} }
return {software: software, length: decoded.length, rest: decoded.rest}; return software;
}; };
/** /**
* Encode user agent. Most underlying software comes first. * Encode user agent into `var_str` Buffer. Most underlying software
* @param {(Object[]|string[]|Object|string)} software - List of * comes first.
* software to encode * @param {(Object[]|string[]|string)} software - List of software to
* encode or raw user agent string
* @return {Buffer} Encoded user agent. * @return {Buffer} Encoded user agent.
*/ */
var encode = exports.encode = function(software) { var encode = exports.encode = function(software) {
if (!Array.isArray(software)) { var ua;
software = [software]; if (Array.isArray(software)) {
ua = software.map(function(soft) {
if (typeof soft === "string") {
return soft;
}
var version = soft.version || "0.0.0";
var str = soft.name + ":" + version;
if (soft.comments) {
str += "(" + soft.comments + ")";
}
return str;
}).join("/");
ua = "/" + ua + "/";
} else {
ua = software;
} }
var ua = software.map(function(soft) { return var_str.encode(ua);
if (typeof soft === "string") {
return soft;
}
var version = soft.version || "0.0.0";
var str = soft.name + ":" + version;
if (soft.comments) {
str += "(" + soft.comments + ")";
}
return str;
}).join("/");
return var_str.encode("/" + ua + "/");
}; };
/** /**

47
test.js
View File

@ -374,10 +374,20 @@ describe("Message types", function() {
expect(res.remotePort).to.equal(48444); expect(res.remotePort).to.equal(48444);
expect(res.port).to.equal(8444); expect(res.port).to.equal(8444);
expect(bufferEqual(res.nonce, version.NONCE)).to.be.true; expect(bufferEqual(res.nonce, version.NONCE)).to.be.true;
expect(res.software).to.deep.equal(UserAgent.SELF); expect(UserAgent.parse(res.userAgent)).to.deep.equal(UserAgent.SELF);
expect(res.streamNumbers).to.deep.equal([1]); expect(res.streamNumbers).to.deep.equal([1]);
expect(res.length).to.equal(101); expect(res.length).to.equal(101);
}); });
it("should accept raw user agent string", function() {
var res = version.decode(version.encode({
remoteHost: "1.2.3.4",
remotePort: 48444,
port: 8444,
userAgent: "/test:0.0.1/",
}));
expect(res.userAgent).to.equal("/test:0.0.1/");
});
}); });
describe("addr", function() { describe("addr", function() {
@ -736,10 +746,10 @@ describe("High-level classes", function() {
var bnode = {name: "bitchan-node", version: "0.0.1"}; var bnode = {name: "bitchan-node", version: "0.0.1"};
var bweb = {name: "bitchan-web"}; var bweb = {name: "bitchan-web"};
it("should decode", function() { it("should decode and parse", function() {
var ua = var_str.encode("/cBitmessage:0.2(iPad; U; CPU OS 3_2_1)/AndroidBuild:0.8/"); var ua = var_str.encode("/cBitmessage:0.2(iPad; U; CPU OS 3_2_1)/AndroidBuild:0.8/");
var res = UserAgent.decode(ua); var res = UserAgent.decode(ua);
expect(res.software).to.deep.equal([ expect(UserAgent.parse(res.str)).to.deep.equal([
{name: "cBitmessage", version: "0.2", comments: "iPad; U; CPU OS 3_2_1"}, {name: "cBitmessage", version: "0.2", comments: "iPad; U; CPU OS 3_2_1"},
{name: "AndroidBuild", version: "0.8"}, {name: "AndroidBuild", version: "0.8"},
]); ]);
@ -749,40 +759,41 @@ describe("High-level classes", function() {
it("should encode", function() { it("should encode", function() {
var ua = UserAgent.encode([pybm]); var ua = UserAgent.encode([pybm]);
expect(var_str.decode(ua).str).to.equal("/PyBitmessage:0.4.4/");
var res = UserAgent.decode(ua); var res = UserAgent.decode(ua);
expect(res.software).to.deep.equal([pybm]); expect(res.str).to.equal("/PyBitmessage:0.4.4/");
expect(UserAgent.parse(res.str)).to.deep.equal([pybm]);
expect(res.length).to.equal(21); expect(res.length).to.equal(21);
expect(res.rest.toString("hex")).to.equal(""); expect(res.rest.toString("hex")).to.equal("");
ua = UserAgent.encode([{name: "test", "comments": "linux"}]); ua = UserAgent.encode([{name: "test", "comments": "linux"}]);
expect(var_str.decode(ua).str).to.equal("/test:0.0.0(linux)/"); expect(UserAgent.decode(ua).str).to.equal("/test:0.0.0(linux)/");
}); });
it("should encode bitmessage's user agent", function() { it("should encode bitmessage's user agent", function() {
var res = UserAgent.decode(UserAgent.encodeSelf()) var res = UserAgent.decode(UserAgent.encodeSelf())
var software = res.software; var software = UserAgent.parse(res.str);
expect(software[0].name).to.equal("bitmessage"); expect(software[0].name).to.equal("bitmessage");
expect(software[0]).to.have.property("version"); expect(software[0]).to.have.property("version");
res = UserAgent.decode(UserAgent.encodeSelfWith([bnode, bweb])); res = UserAgent.decode(UserAgent.encodeSelfWith([bnode, bweb]));
software = res.software; software = UserAgent.parse(res.str);
expect(software[0].name).to.equal("bitmessage"); expect(software[0].name).to.equal("bitmessage");
expect(software[1]).to.deep.equal(bnode); expect(software[1]).to.deep.equal(bnode);
expect(software[2].name).to.equal(bweb.name); expect(software[2].name).to.equal(bweb.name);
expect(software[2].version).to.equal("0.0.0"); expect(software[2].version).to.equal("0.0.0");
}); });
it("should accept just object or string(s) on encode", function() { it("should accept raw user agent string on encode", function() {
var enc1 = UserAgent.encode({name: "test", version: "0.0.1"}); var enc = UserAgent.encode("/test:0.0.1/");
var enc2 = UserAgent.encode("test:0.0.1"); var software = UserAgent.parse(UserAgent.decode(enc).str);
var res = [{name: "test", version: "0.0.1"}]; expect(software).to.deep.equal([{name: "test", version: "0.0.1"}]);
expect(UserAgent.decode(enc1).software).to.deep.equal(res); });
expect(UserAgent.decode(enc2).software).to.deep.equal(res);
var enc3 = UserAgent.encodeSelfWith("test:0.0.1"); it("should parse empty/incorrect user agent into empty list", function() {
var software = UserAgent.decode(enc3).software; expect(UserAgent.parse("").length).to.equal(0);
expect(software[0].name).to.equal("bitmessage"); expect(UserAgent.parse("test").length).to.equal(0);
expect(software[1]).to.deep.equal(res[0]); expect(UserAgent.parse("/test").length).to.equal(0);
expect(UserAgent.parse("test/").length).to.equal(0);
}); });
}); });
}); });