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,
port: addrFrom.port,
nonce: nonce,
software: decodedUa.software,
userAgent: decodedUa.str,
streamNumbers: decodedStreamNumbers.list,
// NOTE(Kagami): Real data length. It may be some gap between end
// of stream numbers list and end of payload:
@ -74,7 +74,7 @@ exports.version = {
var time = opts.time || new Date();
var nonce = opts.nonce || exports.version.NONCE;
assert(nonce.length === 8, "Bad nonce");
var software = opts.software || UserAgent.SELF;
var userAgent = opts.userAgent || UserAgent.SELF;
var streamNumbers = opts.streamNumbers || [1];
// Start encoding.
var protoVersion = new Buffer(4);
@ -98,7 +98,7 @@ exports.version = {
addrRecv,
addrFrom,
nonce,
UserAgent.encode(software),
UserAgent.encode(userAgent),
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}];
/**
* 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
* user agent format because it's not that important.
* Also note that `rest` references input buffer.
* @param {Buffer} buf - A buffer that starts with encoded user agent
* @return {{software: Object[], length: number, rest: Buffer}}
* Decoded user agent structure.
* @param {string} str - Raw user agent string
* @return {Object[]} Parsed user agent.
*/
exports.decode = function(buf) {
var decoded = var_str.decode(buf);
exports.parse = function(str) {
var software = [];
if (decoded.str) {
software = decoded.str.slice(1, -1).split("/");
if (str.length > 2 && str[0] === "/" && str[str.length - 1] === "/") {
software = str.slice(1, -1).split("/");
software = software.map(function(str) {
// That's more readable than /([^:]*)(?::([^(]*)(?:\(([^)]*))?)?/
var soft = {name: str};
@ -47,31 +52,35 @@ exports.decode = function(buf) {
return soft;
});
}
return {software: software, length: decoded.length, rest: decoded.rest};
return software;
};
/**
* Encode user agent. Most underlying software comes first.
* @param {(Object[]|string[]|Object|string)} software - List of
* software to encode
* Encode user agent into `var_str` Buffer. Most underlying software
* comes first.
* @param {(Object[]|string[]|string)} software - List of software to
* encode or raw user agent string
* @return {Buffer} Encoded user agent.
*/
var encode = exports.encode = function(software) {
if (!Array.isArray(software)) {
software = [software];
var ua;
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) {
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 + "/");
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.port).to.equal(8444);
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.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() {
@ -736,10 +746,10 @@ describe("High-level classes", function() {
var bnode = {name: "bitchan-node", version: "0.0.1"};
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 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: "AndroidBuild", version: "0.8"},
]);
@ -749,40 +759,41 @@ describe("High-level classes", function() {
it("should encode", function() {
var ua = UserAgent.encode([pybm]);
expect(var_str.decode(ua).str).to.equal("/PyBitmessage:0.4.4/");
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.rest.toString("hex")).to.equal("");
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() {
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]).to.have.property("version");
res = UserAgent.decode(UserAgent.encodeSelfWith([bnode, bweb]));
software = res.software;
software = UserAgent.parse(res.str);
expect(software[0].name).to.equal("bitmessage");
expect(software[1]).to.deep.equal(bnode);
expect(software[2].name).to.equal(bweb.name);
expect(software[2].version).to.equal("0.0.0");
});
it("should accept just object or string(s) on encode", function() {
var enc1 = UserAgent.encode({name: "test", version: "0.0.1"});
var enc2 = UserAgent.encode("test:0.0.1");
var res = [{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");
var software = UserAgent.decode(enc3).software;
expect(software[0].name).to.equal("bitmessage");
expect(software[1]).to.deep.equal(res[0]);
it("should accept raw user agent string on encode", function() {
var enc = UserAgent.encode("/test:0.0.1/");
var software = UserAgent.parse(UserAgent.decode(enc).str);
expect(software).to.deep.equal([{name: "test", version: "0.0.1"}]);
});
it("should parse empty/incorrect user agent into empty list", function() {
expect(UserAgent.parse("").length).to.equal(0);
expect(UserAgent.parse("test").length).to.equal(0);
expect(UserAgent.parse("/test").length).to.equal(0);
expect(UserAgent.parse("test/").length).to.equal(0);
});
});
});