uri.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. /**
  2. * URI.js
  3. *
  4. * @fileoverview An RFC 3986 compliant, scheme extendable URI parsing/validating/resolving library for JavaScript.
  5. * @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
  6. * @see http://github.com/garycourt/uri-js
  7. */
  8. /**
  9. * Copyright 2011 Gary Court. All rights reserved.
  10. *
  11. * Redistribution and use in source and binary forms, with or without modification, are
  12. * permitted provided that the following conditions are met:
  13. *
  14. * 1. Redistributions of source code must retain the above copyright notice, this list of
  15. * conditions and the following disclaimer.
  16. *
  17. * 2. Redistributions in binary form must reproduce the above copyright notice, this list
  18. * of conditions and the following disclaimer in the documentation and/or other materials
  19. * provided with the distribution.
  20. *
  21. * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED
  22. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  23. * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR
  24. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  26. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  27. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  29. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. *
  31. * The views and conclusions contained in the software and documentation are those of the
  32. * authors and should not be interpreted as representing official policies, either expressed
  33. * or implied, of Gary Court.
  34. */
  35. import URI_PROTOCOL from "./regexps-uri";
  36. import IRI_PROTOCOL from "./regexps-iri";
  37. import punycode from "punycode";
  38. import { toUpperCase, typeOf, assign } from "./util";
  39. export interface URIComponents {
  40. scheme?:string;
  41. userinfo?:string;
  42. host?:string;
  43. port?:number|string;
  44. path?:string;
  45. query?:string;
  46. fragment?:string;
  47. reference?:string;
  48. error?:string;
  49. }
  50. export interface URIOptions {
  51. scheme?:string;
  52. reference?:string;
  53. tolerant?:boolean;
  54. absolutePath?:boolean;
  55. iri?:boolean;
  56. unicodeSupport?:boolean;
  57. domainHost?:boolean;
  58. }
  59. export interface URISchemeHandler<Components extends URIComponents = URIComponents, Options extends URIOptions = URIOptions, ParentComponents extends URIComponents = URIComponents> {
  60. scheme:string;
  61. parse(components:ParentComponents, options:Options):Components;
  62. serialize(components:Components, options:Options):ParentComponents;
  63. unicodeSupport?:boolean;
  64. domainHost?:boolean;
  65. absolutePath?:boolean;
  66. }
  67. export interface URIRegExps {
  68. NOT_SCHEME : RegExp,
  69. NOT_USERINFO : RegExp,
  70. NOT_HOST : RegExp,
  71. NOT_PATH : RegExp,
  72. NOT_PATH_NOSCHEME : RegExp,
  73. NOT_QUERY : RegExp,
  74. NOT_FRAGMENT : RegExp,
  75. ESCAPE : RegExp,
  76. UNRESERVED : RegExp,
  77. OTHER_CHARS : RegExp,
  78. PCT_ENCODED : RegExp,
  79. IPV4ADDRESS : RegExp,
  80. IPV6ADDRESS : RegExp,
  81. }
  82. export const SCHEMES:{[scheme:string]:URISchemeHandler} = {};
  83. export function pctEncChar(chr:string):string {
  84. const c = chr.charCodeAt(0);
  85. let e:string;
  86. if (c < 16) e = "%0" + c.toString(16).toUpperCase();
  87. else if (c < 128) e = "%" + c.toString(16).toUpperCase();
  88. else if (c < 2048) e = "%" + ((c >> 6) | 192).toString(16).toUpperCase() + "%" + ((c & 63) | 128).toString(16).toUpperCase();
  89. else e = "%" + ((c >> 12) | 224).toString(16).toUpperCase() + "%" + (((c >> 6) & 63) | 128).toString(16).toUpperCase() + "%" + ((c & 63) | 128).toString(16).toUpperCase();
  90. return e;
  91. }
  92. export function pctDecChars(str:string):string {
  93. let newStr = "";
  94. let i = 0;
  95. const il = str.length;
  96. while (i < il) {
  97. const c = parseInt(str.substr(i + 1, 2), 16);
  98. if (c < 128) {
  99. newStr += String.fromCharCode(c);
  100. i += 3;
  101. }
  102. else if (c >= 194 && c < 224) {
  103. if ((il - i) >= 6) {
  104. const c2 = parseInt(str.substr(i + 4, 2), 16);
  105. newStr += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
  106. } else {
  107. newStr += str.substr(i, 6);
  108. }
  109. i += 6;
  110. }
  111. else if (c >= 224) {
  112. if ((il - i) >= 9) {
  113. const c2 = parseInt(str.substr(i + 4, 2), 16);
  114. const c3 = parseInt(str.substr(i + 7, 2), 16);
  115. newStr += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
  116. } else {
  117. newStr += str.substr(i, 9);
  118. }
  119. i += 9;
  120. }
  121. else {
  122. newStr += str.substr(i, 3);
  123. i += 3;
  124. }
  125. }
  126. return newStr;
  127. }
  128. function _normalizeComponentEncoding(components:URIComponents, protocol:URIRegExps) {
  129. function decodeUnreserved(str:string):string {
  130. const decStr = pctDecChars(str);
  131. return (!decStr.match(protocol.UNRESERVED) ? str : decStr);
  132. }
  133. if (components.scheme) components.scheme = String(components.scheme).replace(protocol.PCT_ENCODED, decodeUnreserved).toLowerCase().replace(protocol.NOT_SCHEME, "");
  134. if (components.userinfo !== undefined) components.userinfo = String(components.userinfo).replace(protocol.PCT_ENCODED, decodeUnreserved).replace(protocol.NOT_USERINFO, pctEncChar).replace(protocol.PCT_ENCODED, toUpperCase);
  135. if (components.host !== undefined) components.host = String(components.host).replace(protocol.PCT_ENCODED, decodeUnreserved).toLowerCase().replace(protocol.NOT_HOST, pctEncChar).replace(protocol.PCT_ENCODED, toUpperCase);
  136. if (components.path !== undefined) components.path = String(components.path).replace(protocol.PCT_ENCODED, decodeUnreserved).replace((components.scheme ? protocol.NOT_PATH : protocol.NOT_PATH_NOSCHEME), pctEncChar).replace(protocol.PCT_ENCODED, toUpperCase);
  137. if (components.query !== undefined) components.query = String(components.query).replace(protocol.PCT_ENCODED, decodeUnreserved).replace(protocol.NOT_QUERY, pctEncChar).replace(protocol.PCT_ENCODED, toUpperCase);
  138. if (components.fragment !== undefined) components.fragment = String(components.fragment).replace(protocol.PCT_ENCODED, decodeUnreserved).replace(protocol.NOT_FRAGMENT, pctEncChar).replace(protocol.PCT_ENCODED, toUpperCase);
  139. return components;
  140. };
  141. function _stripLeadingZeros(str:string):string {
  142. return str.replace(/^0*(.*)/, "$1") || "0";
  143. }
  144. function _normalizeIPv4(host:string, protocol:URIRegExps):string {
  145. const matches = host.match(protocol.IPV4ADDRESS) || [];
  146. const [, address] = matches;
  147. if (address) {
  148. return address.split(".").map(_stripLeadingZeros).join(".");
  149. } else {
  150. return host;
  151. }
  152. }
  153. function _normalizeIPv6(host:string, protocol:URIRegExps):string {
  154. const matches = host.match(protocol.IPV6ADDRESS) || [];
  155. const [, address, zone] = matches;
  156. if (address) {
  157. const [last, first] = address.toLowerCase().split('::').reverse();
  158. const firstFields = first ? first.split(":").map(_stripLeadingZeros) : [];
  159. const lastFields = last.split(":").map(_stripLeadingZeros);
  160. const isLastFieldIPv4Address = protocol.IPV4ADDRESS.test(lastFields[lastFields.length - 1]);
  161. const fieldCount = isLastFieldIPv4Address ? 7 : 8;
  162. const lastFieldsStart = lastFields.length - fieldCount;
  163. const fields = Array<string>(fieldCount);
  164. for (let x = 0; x < fieldCount; ++x) {
  165. fields[x] = firstFields[x] || lastFields[lastFieldsStart + x] || '';
  166. }
  167. if (isLastFieldIPv4Address) {
  168. fields[fieldCount - 1] = _normalizeIPv4(fields[fieldCount - 1], protocol);
  169. }
  170. const allZeroFields = fields.reduce<Array<{index:number,length:number}>>((acc, field, index) => {
  171. if (!field || field === "0") {
  172. const lastLongest = acc[acc.length - 1];
  173. if (lastLongest && lastLongest.index + lastLongest.length === index) {
  174. lastLongest.length++;
  175. } else {
  176. acc.push({ index, length : 1 });
  177. }
  178. }
  179. return acc;
  180. }, []);
  181. const longestZeroFields = allZeroFields.sort((a, b) => b.length - a.length)[0];
  182. let newHost:string;
  183. if (longestZeroFields && longestZeroFields.length > 1) {
  184. const newFirst = fields.slice(0, longestZeroFields.index) ;
  185. const newLast = fields.slice(longestZeroFields.index + longestZeroFields.length);
  186. newHost = newFirst.join(":") + "::" + newLast.join(":");
  187. } else {
  188. newHost = fields.join(":");
  189. }
  190. if (zone) {
  191. newHost += "%" + zone;
  192. }
  193. return newHost;
  194. } else {
  195. return host;
  196. }
  197. }
  198. const URI_PARSE = /^(?:([^:\/?#]+):)?(?:\/\/((?:([^\/?#@]*)@)?(\[[^\/?#\]]+\]|[^\/?#:]*)(?:\:(\d*))?))?([^?#]*)(?:\?([^#]*))?(?:#((?:.|\n|\r)*))?/i;
  199. const NO_MATCH_IS_UNDEFINED = (<RegExpMatchArray>("").match(/(){0}/))[1] === undefined;
  200. export function parse(uriString:string, options:URIOptions = {}):URIComponents {
  201. const components:URIComponents = {};
  202. const protocol = (options.iri !== false ? IRI_PROTOCOL : URI_PROTOCOL);
  203. if (options.reference === "suffix") uriString = (options.scheme ? options.scheme + ":" : "") + "//" + uriString;
  204. const matches = uriString.match(URI_PARSE);
  205. if (matches) {
  206. if (NO_MATCH_IS_UNDEFINED) {
  207. //store each component
  208. components.scheme = matches[1];
  209. components.userinfo = matches[3];
  210. components.host = matches[4];
  211. components.port = parseInt(matches[5], 10);
  212. components.path = matches[6] || "";
  213. components.query = matches[7];
  214. components.fragment = matches[8];
  215. //fix port number
  216. if (isNaN(components.port)) {
  217. components.port = matches[5];
  218. }
  219. } else { //IE FIX for improper RegExp matching
  220. //store each component
  221. components.scheme = matches[1] || undefined;
  222. components.userinfo = (uriString.indexOf("@") !== -1 ? matches[3] : undefined);
  223. components.host = (uriString.indexOf("//") !== -1 ? matches[4] : undefined);
  224. components.port = parseInt(matches[5], 10);
  225. components.path = matches[6] || "";
  226. components.query = (uriString.indexOf("?") !== -1 ? matches[7] : undefined);
  227. components.fragment = (uriString.indexOf("#") !== -1 ? matches[8] : undefined);
  228. //fix port number
  229. if (isNaN(components.port)) {
  230. components.port = (uriString.match(/\/\/(?:.|\n)*\:(?:\/|\?|\#|$)/) ? matches[4] : undefined);
  231. }
  232. }
  233. if (components.host) {
  234. //normalize IP hosts
  235. components.host = _normalizeIPv6(_normalizeIPv4(components.host, protocol), protocol);
  236. }
  237. //determine reference type
  238. if (components.scheme === undefined && components.userinfo === undefined && components.host === undefined && components.port === undefined && !components.path && components.query === undefined) {
  239. components.reference = "same-document";
  240. } else if (components.scheme === undefined) {
  241. components.reference = "relative";
  242. } else if (components.fragment === undefined) {
  243. components.reference = "absolute";
  244. } else {
  245. components.reference = "uri";
  246. }
  247. //check for reference errors
  248. if (options.reference && options.reference !== "suffix" && options.reference !== components.reference) {
  249. components.error = components.error || "URI is not a " + options.reference + " reference.";
  250. }
  251. //find scheme handler
  252. const schemeHandler = SCHEMES[(options.scheme || components.scheme || "").toLowerCase()];
  253. //check if scheme can't handle IRIs
  254. if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
  255. //if host component is a domain name
  256. if (components.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost))) {
  257. //convert Unicode IDN -> ASCII IDN
  258. try {
  259. components.host = punycode.toASCII(components.host.replace(protocol.PCT_ENCODED, pctDecChars).toLowerCase());
  260. } catch (e) {
  261. components.error = components.error || "Host's domain name can not be converted to ASCII via punycode: " + e;
  262. }
  263. }
  264. //convert IRI -> URI
  265. _normalizeComponentEncoding(components, URI_PROTOCOL);
  266. } else {
  267. //normalize encodings
  268. _normalizeComponentEncoding(components, protocol);
  269. }
  270. //perform scheme specific parsing
  271. if (schemeHandler && schemeHandler.parse) {
  272. schemeHandler.parse(components, options);
  273. }
  274. } else {
  275. components.error = components.error || "URI can not be parsed.";
  276. }
  277. return components;
  278. };
  279. function _recomposeAuthority(components:URIComponents, options:URIOptions):string|undefined {
  280. const protocol = (options.iri !== false ? IRI_PROTOCOL : URI_PROTOCOL);
  281. const uriTokens:Array<string> = [];
  282. if (components.userinfo !== undefined) {
  283. uriTokens.push(components.userinfo);
  284. uriTokens.push("@");
  285. }
  286. if (components.host !== undefined) {
  287. //normalize IP hosts, add brackets and escape zone separator for IPv6
  288. uriTokens.push(_normalizeIPv6(_normalizeIPv4(String(components.host), protocol), protocol).replace(protocol.IPV6ADDRESS, (_, $1, $2) => "[" + $1 + ($2 ? "%25" + $2 : "") + "]"));
  289. }
  290. if (typeof components.port === "number") {
  291. uriTokens.push(":");
  292. uriTokens.push(components.port.toString(10));
  293. }
  294. return uriTokens.length ? uriTokens.join("") : undefined;
  295. };
  296. const RDS1 = /^\.\.?\//;
  297. const RDS2 = /^\/\.(\/|$)/;
  298. const RDS3 = /^\/\.\.(\/|$)/;
  299. const RDS4 = /^\.\.?$/;
  300. const RDS5 = /^\/?(?:.|\n)*?(?=\/|$)/;
  301. export function removeDotSegments(input:string):string {
  302. const output:Array<string> = [];
  303. while (input.length) {
  304. if (input.match(RDS1)) {
  305. input = input.replace(RDS1, "");
  306. } else if (input.match(RDS2)) {
  307. input = input.replace(RDS2, "/");
  308. } else if (input.match(RDS3)) {
  309. input = input.replace(RDS3, "/");
  310. output.pop();
  311. } else if (input === "." || input === "..") {
  312. input = "";
  313. } else {
  314. const im = input.match(RDS5);
  315. if (im) {
  316. const s = im[0];
  317. input = input.slice(s.length);
  318. output.push(s);
  319. } else {
  320. throw new Error("Unexpected dot segment condition");
  321. }
  322. }
  323. }
  324. return output.join("");
  325. };
  326. export function serialize(components:URIComponents, options:URIOptions = {}):string {
  327. const protocol = (options.iri ? IRI_PROTOCOL : URI_PROTOCOL);
  328. const uriTokens:Array<string> = [];
  329. //find scheme handler
  330. const schemeHandler = SCHEMES[(options.scheme || components.scheme || "").toLowerCase()];
  331. //perform scheme specific serialization
  332. if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(components, options);
  333. if (components.host) {
  334. //if host component is an IPv6 address
  335. if (protocol.IPV6ADDRESS.test(components.host)) {
  336. //TODO: normalize IPv6 address as per RFC 5952
  337. }
  338. //if host component is a domain name
  339. else if (options.domainHost || (schemeHandler && schemeHandler.domainHost)) {
  340. //convert IDN via punycode
  341. try {
  342. components.host = (!options.iri ? punycode.toASCII(components.host.replace(protocol.PCT_ENCODED, pctDecChars).toLowerCase()) : punycode.toUnicode(components.host));
  343. } catch (e) {
  344. components.error = components.error || "Host's domain name can not be converted to " + (!options.iri ? "ASCII" : "Unicode") + " via punycode: " + e;
  345. }
  346. }
  347. }
  348. //normalize encoding
  349. _normalizeComponentEncoding(components, protocol);
  350. if (options.reference !== "suffix" && components.scheme) {
  351. uriTokens.push(components.scheme);
  352. uriTokens.push(":");
  353. }
  354. const authority = _recomposeAuthority(components, options);
  355. if (authority !== undefined) {
  356. if (options.reference !== "suffix") {
  357. uriTokens.push("//");
  358. }
  359. uriTokens.push(authority);
  360. if (components.path && components.path.charAt(0) !== "/") {
  361. uriTokens.push("/");
  362. }
  363. }
  364. if (components.path !== undefined) {
  365. let s = components.path;
  366. if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
  367. s = removeDotSegments(s);
  368. }
  369. if (authority === undefined) {
  370. s = s.replace(/^\/\//, "/%2F"); //don't allow the path to start with "//"
  371. }
  372. uriTokens.push(s);
  373. }
  374. if (components.query !== undefined) {
  375. uriTokens.push("?");
  376. uriTokens.push(components.query);
  377. }
  378. if (components.fragment !== undefined) {
  379. uriTokens.push("#");
  380. uriTokens.push(components.fragment);
  381. }
  382. return uriTokens.join(""); //merge tokens into a string
  383. };
  384. export function resolveComponents(base:URIComponents, relative:URIComponents, options:URIOptions = {}, skipNormalization?:boolean):URIComponents {
  385. const target:URIComponents = {};
  386. if (!skipNormalization) {
  387. base = parse(serialize(base, options), options); //normalize base components
  388. relative = parse(serialize(relative, options), options); //normalize relative components
  389. }
  390. options = options || {};
  391. if (!options.tolerant && relative.scheme) {
  392. target.scheme = relative.scheme;
  393. //target.authority = relative.authority;
  394. target.userinfo = relative.userinfo;
  395. target.host = relative.host;
  396. target.port = relative.port;
  397. target.path = removeDotSegments(relative.path || "");
  398. target.query = relative.query;
  399. } else {
  400. if (relative.userinfo !== undefined || relative.host !== undefined || relative.port !== undefined) {
  401. //target.authority = relative.authority;
  402. target.userinfo = relative.userinfo;
  403. target.host = relative.host;
  404. target.port = relative.port;
  405. target.path = removeDotSegments(relative.path || "");
  406. target.query = relative.query;
  407. } else {
  408. if (!relative.path) {
  409. target.path = base.path;
  410. if (relative.query !== undefined) {
  411. target.query = relative.query;
  412. } else {
  413. target.query = base.query;
  414. }
  415. } else {
  416. if (relative.path.charAt(0) === "/") {
  417. target.path = removeDotSegments(relative.path);
  418. } else {
  419. if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) {
  420. target.path = "/" + relative.path;
  421. } else if (!base.path) {
  422. target.path = relative.path;
  423. } else {
  424. target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
  425. }
  426. target.path = removeDotSegments(target.path);
  427. }
  428. target.query = relative.query;
  429. }
  430. //target.authority = base.authority;
  431. target.userinfo = base.userinfo;
  432. target.host = base.host;
  433. target.port = base.port;
  434. }
  435. target.scheme = base.scheme;
  436. }
  437. target.fragment = relative.fragment;
  438. return target;
  439. };
  440. export function resolve(baseURI:string, relativeURI:string, options?:URIOptions):string {
  441. const schemelessOptions = assign({ scheme : 'null' }, options);
  442. return serialize(resolveComponents(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true), schemelessOptions);
  443. };
  444. export function normalize(uri:string, options?:URIOptions):string;
  445. export function normalize(uri:URIComponents, options?:URIOptions):URIComponents;
  446. export function normalize(uri:any, options?:URIOptions):any {
  447. if (typeof uri === "string") {
  448. uri = serialize(parse(uri, options), options);
  449. } else if (typeOf(uri) === "object") {
  450. uri = parse(serialize(<URIComponents>uri, options), options);
  451. }
  452. return uri;
  453. };
  454. export function equal(uriA:string, uriB:string, options?: URIOptions):boolean;
  455. export function equal(uriA:URIComponents, uriB:URIComponents, options?:URIOptions):boolean;
  456. export function equal(uriA:any, uriB:any, options?:URIOptions):boolean {
  457. if (typeof uriA === "string") {
  458. uriA = serialize(parse(uriA, options), options);
  459. } else if (typeOf(uriA) === "object") {
  460. uriA = serialize(<URIComponents>uriA, options);
  461. }
  462. if (typeof uriB === "string") {
  463. uriB = serialize(parse(uriB, options), options);
  464. } else if (typeOf(uriB) === "object") {
  465. uriB = serialize(<URIComponents>uriB, options);
  466. }
  467. return uriA === uriB;
  468. };
  469. export function escapeComponent(str:string, options?:URIOptions):string {
  470. return str && str.toString().replace((!options || !options.iri ? URI_PROTOCOL.ESCAPE : IRI_PROTOCOL.ESCAPE), pctEncChar);
  471. };
  472. export function unescapeComponent(str:string, options?:URIOptions):string {
  473. return str && str.toString().replace((!options || !options.iri ? URI_PROTOCOL.PCT_ENCODED : IRI_PROTOCOL.PCT_ENCODED), pctDecChars);
  474. };