'use strict'; const GLib = imports.gi.GLib; /** * The same regular expression used in GNOME Shell * * http://daringfireball.net/2010/07/improved_regex_for_matching_urls */ const _balancedParens = '\\((?:[^\\s()<>]+|(?:\\(?:[^\\s()<>]+\\)))*\\)'; const _leadingJunk = '[\\s`(\\[{\'\\"<\u00AB\u201C\u2018]'; const _notTrailingJunk = '[^\\s`!()\\[\\]{};:\'\\".,<>?\u00AB\u00BB\u201C\u201D\u2018\u2019]'; const _urlRegexp = new RegExp( '(^|' + _leadingJunk + ')' + '(' + '(?:' + '(?:http|https)://' + // scheme:// '|' + 'www\\d{0,3}[.]' + // www. '|' + '[a-z0-9.\\-]+[.][a-z]{2,4}/' + // foo.xx/ ')' + '(?:' + // one or more: '[^\\s()<>]+' + // run of non-space non-() '|' + // or _balancedParens + // balanced parens ')+' + '(?:' + // end with: _balancedParens + // balanced parens '|' + // or _notTrailingJunk + // last non-junk char ')' + ')', 'gi'); /** * sms/tel URI RegExp (https://tools.ietf.org/html/rfc5724) * * A fairly lenient regexp for sms: URIs that allows tel: numbers with chars * from global-number, local-number (without phone-context) and single spaces. * This allows passing numbers directly from libfolks or GData without * pre-processing. It also makes an allowance for URIs passed from Gio.File * that always come in the form "sms:///". */ let _smsParam = "[\\w.!~*'()-]+=(?:[\\w.!~*'()-]|%[0-9A-F]{2})*"; let _telParam = ";[a-zA-Z0-9-]+=(?:[\\w\\[\\]/:&+$.!~*'()-]|%[0-9A-F]{2})+"; let _lenientDigits = '[+]?(?:[0-9A-F*#().-]| (?! )|%20(?!%20))+'; let _lenientNumber = _lenientDigits + '(?:' + _telParam + ')*'; var _smsRegex = new RegExp( '^' + 'sms:' + // scheme '(?:[/]{2,3})?' + // Gio.File returns ":///" '(' + // one or more... _lenientNumber + // phone numbers '(?:,' + _lenientNumber + ')*' + // separated by commas ')' + '(?:\\?(' + // followed by optional... _smsParam + // parameters... '(?:&' + _smsParam + ')*' + // separated by "&" (unescaped) '))?' + '$', 'g'); // fragments (#foo) not allowed var _numberRegex = new RegExp( '^' + '(' + _lenientDigits + ')' + // phone number digits '((?:' + _telParam + ')*)' + // followed by optional parameters '$', 'g'); /** * Searches @str for URLs and returns an array of objects with %url * properties showing the matched URL string, and %pos properties indicating * the position within @str where the URL was found. * * @param {string} str - the string to search * @returns {object[]} - the list of match objects, as described above */ function findUrls(str) { _urlRegexp.lastIndex = 0; let res = [], match; while ((match = _urlRegexp.exec(str))) { let name = match[2]; let url = GLib.uri_parse_scheme(name) ? name : `http://${name}`; res.push({name, url, pos: match.index + match[1].length}); } return res; } /** * Return a string with URLs couched in tags, parseable by Pango and * using the same RegExp as GNOME Shell. * * @param {string} str - The string to be modified * @param {string} [title] - An optional title (eg. alt text, tooltip) * @return {string} - the modified text */ function linkify(str, title = null) { let text = GLib.markup_escape_text(str, -1); _urlRegexp.lastIndex = 0; if (title) { return text.replace( _urlRegexp, `$1$2` ); } else { return text.replace(_urlRegexp, '$1$2'); } } /** * A simple parsing class for sms: URI's (https://tools.ietf.org/html/rfc5724) */ var SmsURI = class URI { constructor(uri) { _smsRegex.lastIndex = 0; let [, recipients, query] = _smsRegex.exec(uri); this.recipients = recipients.split(',').map(recipient => { _numberRegex.lastIndex = 0; let [, number, params] = _numberRegex.exec(recipient); if (params) { for (let param of params.substr(1).split(';')) { let [key, value] = param.split('='); // add phone-context to beginning of if (key === 'phone-context' && value.startsWith('+')) { return value + unescape(number); } } } return unescape(number); }); if (query) { for (let field of query.split('&')) { let [key, value] = field.split('='); if (key === 'body') { if (this.body) { throw URIError('duplicate "body" field'); } this.body = (value) ? decodeURIComponent(value) : undefined; } } } } toString() { let uri = 'sms:' + this.recipients.join(','); return (this.body) ? uri + '?body=' + escape(this.body) : uri; } };