From aa691b4d1f59e3524b7d176b0f87dc0951546ad7 Mon Sep 17 00:00:00 2001 From: Kagami Hiiragi Date: Sat, 24 Jan 2015 15:13:21 +0300 Subject: [PATCH] Improve user agent API --- lib/messages.js | 6 ++--- lib/user-agent.js | 63 +++++++++++++++++++++++++++-------------------- test.js | 47 +++++++++++++++++++++-------------- 3 files changed, 68 insertions(+), 48 deletions(-) diff --git a/lib/messages.js b/lib/messages.js index 78b4f4c..1069d1b 100644 --- a/lib/messages.js +++ b/lib/messages.js @@ -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), ]); }, diff --git a/lib/user-agent.js b/lib/user-agent.js index 5a242e9..9c8ee35 100644 --- a/lib/user-agent.js +++ b/lib/user-agent.js @@ -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); }; /** diff --git a/test.js b/test.js index 12ea401..36479db 100644 --- a/test.js +++ b/test.js @@ -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); }); }); });