From cbb2fa5c547b23ef5ce99228414955c941898cb5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 24 Aug 2016 01:16:48 -0700 Subject: [PATCH] uri: rewrite. --- lib/bcoin/utils/uri.js | 225 +++++++++++++++++++++++++++++------------ 1 file changed, 162 insertions(+), 63 deletions(-) diff --git a/lib/bcoin/utils/uri.js b/lib/bcoin/utils/uri.js index d3943537..5473a269 100644 --- a/lib/bcoin/utils/uri.js +++ b/lib/bcoin/utils/uri.js @@ -1,93 +1,192 @@ /** - * bitcoin uri parsing - * @module uri - * @license - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * uri.js - bitcoin uri parsing for bcoin * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; -var url = require('url'); -var querystring = require('querystring'); +var bcoin = require('../env'); var utils = require('../utils/utils'); var assert = utils.assert; -/** - * Parse a bitcoin URI. - * @param {String} uri - Bitcoin URI. - * @returns {ParsedURI} - * @throws on non-bitcoin uri - */ +function URI(options) { + if (!(this instanceof URI)) + return new URI(options); -exports.parse = function parse(uri) { - var data = url.parse(uri); - var query = querystring.parse(data.query || ''); + this.address = new bcoin.address(); + this.amount = -1; + this.label = null; + this.message = null; + this.request = null; - assert(data.protocol === 'bitcoin:', 'Not a bitcoin URI.'); - assert(data.hostname, 'No address present.'); + if (options) + this.fromOptions(options); +} - return { - address: data.hostname, - amount: query.amount ? utils.satoshi(query.amount) : null, - label: query.label || null, - message: query.message || null, - request: query.r || null - }; -}; +URI.prototype.fromOptions = function fromOptions(options) { + if (typeof options === 'string') + return this.fromString(options); -/** - * Test whether an object is a bitcoin URI. - * @param {String} uri - * @returns {Boolean} - */ + if (options.address) + this.address.fromOptions(options.address); -exports.validate = function validate(uri) { - try { - exports.parse(uri); - return true; - } catch (e) { - return false; + if (options.amount != null) { + assert(utils.isNumber(options.amount)); + this.amount = options.amount; } + + if (options.label) { + assert(typeof options.label === 'string'); + this.label = options.label; + } + + if (options.message) { + assert(typeof options.message === 'string'); + this.message = options.message; + } + + if (options.r) { + assert(typeof options.request === 'string'); + this.request = options.r; + } + + return this; }; -/** - * Encode data as a bitcoin URI. - * @param {ParsedURI|Base58Address} data/address - * @param {Amount?} amount - * @returns {String} URI - * @throws when no address provided +URI.fromOptions = function fromOptions(options) { + return new URI().fromOptions(options); +}; + +URI.prototype.fromString = function fromString(str) { + var prefix, index, address, query; + + assert(typeof str === 'string'); + assert(str.length > 8, 'Not a bitcoin URI.'); + + prefix = str.substring(0, 8); + + if (prefix !== 'bitcoin:') + throw new Error('Not a bitcoin URI.'); + + str = str.substring(8); + + index = str.indexOf('?'); + + if (index === -1) { + address = str; + } else { + address = str.substring(0, index); + query = str.substring(index + 1); + } + + this.address.fromBase58(address); + + if (!query) + return this; + + query = parsePairs(query); + + if (query.amount) + this.amount = utils.satoshi(query.amount); + + if (query.label) + this.label = query.label; + + if (query.message) + this.message = query.message; + + if (query.r) + this.request = query.r; + + return this; +}; + +URI.fromString = function fromString(str) { + return new URI().fromString(str); +}; + +URI.prototype.toString = function toString() { + var str = 'bitcoin:'; + var query = []; + + str += this.address.toBase58(); + + if (this.amount !== -1) + query.push('amount=' + utils.btc(this.amount)); + + if (this.label) + query.push('label=' + escape(this.label)); + + if (this.message) + query.push('message=' + escape(this.message)); + + if (this.request) + query.push('r=' + escape(this.request)); + + if (query.length > 0) + str += '?' + query.join('&'); + + return str; +}; + +URI.prototype.inspect = function inspect() { + return ''; +}; + +/* + * Helpers */ -exports.stringify = function stringify(address, amount) { - var query = {}; - var data = address; - var uri; +function parsePairs(str) { + var parts = str.split('&'); + var data = {}; + var i, index, pair, key, value; - if (typeof address === 'string') - data = { address: address, amount: amount }; + for (i = 0; i < parts.length; i++) { + pair = parts[i]; + index = pair.indexOf('='); - assert(data.address, 'Address is required for a bitcoin URI.'); + if (index === -1) { + key = pair; + value = ''; + } else { + key = pair.substring(0, index); + value = pair.substring(index + 1); + } - uri = 'bitcoin:' + data.address; + key = unescape(key); - if (data.amount) - query.amount = utils.btc(data.amount); + if (key.length === 0) + continue; - if (data.label) - query.label = data.label; + if (value.length > 0) + value = unescape(value); - if (data.message) - query.message = data.message; + data[key] = value; + } - if (data.request) - query.r = data.request; + return data; +} - query = querystring.stringify(query); +function unescape(str) { + try { + str = decodeURIComponent(str).replace(/\+/g, ' '); + } finally { + return str.replace(/\0/g, ''); + } +} - if (query.length === 0) - return uri; +function escape(str) { + try { + str = encodeURIComponent(str).replace(/%20/g, '+'); + } finally { + return str; + } +} - return uri + '?' + query; -}; +/* + * Expose + */ + +module.exports = URI;