/** * uri.js - bitcoin uri parsing for bcoin * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; var util = require('../utils/util'); var Address = require('../primitives/address'); var Amount = require('./amount'); var assert = require('assert'); /** * Represents a bitcoin URI. * @alias module:btc.URI * @constructor * @param {Object|String} options * @property {Address} address * @property {Amount} amount * @property {String|null} label * @property {String|null} message * @property {String|null} request */ function URI(options) { if (!(this instanceof URI)) return new URI(options); this.address = new Address(); this.amount = -1; this.label = null; this.message = null; this.request = null; if (options) this.fromOptions(options); } /** * Inject properties from options object. * @private * @param {Object|String} options * @returns {URI} */ URI.prototype.fromOptions = function fromOptions(options) { if (typeof options === 'string') return this.fromString(options); if (options.address) this.address.fromOptions(options.address); if (options.amount != null) { assert(util.isUInt53(options.amount), 'Amount must be a uint53.'); this.amount = options.amount; } if (options.label) { assert(typeof options.label === 'string', 'Label must be a string.'); this.label = options.label; } if (options.message) { assert(typeof options.message === 'string', 'Message must be a string.'); this.message = options.message; } if (options.request) { assert(typeof options.request === 'string', 'Request must be a string.'); this.request = options.request; } return this; }; /** * Instantiate URI from options. * @param {Object|String} options * @returns {URI} */ URI.fromOptions = function fromOptions(options) { return new URI().fromOptions(options); }; /** * Parse and inject properties from string. * @private * @param {String} str * @returns {URI} */ URI.prototype.fromString = function fromString(str) { var prefix, index, query, address; assert(typeof str === 'string'); assert(str.length > 8, 'Not a bitcoin URI.'); prefix = str.substring(0, 8); assert(prefix === 'bitcoin:', '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) { assert(query.amount.length > 0, 'Value is empty.'); assert(query.amount[0] !== '-', 'Value is negative.'); this.amount = Amount.value(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; }; /** * Instantiate uri from string. * @param {String} str * @returns {URI} */ URI.fromString = function fromString(str) { return new URI().fromString(str); }; /** * Serialize uri to a string. * @returns {String} */ URI.prototype.toString = function toString() { var str = 'bitcoin:'; var query = []; str += this.address.toBase58(); if (this.amount !== -1) query.push('amount=' + Amount.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; }; /** * Inspect bitcoin uri. * @returns {String} */ URI.prototype.inspect = function inspect() { return ''; }; /* * Helpers */ function BitcoinQuery() { this.amount = null; this.label = null; this.message = null; this.r = null; } function parsePairs(str) { var parts = str.split('&'); var data = new BitcoinQuery(); var size = 0; var i, index, pair, key, value; for (i = 0; i < parts.length; i++) { pair = parts[i]; index = pair.indexOf('='); if (index === -1) { key = pair; value = ''; } else { key = pair.substring(0, index); value = pair.substring(index + 1); } if (key.length === 0) { assert(value.length === 0, 'Empty key in querystring.'); continue; } assert(size < 4, 'Too many keys in querystring.'); switch (key) { case 'amount': assert(data.amount == null, 'Duplicate key in querystring (amount).'); data.amount = unescape(value); break; case 'label': assert(data.label == null, 'Duplicate key in querystring (label).'); data.label = unescape(value); break; case 'message': assert(data.message == null, 'Duplicate key in querystring (message).'); data.message = unescape(value); break; case 'r': assert(data.r == null, 'Duplicate key in querystring (r).'); data.r = unescape(value); break; default: assert(false, 'Unknown querystring key: ' + value); break; } size++; } return data; } function unescape(str) { str = decodeURIComponent(str); str = str.replace(/\+/g, ' '); str = str.replace(/\0/g, ''); return str; } function escape(str) { str = encodeURIComponent(str); str = str.replace(/%20/g, '+'); return str; } /* * Expose */ module.exports = URI;