URLToolkit

Lightweight library to build an absolute URL from a base URL and a relative URL, written from the spec (RFC 1808)

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/382648/695476/URLToolkit.js

  1. // ==UserScript==
  2. // @name URLToolkit
  3. // @namespace https://greasyfork.org/users/136230
  4. // @description Lightweight library to build an absolute URL from a base URL and a relative URL, written from the spec (RFC 1808)
  5. // @include *://*
  6. // @version 2.1.6
  7. // @run-at document-start
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11.  
  12. // see https://tools.ietf.org/html/rfc1808
  13.  
  14. /* jshint ignore:start */
  15. (function(root) {
  16. /* jshint ignore:end */
  17.  
  18. var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
  19. var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
  20. var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
  21. var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
  22.  
  23. var URLToolkit = { // jshint ignore:line
  24. // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
  25. // E.g
  26. // With opts.alwaysNormalize = false (default, spec compliant)
  27. // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
  28. // With opts.alwaysNormalize = true (not spec compliant)
  29. // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
  30. buildAbsoluteURL: function(baseURL, relativeURL, opts) {
  31. opts = opts || {};
  32. // remove any remaining space and CRLF
  33. baseURL = baseURL.trim();
  34. relativeURL = relativeURL.trim();
  35. if (!relativeURL) {
  36. // 2a) If the embedded URL is entirely empty, it inherits the
  37. // entire base URL (i.e., is set equal to the base URL)
  38. // and we are done.
  39. if (!opts.alwaysNormalize) {
  40. return baseURL;
  41. }
  42. var basePartsForNormalise = URLToolkit.parseURL(baseURL);
  43. if (!basePartsForNormalise) {
  44. throw new Error('Error trying to parse base URL.');
  45. }
  46. basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
  47. return URLToolkit.buildURLFromParts(basePartsForNormalise);
  48. }
  49. var relativeParts = URLToolkit.parseURL(relativeURL);
  50. if (!relativeParts) {
  51. throw new Error('Error trying to parse relative URL.');
  52. }
  53. if (relativeParts.scheme) {
  54. // 2b) If the embedded URL starts with a scheme name, it is
  55. // interpreted as an absolute URL and we are done.
  56. if (!opts.alwaysNormalize) {
  57. return relativeURL;
  58. }
  59. relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
  60. return URLToolkit.buildURLFromParts(relativeParts);
  61. }
  62. var baseParts = URLToolkit.parseURL(baseURL);
  63. if (!baseParts) {
  64. throw new Error('Error trying to parse base URL.');
  65. }
  66. if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
  67. // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
  68. // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
  69. var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
  70. baseParts.netLoc = pathParts[1];
  71. baseParts.path = pathParts[2];
  72. }
  73. if (baseParts.netLoc && !baseParts.path) {
  74. baseParts.path = '/';
  75. }
  76. var builtParts = {
  77. // 2c) Otherwise, the embedded URL inherits the scheme of
  78. // the base URL.
  79. scheme: baseParts.scheme,
  80. netLoc: relativeParts.netLoc,
  81. path: null,
  82. params: relativeParts.params,
  83. query: relativeParts.query,
  84. fragment: relativeParts.fragment
  85. };
  86. if (!relativeParts.netLoc) {
  87. // 3) If the embedded URL's <net_loc> is non-empty, we skip to
  88. // Step 7. Otherwise, the embedded URL inherits the <net_loc>
  89. // (if any) of the base URL.
  90. builtParts.netLoc = baseParts.netLoc;
  91. // 4) If the embedded URL path is preceded by a slash "/", the
  92. // path is not relative and we skip to Step 7.
  93. if (relativeParts.path[0] !== '/') {
  94. if (!relativeParts.path) {
  95. // 5) If the embedded URL path is empty (and not preceded by a
  96. // slash), then the embedded URL inherits the base URL path
  97. builtParts.path = baseParts.path;
  98. // 5a) if the embedded URL's <params> is non-empty, we skip to
  99. // step 7; otherwise, it inherits the <params> of the base
  100. // URL (if any) and
  101. if (!relativeParts.params) {
  102. builtParts.params = baseParts.params;
  103. // 5b) if the embedded URL's <query> is non-empty, we skip to
  104. // step 7; otherwise, it inherits the <query> of the base
  105. // URL (if any) and we skip to step 7.
  106. if (!relativeParts.query) {
  107. builtParts.query = baseParts.query;
  108. }
  109. }
  110. } else {
  111. // 6) The last segment of the base URL's path (anything
  112. // following the rightmost slash "/", or the entire path if no
  113. // slash is present) is removed and the embedded URL's path is
  114. // appended in its place.
  115. var baseURLPath = baseParts.path;
  116. var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
  117. builtParts.path = URLToolkit.normalizePath(newPath);
  118. }
  119. }
  120. }
  121. if (builtParts.path === null) {
  122. builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
  123. }
  124. return URLToolkit.buildURLFromParts(builtParts);
  125. },
  126. parseURL: function(url) {
  127. var parts = URL_REGEX.exec(url);
  128. if (!parts) {
  129. return null;
  130. }
  131. return {
  132. scheme: parts[1] || '',
  133. netLoc: parts[2] || '',
  134. path: parts[3] || '',
  135. params: parts[4] || '',
  136. query: parts[5] || '',
  137. fragment: parts[6] || ''
  138. };
  139. },
  140. normalizePath: function(path) {
  141. // The following operations are
  142. // then applied, in order, to the new path:
  143. // 6a) All occurrences of "./", where "." is a complete path
  144. // segment, are removed.
  145. // 6b) If the path ends with "." as a complete path segment,
  146. // that "." is removed.
  147. path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, '');
  148. // 6c) All occurrences of "<segment>/../", where <segment> is a
  149. // complete path segment not equal to "..", are removed.
  150. // Removal of these path segments is performed iteratively,
  151. // removing the leftmost matching pattern on each iteration,
  152. // until no matching pattern remains.
  153. // 6d) If the path ends with "<segment>/..", where <segment> is a
  154. // complete path segment not equal to "..", that
  155. // "<segment>/.." is removed.
  156. while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
  157. return path.split('').reverse().join('');
  158. },
  159. buildURLFromParts: function(parts) {
  160. return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
  161. }
  162. };
  163.  
  164. /* jshint ignore:start */
  165. root["URLToolkit"] = URLToolkit;
  166. })(window);
  167. /* jshint ignore:end */