uri.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
  2. //
  3. // SPDX-License-Identifier: GPL-2.0-or-later
  4. 'use strict';
  5. const GLib = imports.gi.GLib;
  6. /**
  7. * The same regular expression used in GNOME Shell
  8. *
  9. * http://daringfireball.net/2010/07/improved_regex_for_matching_urls
  10. */
  11. const _balancedParens = '\\((?:[^\\s()<>]+|(?:\\(?:[^\\s()<>]+\\)))*\\)';
  12. const _leadingJunk = '[\\s`(\\[{\'\\"<\u00AB\u201C\u2018]';
  13. const _notTrailingJunk = '[^\\s`!()\\[\\]{};:\'\\".,<>?\u00AB\u00BB\u201C\u201D\u2018\u2019]';
  14. const _urlRegexp = new RegExp(
  15. '(^|' + _leadingJunk + ')' +
  16. '(' +
  17. '(?:' +
  18. '(?:http|https)://' + // scheme://
  19. '|' +
  20. 'www\\d{0,3}[.]' + // www.
  21. '|' +
  22. '[a-z0-9.\\-]+[.][a-z]{2,4}/' + // foo.xx/
  23. ')' +
  24. '(?:' + // one or more:
  25. '[^\\s()<>]+' + // run of non-space non-()
  26. '|' + // or
  27. _balancedParens + // balanced parens
  28. ')+' +
  29. '(?:' + // end with:
  30. _balancedParens + // balanced parens
  31. '|' + // or
  32. _notTrailingJunk + // last non-junk char
  33. ')' +
  34. ')', 'gi');
  35. /**
  36. * sms/tel URI RegExp (https://tools.ietf.org/html/rfc5724)
  37. *
  38. * A fairly lenient regexp for sms: URIs that allows tel: numbers with chars
  39. * from global-number, local-number (without phone-context) and single spaces.
  40. * This allows passing numbers directly from libfolks or GData without
  41. * pre-processing. It also makes an allowance for URIs passed from Gio.File
  42. * that always come in the form "sms:///".
  43. */
  44. const _smsParam = "[\\w.!~*'()-]+=(?:[\\w.!~*'()-]|%[0-9A-F]{2})*";
  45. const _telParam = ";[a-zA-Z0-9-]+=(?:[\\w\\[\\]/:&+$.!~*'()-]|%[0-9A-F]{2})+";
  46. const _lenientDigits = '[+]?(?:[0-9A-F*#().-]| (?! )|%20(?!%20))+';
  47. const _lenientNumber = `${_lenientDigits}(?:${_telParam})*`;
  48. const _smsRegex = new RegExp(
  49. '^' +
  50. 'sms:' + // scheme
  51. '(?:[/]{2,3})?' + // Gio.File returns ":///"
  52. '(' + // one or more...
  53. _lenientNumber + // phone numbers
  54. '(?:,' + _lenientNumber + ')*' + // separated by commas
  55. ')' +
  56. '(?:\\?(' + // followed by optional...
  57. _smsParam + // parameters...
  58. '(?:&' + _smsParam + ')*' + // separated by "&" (unescaped)
  59. '))?' +
  60. '$', 'g'); // fragments (#foo) not allowed
  61. const _numberRegex = new RegExp(
  62. '^' +
  63. '(' + _lenientDigits + ')' + // phone number digits
  64. '((?:' + _telParam + ')*)' + // followed by optional parameters
  65. '$', 'g');
  66. /**
  67. * Searches @str for URLs and returns an array of objects with %url
  68. * properties showing the matched URL string, and %pos properties indicating
  69. * the position within @str where the URL was found.
  70. *
  71. * @param {string} str - the string to search
  72. * @return {Object[]} the list of match objects, as described above
  73. */
  74. function findUrls(str) {
  75. _urlRegexp.lastIndex = 0;
  76. const res = [];
  77. let match;
  78. while ((match = _urlRegexp.exec(str))) {
  79. const name = match[2];
  80. const url = GLib.uri_parse_scheme(name) ? name : `http://${name}`;
  81. res.push({name, url, pos: match.index + match[1].length});
  82. }
  83. return res;
  84. }
  85. /**
  86. * Return a string with URLs couched in <a> tags, parseable by Pango and
  87. * using the same RegExp as GNOME Shell.
  88. *
  89. * @param {string} str - The string to be modified
  90. * @param {string} [title] - An optional title (eg. alt text, tooltip)
  91. * @return {string} the modified text
  92. */
  93. function linkify(str, title = null) {
  94. const text = GLib.markup_escape_text(str, -1);
  95. _urlRegexp.lastIndex = 0;
  96. if (title) {
  97. return text.replace(
  98. _urlRegexp,
  99. `$1<a href="$2" title="${title}">$2</a>`
  100. );
  101. } else {
  102. return text.replace(_urlRegexp, '$1<a href="$2">$2</a>');
  103. }
  104. }
  105. /**
  106. * A simple parsing class for sms: URI's (https://tools.ietf.org/html/rfc5724)
  107. */
  108. var SmsURI = class URI {
  109. constructor(uri) {
  110. _smsRegex.lastIndex = 0;
  111. const [, recipients, query] = _smsRegex.exec(uri);
  112. this.recipients = recipients.split(',').map(recipient => {
  113. _numberRegex.lastIndex = 0;
  114. const [, number, params] = _numberRegex.exec(recipient);
  115. if (params) {
  116. for (const param of params.substr(1).split(';')) {
  117. const [key, value] = param.split('=');
  118. // add phone-context to beginning of
  119. if (key === 'phone-context' && value.startsWith('+'))
  120. return value + unescape(number);
  121. }
  122. }
  123. return unescape(number);
  124. });
  125. if (query) {
  126. for (const field of query.split('&')) {
  127. const [key, value] = field.split('=');
  128. if (key === 'body') {
  129. if (this.body)
  130. throw URIError('duplicate "body" field');
  131. this.body = value ? decodeURIComponent(value) : undefined;
  132. }
  133. }
  134. }
  135. }
  136. toString() {
  137. const uri = `sms:${this.recipients.join(',')}`;
  138. return this.body ? `${uri}?body=${escape(this.body)}` : uri;
  139. }
  140. };