PageRenderTime 73ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/src/sys/dotnet/fan/sys/Uri.cs

https://bitbucket.org/bedlaczech/fan-1.0
C# | 1503 lines | 1163 code | 196 blank | 144 comment | 419 complexity | 497758ab37b9372d490437b349b6eb34 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. //
  2. // Copyright (c) 2006, Brian Frank and Andy Frank
  3. // Licensed under the Academic Free License version 3.0
  4. //
  5. // History:
  6. // 19 Oct 06 Andy Frank Creation
  7. // 21 Aug 07 Brian Rewrite with our own parser/encoder
  8. //
  9. using System;
  10. using System.Collections;
  11. using System.Text;
  12. using Fanx.Util;
  13. using Fanx.Serial;
  14. namespace Fan.Sys
  15. {
  16. /// <summary>
  17. /// Uri is used to immutably represent a Universal Resource Identifier.
  18. /// </summary>
  19. public sealed class Uri : FanObj, Literal
  20. {
  21. //////////////////////////////////////////////////////////////////////////
  22. // Construction
  23. //////////////////////////////////////////////////////////////////////////
  24. public static Uri fromStr(string s) { return fromStr(s, true); }
  25. public static Uri fromStr(string s, bool check)
  26. {
  27. try
  28. {
  29. return new Uri(new Decoder(s, false).decode());
  30. }
  31. catch (ParseErr.Val e)
  32. {
  33. if (!check) return null;
  34. throw ParseErr.make("Uri", s, e.m_err.msg()).val;
  35. }
  36. catch (Exception)
  37. {
  38. if (!check) return null;
  39. throw ParseErr.make("Uri", s).val;
  40. }
  41. }
  42. public static Uri decode(string s) { return decode(s, true); }
  43. public static Uri decode(string s, bool check)
  44. {
  45. try
  46. {
  47. return new Uri(new Decoder(s, true).decode());
  48. }
  49. catch (ParseErr.Val e)
  50. {
  51. if (!check) return null;
  52. throw ParseErr.make("Uri", s, e.m_err.msg()).val;
  53. }
  54. catch (Exception)
  55. {
  56. if (!check) return null;
  57. throw ParseErr.make("Uri", s).val;
  58. }
  59. }
  60. //////////////////////////////////////////////////////////////////////////
  61. // Utils
  62. //////////////////////////////////////////////////////////////////////////
  63. public static Map decodeQuery(string s)
  64. {
  65. try
  66. {
  67. return new Decoder(s, true).decodeQuery();
  68. }
  69. catch (ArgErr.Val e)
  70. {
  71. throw ArgErr.make("Invalid Uri query: `" + s + "`: " + e.m_err.msg()).val;
  72. }
  73. catch (Exception)
  74. {
  75. throw ArgErr.make("Invalid Uri query: `" + s + "`").val;
  76. }
  77. }
  78. public static string encodeQuery(Map map)
  79. {
  80. StringBuilder buf = new StringBuilder(256);
  81. IEnumerator en = map.keysEnumerator();
  82. while (en.MoveNext())
  83. {
  84. string key = (string)en.Current;
  85. string val = (string)map.get(key);
  86. if (buf.Length > 0) buf.Append('&');
  87. encodeQueryStr(buf, key);
  88. if (val != null)
  89. {
  90. buf.Append('=');
  91. encodeQueryStr(buf, val);
  92. }
  93. }
  94. return buf.ToString();
  95. }
  96. static void encodeQueryStr(StringBuilder buf, string str)
  97. {
  98. for (int i=0; i<str.Length; ++i)
  99. {
  100. int c = str[i];
  101. if (c < 128 && (charMap[c] & QUERY) != 0 && (delimEscMap[c] & QUERY) == 0)
  102. buf.Append((char)c);
  103. else if (c == ' ')
  104. buf.Append('+');
  105. else
  106. percentEncodeChar(buf, c);
  107. }
  108. }
  109. //////////////////////////////////////////////////////////////////////////
  110. // Java Constructors
  111. //////////////////////////////////////////////////////////////////////////
  112. private Uri(Sections x)
  113. {
  114. m_scheme = x.scheme;
  115. m_userInfo = x.userInfo;
  116. m_host = x.host;
  117. m_port = x.port;
  118. m_pathStr = x.pathStr;
  119. m_path = x.path.ro();
  120. m_queryStr = x.queryStr;
  121. m_query = x.query.ro();
  122. m_frag = x.frag;
  123. m_str = x.str != null ? x.str : new Encoder(this, false).encode();
  124. }
  125. //////////////////////////////////////////////////////////////////////////
  126. // Sections
  127. //////////////////////////////////////////////////////////////////////////
  128. class Sections
  129. {
  130. internal void setAuth(Uri x) { userInfo = x.m_userInfo; host = x.m_host; port = x.m_port; }
  131. internal void setPath(Uri x) { pathStr = x.m_pathStr; path = x.m_path; }
  132. internal void setQuery(Uri x) { queryStr = x.m_queryStr; query = x.m_query; }
  133. internal void setFrag(Uri x) { frag = x.m_frag; }
  134. internal void normalize()
  135. {
  136. normalizeSchemes();
  137. normalizePath();
  138. normalizeQuery();
  139. }
  140. private void normalizeSchemes()
  141. {
  142. if (scheme == null) return;
  143. if (scheme == "http") { normalizeScheme(80); return; }
  144. if (scheme == "https") { normalizeScheme(443); return; }
  145. if (scheme == "ftp") { normalizeScheme(21); return; }
  146. }
  147. private void normalizeScheme(int p)
  148. {
  149. // port 80 -> null
  150. if (port != null && port.longValue() == p) port = null;
  151. // if path is "" -> "/"
  152. if (pathStr == null || pathStr.Length == 0)
  153. {
  154. pathStr = FanStr.m_ascii['/'];
  155. if (path == null) path = emptyPath();
  156. }
  157. }
  158. private void normalizePath()
  159. {
  160. if (path == null) return;
  161. bool isAbs = pathStr.StartsWith("/");
  162. bool isDir = pathStr.EndsWith("/");
  163. bool dotLast = false;
  164. bool modified = false;
  165. for (int i=0; i<path.sz(); ++i)
  166. {
  167. string seg = (string)path.get(i);
  168. if (seg == "." && (path.sz() > 1 || host != null))
  169. {
  170. path.removeAt(i);
  171. modified = true;
  172. dotLast = true;
  173. i -= 1;
  174. }
  175. else if (seg == ".." && i > 0 && path.get(i-1).ToString() != "..")
  176. {
  177. path.removeAt(i);
  178. path.removeAt(i-1);
  179. modified = true;
  180. i -= 2;
  181. dotLast = true;
  182. }
  183. else
  184. {
  185. dotLast = false;
  186. }
  187. }
  188. if (modified)
  189. {
  190. if (dotLast) isDir = true;
  191. if (path.sz() == 0 || path.last().ToString() == "..") isDir = false;
  192. pathStr = toPathStr(isAbs, path, isDir);
  193. }
  194. }
  195. private void normalizeQuery()
  196. {
  197. if (query == null)
  198. query = emptyQuery();
  199. }
  200. internal string scheme;
  201. internal string host;
  202. internal string userInfo;
  203. internal Long port;
  204. internal string pathStr;
  205. internal List path;
  206. internal string queryStr;
  207. internal Map query;
  208. internal string frag;
  209. internal string str;
  210. }
  211. //////////////////////////////////////////////////////////////////////////
  212. // Decoder
  213. //////////////////////////////////////////////////////////////////////////
  214. class Decoder : Sections
  215. {
  216. internal Decoder(string str, bool decoding)
  217. {
  218. this.str = str;
  219. this.decoding = decoding;
  220. }
  221. internal Decoder decode()
  222. {
  223. string str = this.str;
  224. int len = str.Length;
  225. int pos = 0;
  226. // ==== scheme ====
  227. // scan the string from the beginning looking for either a
  228. // colon or any character which doesn't fit a valid scheme
  229. bool hasUpper = false;
  230. for (int i=0; i<len; ++i)
  231. {
  232. int c = str[i];
  233. if (isScheme(c)) { hasUpper |= isUpper(c); continue; }
  234. if (c != ':') break;
  235. // at this point we have a scheme; if we detected
  236. // any upper case characters normalize to lowercase
  237. pos = i + 1;
  238. string scheme = str.Substring(0, i);
  239. if (hasUpper) scheme = FanStr.lower(scheme);
  240. this.scheme = scheme;
  241. break;
  242. }
  243. // ==== authority ====
  244. // authority must start with //
  245. if (pos+1 < len && str[pos] == '/' && str[pos+1] == '/')
  246. {
  247. // find end of authority which is /, ?, #, or end of string;
  248. // while we're scanning look for @ and last colon which isn't
  249. // inside an [] IPv6 literal
  250. int authStart = pos+2, authEnd = len, at = -1, colon = -1;
  251. for (int i=authStart; i<len; ++i)
  252. {
  253. int c = str[i];
  254. if (c == '/' || c == '?' || c == '#') { authEnd = i; break; }
  255. else if (c == '@' && at < 0) { at = i; colon = -1; }
  256. else if (c == ':') colon = i;
  257. else if (c == ']') colon = -1;
  258. }
  259. // start with assumption that there is no userinfo or port
  260. int hostStart = authStart, hostEnd = authEnd;
  261. // if we found an @ symbol, parse out userinfo
  262. if (at > 0)
  263. {
  264. this.userInfo = substr(authStart, at, USER);
  265. hostStart = at+1;
  266. }
  267. // if we found an colon, parse out port
  268. if (colon > 0)
  269. {
  270. this.port = Long.valueOf(Convert.ToInt64(str.Substring(colon+1, authEnd-colon-1)));
  271. hostEnd = colon;
  272. }
  273. // host is everything left in the authority
  274. this.host = substr(hostStart, hostEnd, HOST);
  275. pos = authEnd;
  276. }
  277. // ==== path ====
  278. // scan the string looking '?' or '#' which ends the path
  279. // section; while we're scanning count the number of slashes
  280. int pathStart = pos, pathEnd = len, numSegs = 1, prev = 0;
  281. for (int i=pathStart; i<len; ++i)
  282. {
  283. int c = str[i];
  284. if (prev != '\\')
  285. {
  286. if (c == '?' || c == '#') { pathEnd = i; break; }
  287. if (i != pathStart && c == '/') ++numSegs;
  288. prev = c;
  289. }
  290. else
  291. {
  292. prev = (c != '\\') ? c : 0;
  293. }
  294. }
  295. // we now have the complete path section
  296. this.pathStr = substr(pathStart, pathEnd, PATH);
  297. this.path = pathSegments(pathStr, numSegs);
  298. pos = pathEnd;
  299. // ==== query ====
  300. if (pos < len && str[pos] == '?')
  301. {
  302. // look for end of query which is # or end of string
  303. int queryStart = pos+1, queryEnd = len;
  304. prev = 0;
  305. for (int i=queryStart; i<len; ++i)
  306. {
  307. int c = str[i];
  308. if (prev != '\\')
  309. {
  310. if (c == '#') { queryEnd = i; break; }
  311. prev = c;
  312. }
  313. else
  314. {
  315. prev = (c != '\\') ? c : 0;
  316. }
  317. }
  318. // we now have the complete query section
  319. this.queryStr = substr(queryStart, queryEnd, QUERY);
  320. this.query = parseQuery(queryStr);
  321. pos = queryEnd;
  322. }
  323. // ==== frag ====
  324. if (pos < len && str[pos] == '#')
  325. {
  326. this.frag = substr(pos+1, len, FRAG);
  327. }
  328. // === normalize ===
  329. normalize();
  330. return this;
  331. }
  332. private List pathSegments(string pathStr, int numSegs)
  333. {
  334. // if pathStr is "/" then path si the empty list
  335. int len = pathStr.Length;
  336. if (len == 0 || (len == 1 && pathStr[0] == '/'))
  337. return emptyPath();
  338. // check for trailing slash
  339. if (len > 1 && pathStr[len-1] == '/')
  340. {
  341. numSegs--;
  342. len--;
  343. }
  344. // parse the segments
  345. string[] path = new string[numSegs];
  346. int n = 0;
  347. int segStart = 0, prev = 0;
  348. for (int i=0; i<pathStr.Length; ++i)
  349. {
  350. int c = pathStr[i];
  351. if (prev != '\\')
  352. {
  353. if (c == '/')
  354. {
  355. if (i > 0) path[n++] = pathStr.Substring(segStart, i-segStart);
  356. segStart = i+1;
  357. }
  358. prev = c;
  359. }
  360. else
  361. {
  362. prev = (c != '\\') ? c : 0;
  363. }
  364. }
  365. if (segStart < len)
  366. path[n++] = pathStr.Substring(segStart, pathStr.Length-segStart);
  367. return new List(Sys.StrType, path);
  368. }
  369. internal Map decodeQuery()
  370. {
  371. return parseQuery(substring(0, str.Length, QUERY));
  372. }
  373. private Map parseQuery(string q)
  374. {
  375. if (q == null) return null;
  376. Map map = new Map(Sys.StrType, Sys.StrType);
  377. try
  378. {
  379. int start = 0, eq = 0, len = q.Length, prev = 0;
  380. bool escaped = false;
  381. for (int i=0; i<len; ++i)
  382. {
  383. int ch = q[i];
  384. if (prev != '\\')
  385. {
  386. if (ch == '=') eq = i;
  387. if (ch != '&' && ch != ';') { prev = ch; continue; }
  388. }
  389. else
  390. {
  391. escaped = true;
  392. prev = (ch != '\\') ? ch : 0;
  393. continue;
  394. }
  395. if (start < i)
  396. {
  397. addQueryParam(map, q, start, eq, i, escaped);
  398. escaped = false;
  399. }
  400. start = eq = i+1;
  401. }
  402. if (start < len)
  403. addQueryParam(map, q, start, eq, len, escaped);
  404. }
  405. catch (Exception e)
  406. {
  407. // don't let internal error bring down whole uri
  408. Err.dumpStack(e);
  409. }
  410. return map;
  411. }
  412. private void addQueryParam(Map map, string q, int start, int eq, int end, bool escaped)
  413. {
  414. string key, val;
  415. if (start == eq && q[start] != '=')
  416. {
  417. key = toQueryStr(q, start, end, escaped);
  418. val = "true";
  419. }
  420. else
  421. {
  422. key = toQueryStr(q, start, eq, escaped);
  423. val = toQueryStr(q, eq+1, end, escaped);
  424. }
  425. string dup = (string)map.get(key);
  426. if (dup != null) val = dup + "," + val;
  427. map.set(key, val);
  428. }
  429. private string toQueryStr(string q, int start, int end, bool escaped)
  430. {
  431. if (!escaped) return q.Substring(start, end-start);
  432. StringBuilder s = new StringBuilder(end-start);
  433. int prev = 0;
  434. for (int i=start; i<end; ++i)
  435. {
  436. int c = q[i];
  437. if (c != '\\')
  438. {
  439. s.Append((char)c);
  440. prev = c;
  441. }
  442. else
  443. {
  444. if (prev == '\\') { s.Append((char)c); prev = 0; }
  445. else prev = c;
  446. }
  447. }
  448. return s.ToString();
  449. }
  450. private string substr(int start, int end, int section)
  451. {
  452. return substring(start, end, section);
  453. }
  454. private string substring(int start, int end, int section)
  455. {
  456. if (!decoding) return str.Substring(start, end-start);
  457. StringBuilder buf = new StringBuilder(end-start);
  458. dpos = start;
  459. while (dpos < end)
  460. {
  461. int ch = nextChar(section);
  462. if (nextCharWasEscaped && ch < delimEscMap.Length && (delimEscMap[ch] & section) != 0)
  463. buf.Append('\\');
  464. buf.Append((char)ch);
  465. }
  466. return buf.ToString();
  467. }
  468. private int nextChar(int section)
  469. {
  470. int c = nextOctet(section);
  471. if (c < 0) return -1;
  472. int c2, c3;
  473. switch (c >> 4)
  474. {
  475. case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
  476. /* 0xxxxxxx*/
  477. return c;
  478. case 12: case 13:
  479. /* 110x xxxx 10xx xxxx*/
  480. c2 = nextOctet(section);
  481. if ((c2 & 0xC0) != 0x80)
  482. throw err("Invalid UTF-8 encoding");
  483. return ((c & 0x1F) << 6) | (c2 & 0x3F);
  484. case 14:
  485. /* 1110 xxxx 10xx xxxx 10xx xxxx */
  486. c2 = nextOctet(section);
  487. c3 = nextOctet(section);
  488. if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80))
  489. throw err("Invalid UTF-8 encoding");
  490. return (((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | ((c3 & 0x3F) << 0));
  491. default:
  492. throw err("Invalid UTF-8 encoding");
  493. }
  494. }
  495. private int nextOctet(int section)
  496. {
  497. int c = str[dpos++];
  498. // if percent encoded applied to all sections except
  499. // scheme which should never never use this method
  500. if (c == '%')
  501. {
  502. nextCharWasEscaped = true;
  503. return (hexNibble(str[dpos++]) << 4) | hexNibble(str[dpos++]);
  504. }
  505. else
  506. {
  507. nextCharWasEscaped = false;
  508. }
  509. // + maps to space only in query
  510. if (c == '+' && section == QUERY)
  511. return ' ';
  512. // verify character ok
  513. if (c >= charMap.Length || (charMap[c] & section) == 0)
  514. throw err("Invalid char in " + toSection(section) + " at index " + (dpos-1));
  515. // return character as is
  516. return c;
  517. }
  518. new string str;
  519. bool decoding;
  520. int dpos;
  521. bool nextCharWasEscaped;
  522. }
  523. //////////////////////////////////////////////////////////////////////////
  524. // Encoder
  525. //////////////////////////////////////////////////////////////////////////
  526. class Encoder
  527. {
  528. internal Encoder(Uri uri, bool encoding)
  529. {
  530. this.uri = uri;
  531. this.encoding = encoding;
  532. this.buf = new StringBuilder();
  533. }
  534. internal string encode()
  535. {
  536. // scheme
  537. if (uri.m_scheme != null) buf.Append(uri.m_scheme).Append(':');
  538. // authority
  539. if (uri.m_userInfo != null || uri.m_host != null || uri.m_port != null)
  540. {
  541. buf.Append('/').Append('/');
  542. if (uri.m_userInfo != null) encode(uri.m_userInfo, USER).Append('@');
  543. if (uri.m_host != null) encode(uri.m_host, HOST);
  544. if (uri.m_port != null) buf.Append(':').Append(uri.m_port);
  545. }
  546. // path
  547. if (uri.m_pathStr != null)
  548. encode(uri.m_pathStr, PATH);
  549. // query
  550. if (uri.m_queryStr != null)
  551. { buf.Append('?'); encode(uri.m_queryStr, QUERY); }
  552. // frag
  553. if (uri.m_frag != null)
  554. { buf.Append('#'); encode(uri.m_frag, FRAG); }
  555. return buf.ToString();
  556. }
  557. internal StringBuilder encode(string s, int section)
  558. {
  559. if (!encoding) return buf.Append(s);
  560. int len = s.Length;
  561. int c = 0, prev;
  562. for (int i=0; i<len; ++i)
  563. {
  564. prev = c;
  565. c = s[i];
  566. // unreserved character
  567. if (c < 128 && (charMap[c] & section) != 0 && prev != '\\')
  568. {
  569. buf.Append((char)c);
  570. continue;
  571. }
  572. // the backslash esc itself doesn't get encoded
  573. if (c == '\\' && prev != '\\') continue;
  574. // we have a reserved, escaped, or non-ASCII
  575. // encode
  576. if (c == ' ' && section == QUERY)
  577. buf.Append('+');
  578. else
  579. percentEncodeChar(buf, c);
  580. // if we just encoded backslash, then it
  581. // doesn't escape the next char
  582. if (c == '\\') c = 0;
  583. }
  584. return buf;
  585. }
  586. Uri uri;
  587. bool encoding;
  588. StringBuilder buf;
  589. }
  590. static void percentEncodeChar(StringBuilder buf, int c)
  591. {
  592. if (c <= 0x007F)
  593. {
  594. percentEncodeByte(buf, c);
  595. }
  596. else if (c > 0x07FF)
  597. {
  598. percentEncodeByte(buf, 0xE0 | ((c >> 12) & 0x0F));
  599. percentEncodeByte(buf, 0x80 | ((c >> 6) & 0x3F));
  600. percentEncodeByte(buf, 0x80 | ((c >> 0) & 0x3F));
  601. }
  602. else
  603. {
  604. percentEncodeByte(buf, 0xC0 | ((c >> 6) & 0x1F));
  605. percentEncodeByte(buf, 0x80 | ((c >> 0) & 0x3F));
  606. }
  607. }
  608. static void percentEncodeByte(StringBuilder buf, int c)
  609. {
  610. buf.Append('%');
  611. int hi = (c >> 4) & 0xf;
  612. int lo = c & 0xf;
  613. buf.Append((char)(hi < 10 ? '0'+hi : 'A'+(hi-10)));
  614. buf.Append((char)(lo < 10 ? '0'+lo : 'A'+(lo-10)));
  615. }
  616. //////////////////////////////////////////////////////////////////////////
  617. // Identity
  618. //////////////////////////////////////////////////////////////////////////
  619. public override bool Equals(object obj)
  620. {
  621. if (obj is Uri)
  622. {
  623. return m_str == ((Uri)obj).m_str;
  624. }
  625. return false;
  626. }
  627. public override int GetHashCode()
  628. {
  629. return m_str.GetHashCode();
  630. }
  631. public override long hash()
  632. {
  633. return FanStr.hash(m_str);
  634. }
  635. public override string toStr()
  636. {
  637. return m_str;
  638. }
  639. public string toLocale()
  640. {
  641. return m_str;
  642. }
  643. public void encode(ObjEncoder @out)
  644. {
  645. @out.wStrLiteral(m_str, '`');
  646. }
  647. public override Type @typeof()
  648. {
  649. return Sys.UriType;
  650. }
  651. public string encode()
  652. {
  653. string x = m_encoded;
  654. if (x != null) return x;
  655. return m_encoded = new Encoder(this, true).encode();
  656. }
  657. //////////////////////////////////////////////////////////////////////////
  658. // Components
  659. //////////////////////////////////////////////////////////////////////////
  660. public bool isAbs()
  661. {
  662. return m_scheme != null;
  663. }
  664. public bool isRel()
  665. {
  666. return m_scheme == null;
  667. }
  668. public bool isDir()
  669. {
  670. if (m_pathStr != null)
  671. {
  672. string p = m_pathStr;
  673. int len = p.Length;
  674. if (len > 0 && p[len-1] == '/')
  675. return true;
  676. }
  677. return false;
  678. }
  679. public string scheme()
  680. {
  681. return m_scheme;
  682. }
  683. public string auth()
  684. {
  685. if (m_host == null) return null;
  686. if (m_port == null)
  687. {
  688. if (m_userInfo == null) return m_host;
  689. else return m_userInfo + '@' + m_host;
  690. }
  691. else
  692. {
  693. if (m_userInfo == null) return m_host + ':' + m_port;
  694. else return m_userInfo + '@' + m_host + ':' + m_port;
  695. }
  696. }
  697. public string host()
  698. {
  699. return m_host;
  700. }
  701. public string userInfo()
  702. {
  703. return m_userInfo;
  704. }
  705. public Long port()
  706. {
  707. return m_port;
  708. }
  709. public string path(int depth) { return ((string)m_path.get(depth)); }
  710. public List path()
  711. {
  712. return m_path;
  713. }
  714. public string pathStr()
  715. {
  716. return m_pathStr;
  717. }
  718. public bool isPathAbs()
  719. {
  720. if (m_pathStr == null || m_pathStr.Length == 0)
  721. return false;
  722. else
  723. return m_pathStr[0] == '/';
  724. }
  725. public bool isPathOnly()
  726. {
  727. return m_scheme == null && m_host == null && m_port == null &&
  728. m_userInfo == null && m_queryStr == null && m_frag == null;
  729. }
  730. public string name()
  731. {
  732. if (m_path.sz() == 0) return string.Empty;
  733. return (string)m_path.last();
  734. }
  735. public string basename()
  736. {
  737. string n = this.name();
  738. int dot = n.LastIndexOf('.');
  739. if (dot < 2)
  740. {
  741. if (dot < 0) return n;
  742. if (n == ".") return n;
  743. if (n == "..") return n;
  744. }
  745. return n.Substring(0, dot);
  746. }
  747. public string ext()
  748. {
  749. string n = this.name();
  750. int dot = n.LastIndexOf('.');
  751. if (dot < 2)
  752. {
  753. if (dot < 0) return null;
  754. if (n == ".") return null;
  755. if (n == "..") return null;
  756. }
  757. return n.Substring(dot+1);
  758. }
  759. public MimeType mimeType()
  760. {
  761. if (isDir()) return MimeType.m_dir;
  762. return MimeType.forExt(ext());
  763. }
  764. public Map query()
  765. {
  766. return m_query;
  767. }
  768. public string queryStr()
  769. {
  770. return m_queryStr;
  771. }
  772. public string frag()
  773. {
  774. return m_frag;
  775. }
  776. //////////////////////////////////////////////////////////////////////////
  777. // Utils
  778. //////////////////////////////////////////////////////////////////////////
  779. public Uri parent()
  780. {
  781. // if no path bail
  782. if (m_path.sz() == 0) return null;
  783. // if just a simple filename, then no parent
  784. string p = m_pathStr;
  785. if (m_path.sz() == 1 && !isPathAbs() && !isDir()) return null;
  786. // use slice
  787. return getRange(parentRange);
  788. }
  789. public Uri pathOnly()
  790. {
  791. if (m_pathStr == null)
  792. throw Err.make("Uri has no path: " + this).val;
  793. if (m_scheme == null && m_userInfo == null && m_host == null &&
  794. m_port == null && m_queryStr == null && m_frag == null)
  795. return this;
  796. Sections t = new Sections();
  797. t.path = this.m_path;
  798. t.pathStr = this.m_pathStr;
  799. t.query = emptyQuery();
  800. t.str = this.m_pathStr;
  801. return new Uri(t);
  802. }
  803. public Uri getRange(Range range) { return slice(range, false); }
  804. public Uri getRangeToPathAbs(Range range) { return slice(range, true); }
  805. private Uri slice(Range range, bool forcePathAbs)
  806. {
  807. if (m_pathStr == null)
  808. throw Err.make("Uri has no path: " + this).val;
  809. int size = m_path.sz();
  810. int s = range.start(size);
  811. int e = range.end(size);
  812. int n = e - s + 1;
  813. if (n < 0) throw IndexErr.make(range).val;
  814. bool head = (s == 0);
  815. bool tail = (e == size-1);
  816. if (head && tail && (!forcePathAbs || isPathAbs())) return this;
  817. Sections t = new Sections();
  818. t.path = m_path.getRange(range);
  819. StringBuilder sb = new StringBuilder(m_pathStr.Length);
  820. if ((head && isPathAbs()) || forcePathAbs) sb.Append('/');
  821. for (int i=0; i<t.path.sz(); ++i)
  822. {
  823. if (i > 0) sb.Append('/');
  824. sb.Append(t.path.get(i));
  825. }
  826. if (t.path.sz() > 0 && (!tail || isDir())) sb.Append('/');
  827. t.pathStr = sb.ToString();
  828. if (head)
  829. {
  830. t.scheme = m_scheme;
  831. t.userInfo = m_userInfo;
  832. t.host = m_host;
  833. t.port = m_port;
  834. }
  835. if (tail)
  836. {
  837. t.queryStr = m_queryStr;
  838. t.query = m_query;
  839. t.frag = m_frag;
  840. }
  841. else
  842. {
  843. t.query = emptyQuery();
  844. }
  845. if (!head && !tail)
  846. {
  847. t.str = t.pathStr;
  848. }
  849. return new Uri(t);
  850. }
  851. //////////////////////////////////////////////////////////////////////////
  852. // Relativize
  853. //////////////////////////////////////////////////////////////////////////
  854. public Uri relTo(Uri baseUri)
  855. {
  856. if (!OpUtil.compareEQ(this.m_scheme, baseUri.m_scheme) ||
  857. !OpUtil.compareEQ(this.m_userInfo, baseUri.m_userInfo) ||
  858. !OpUtil.compareEQ(this.m_host, baseUri.m_host) ||
  859. !OpUtil.compareEQ(this.m_port, baseUri.m_port))
  860. return this;
  861. // at this point we know we have the same scheme and auth, and
  862. // we're going to create a new URI which is a subset of this one
  863. Sections t = new Sections();
  864. t.query = this.m_query;
  865. t.queryStr = this.m_queryStr;
  866. t.frag = this.m_frag;
  867. // find divergence
  868. int d=0;
  869. int len = Math.Min(this.m_path.sz(), baseUri.m_path.sz());
  870. for (; d<len; ++d)
  871. if (!this.m_path.get(d).Equals(baseUri.m_path.get(d)))
  872. break;
  873. // if diverenge is at root, then no commonality
  874. if (d == 0)
  875. {
  876. // `/a/b/c`.relTo(`/`) should be `a/b/c`
  877. if (baseUri.m_path.isEmpty() && this.m_pathStr.StartsWith("/"))
  878. {
  879. t.path = this.m_path;
  880. t.pathStr = this.m_pathStr.Substring(1);
  881. }
  882. else
  883. {
  884. t.path = this.m_path;
  885. t.pathStr = this.m_pathStr;
  886. }
  887. }
  888. // if paths are exactly the same
  889. else if (d == this.m_path.sz() && d == baseUri.m_path.sz())
  890. {
  891. t.path = emptyPath();
  892. t.pathStr = string.Empty;
  893. }
  894. // create sub-path at divergence point
  895. else
  896. {
  897. // slice my path
  898. t.path = this.m_path.getRange(Range.makeInclusive(d, -1));
  899. // insert .. backup if needed
  900. int backup = baseUri.m_path.sz() - d;
  901. if (!baseUri.isDir()) backup--;
  902. while (backup-- > 0) t.path.insert(0, dotDot);
  903. // format the new path string
  904. t.pathStr = toPathStr(false, t.path, this.isDir());
  905. }
  906. return new Uri(t);
  907. }
  908. public Uri relToAuth()
  909. {
  910. if (m_scheme == null && m_userInfo == null &&
  911. m_host == null && m_port == null)
  912. return this;
  913. Sections t = new Sections();
  914. t.path = this.m_path;
  915. t.pathStr = this.m_pathStr;
  916. t.query = this.m_query;
  917. t.queryStr = this.m_queryStr;
  918. t.frag = this.m_frag;
  919. return new Uri(t);
  920. }
  921. //////////////////////////////////////////////////////////////////////////
  922. // Plus
  923. //////////////////////////////////////////////////////////////////////////
  924. public Uri plus(Uri r)
  925. {
  926. // if r is more or equal as absolute as base, return r
  927. if (r.m_scheme != null) return r;
  928. if (r.m_host != null && this.m_scheme == null) return r;
  929. if (r.isPathAbs() && this.m_host == null) return r;
  930. // this algorthm is lifted straight from
  931. // RFC 3986 (5.2.2) Transform References;
  932. Uri baseUri = this;
  933. Sections t = new Sections();
  934. if (r.m_host != null)
  935. {
  936. t.setAuth(r);
  937. t.setPath(r);
  938. t.setQuery(r);
  939. }
  940. else
  941. {
  942. if (r.m_pathStr == null || r.m_pathStr == "")
  943. {
  944. t.setPath(baseUri);
  945. if (r.m_queryStr != null)
  946. t.setQuery(r);
  947. else
  948. t.setQuery(baseUri);
  949. }
  950. else
  951. {
  952. if (r.m_pathStr.StartsWith("/"))
  953. t.setPath(r);
  954. else
  955. merge(t, baseUri, r);
  956. t.setQuery(r);
  957. }
  958. t.setAuth(baseUri);
  959. }
  960. t.scheme = baseUri.m_scheme;
  961. t.frag = r.m_frag;
  962. t.normalize();
  963. return new Uri(t);
  964. }
  965. static void merge(Sections t, Uri baseUri, Uri r)
  966. {
  967. bool baseIsAbs = baseUri.isPathAbs();
  968. bool baseIsDir = baseUri.isDir();
  969. bool rIsDir = r.isDir();
  970. List rPath = r.m_path;
  971. bool dotLast = false;
  972. // compute the target path taking into account whether
  973. // the base is a dir and any dot segments in relative ref
  974. List tPath;
  975. if (baseUri.m_path.sz() == 0)
  976. {
  977. tPath = r.m_path;
  978. }
  979. else
  980. {
  981. tPath = baseUri.m_path.rw();
  982. if (!baseIsDir) tPath.pop();
  983. for (int i=0; i<rPath.sz(); ++i)
  984. {
  985. string rSeg = (string)rPath.get(i);
  986. if (rSeg == ".") { dotLast = true; continue; }
  987. if (rSeg == "..")
  988. {
  989. if (!tPath.isEmpty()) { tPath.pop(); dotLast = true; continue; }
  990. if (baseIsAbs) continue;
  991. }
  992. tPath.add(rSeg); dotLast = false;
  993. }
  994. //tPath = tPath;
  995. }
  996. t.path = tPath;
  997. t.pathStr = toPathStr(baseIsAbs, tPath, rIsDir || dotLast);
  998. }
  999. static string toPathStr(bool isAbs, List path, bool isDir)
  1000. {
  1001. StringBuilder buf = new StringBuilder();
  1002. if (isAbs) buf.Append('/');
  1003. for (int i=0; i<path.sz(); ++i)
  1004. {
  1005. if (i > 0) buf.Append('/');
  1006. buf.Append(path.get(i));
  1007. }
  1008. if (isDir && !(buf.Length > 0 && buf[buf.Length-1] == '/'))
  1009. buf.Append('/');
  1010. return buf.ToString();
  1011. }
  1012. public Uri plusName(string name) { return plusName(name, false); }
  1013. public Uri plusName(string name, bool asDir)
  1014. {
  1015. int size = m_path.sz();
  1016. bool isDir = this.isDir() || m_path.isEmpty();
  1017. int newSize = isDir ? size + 1 : size;
  1018. string[] temp = (string[])m_path.toArray(new string[newSize]);
  1019. temp[newSize-1] = name;
  1020. Sections t = new Sections();
  1021. t.scheme = this.m_scheme;
  1022. t.userInfo = this.m_userInfo;
  1023. t.host = this.m_host;
  1024. t.port = this.m_port;
  1025. t.query = emptyQuery();
  1026. t.queryStr = null;
  1027. t.frag = null;
  1028. t.path = new List(Sys.StrType, temp);
  1029. t.pathStr = toPathStr(isAbs() || isPathAbs(), t.path, asDir);
  1030. return new Uri(t);
  1031. }
  1032. public Uri plusSlash()
  1033. {
  1034. if (isDir()) return this;
  1035. Sections t = new Sections();
  1036. t.scheme = this.m_scheme;
  1037. t.userInfo = this.m_userInfo;
  1038. t.host = this.m_host;
  1039. t.port = this.m_port;
  1040. t.query = this.m_query;
  1041. t.queryStr = this.m_queryStr;
  1042. t.frag = this.m_frag;
  1043. t.path = this.m_path;
  1044. t.pathStr = this.m_pathStr + "/";
  1045. return new Uri(t);
  1046. }
  1047. public Uri plusQuery(Map q)
  1048. {
  1049. if (q == null || q.isEmpty()) return this;
  1050. Map merge = m_query.dup().setAll(q);
  1051. StringBuilder s = new StringBuilder(256);
  1052. IDictionaryEnumerator en = merge.pairsIterator();
  1053. while (en.MoveNext())
  1054. {
  1055. if (s.Length > 0) s.Append('&');
  1056. string key = (string)en.Key;
  1057. string val = (string)en.Value;
  1058. appendQueryStr(s, key);
  1059. s.Append('=');
  1060. appendQueryStr(s, val);
  1061. }
  1062. Sections t = new Sections();
  1063. t.scheme = m_scheme;
  1064. t.userInfo = m_userInfo;
  1065. t.host = m_host;
  1066. t.port = m_port;
  1067. t.frag = m_frag;
  1068. t.pathStr = m_pathStr;
  1069. t.path = m_path;
  1070. t.query = merge.ro();
  1071. t.queryStr = s.ToString();
  1072. return new Uri(t);
  1073. }
  1074. static void appendQueryStr(StringBuilder buf, string str)
  1075. {
  1076. for (int i=0; i<str.Length; ++i)
  1077. {
  1078. int c = str[i];
  1079. if (c < delimEscMap.Length && (delimEscMap[c] & QUERY) != 0)
  1080. buf.Append('\\');
  1081. buf.Append((char)c);
  1082. }
  1083. }
  1084. //////////////////////////////////////////////////////////////////////////
  1085. // Resolution
  1086. //////////////////////////////////////////////////////////////////////////
  1087. public File toFile()
  1088. {
  1089. return File.make(this);
  1090. }
  1091. public object get() { return get(null, true); }
  1092. public object get(object @base) { return get(@base, true); }
  1093. public object get(object @base, bool check)
  1094. {
  1095. // if we have a relative uri, we need to resolve against
  1096. // the base object's uri
  1097. Uri uri = this;
  1098. if (m_scheme == null)
  1099. {
  1100. if (@base == null) throw UnresolvedErr.make("Relative uri with no base: " + this).val;
  1101. Uri baseUri = null;
  1102. try
  1103. {
  1104. baseUri = (Uri)trap(@base, "uri", null);
  1105. if (baseUri == null)
  1106. throw UnresolvedErr.make("Base object's uri is null: " + this).val;
  1107. }
  1108. catch (System.Exception e)
  1109. {
  1110. throw UnresolvedErr.make("Cannot access base '" + FanObj.@typeof(@base) + ".uri' to normalize: " + this, e).val;
  1111. }
  1112. if (baseUri.m_scheme == null)
  1113. throw UnresolvedErr.make("Base object's uri is not absolute: " + baseUri).val;
  1114. uri = baseUri.plus(this);
  1115. }
  1116. // resolve scheme handler
  1117. UriScheme scheme = UriScheme.find(uri.m_scheme);
  1118. // route to scheme
  1119. try
  1120. {
  1121. return scheme.get(uri, @base);
  1122. }
  1123. catch (UnresolvedErr.Val e)
  1124. {
  1125. if (check) throw e;
  1126. return null;
  1127. }
  1128. }
  1129. //////////////////////////////////////////////////////////////////////////
  1130. // Conversion
  1131. //////////////////////////////////////////////////////////////////////////
  1132. public string toCode()
  1133. {
  1134. StringBuilder s = new StringBuilder(m_str.Length+4);
  1135. s.Append('`');
  1136. int len = m_str.Length;
  1137. for (int i=0; i<len; ++i)
  1138. {
  1139. int c = m_str[i];
  1140. switch (c)
  1141. {
  1142. case '\n': s.Append('\\').Append('n'); break;
  1143. case '\r': s.Append('\\').Append('r'); break;
  1144. case '\f': s.Append('\\').Append('f'); break;
  1145. case '\t': s.Append('\\').Append('t'); break;
  1146. case '`': s.Append('\\').Append('`'); break;
  1147. case '$': s.Append('\\').Append('$'); break;
  1148. default: s.Append((char)c); break;
  1149. }
  1150. }
  1151. // closing quote
  1152. return s.Append('`').ToString();
  1153. }
  1154. //////////////////////////////////////////////////////////////////////////
  1155. // Utils
  1156. //////////////////////////////////////////////////////////////////////////
  1157. public static bool isName(string name)
  1158. {
  1159. int len = name.Length;
  1160. // must be at least one character long
  1161. if (len == 0) return false;
  1162. // check for "." and ".."
  1163. if (name[0] == '.' && len <= 2)
  1164. {
  1165. if (len == 1) return false;
  1166. if (name[1] == '.') return false;
  1167. }
  1168. // check that each char is unreserved
  1169. for (int i=0; i<len; ++i)
  1170. {
  1171. int c = name[i];
  1172. if (c < 128 && nameMap[c]) continue;
  1173. return false;
  1174. }
  1175. return true;
  1176. }
  1177. public static void checkName(string name)
  1178. {
  1179. if (!isName(name))
  1180. throw NameErr.make(name).val;
  1181. }
  1182. static bool isUpper(int c)
  1183. {
  1184. return 'A' <= c && c <= 'Z';
  1185. }
  1186. static int hexNibble(int ch)
  1187. {
  1188. if ((charMap[ch] & HEX) == 0) throw err("Invalid percent encoded hex: '" + (char)ch);
  1189. if (ch <= '9') return ch - '0';
  1190. if (ch <= 'Z') return (ch - 'A') + 10;
  1191. return (ch - 'a') + 10;
  1192. }
  1193. static Exception err(string msg)
  1194. {
  1195. return ParseErr.make(msg).val;
  1196. }
  1197. //////////////////////////////////////////////////////////////////////////
  1198. // Character Map
  1199. //////////////////////////////////////////////////////////////////////////
  1200. static string toSection(int section)
  1201. {
  1202. switch (section)
  1203. {
  1204. case SCHEME: return "scheme";
  1205. case USER: return "userInfo";
  1206. case HOST: return "host";
  1207. case PATH: return "path";
  1208. case QUERY: return "query";
  1209. case FRAG: return "frag";
  1210. default: return "uri";
  1211. }
  1212. }
  1213. static bool isScheme(int c) { return c < 128 ? (charMap[c] & SCHEME) != 0 : false; }
  1214. static readonly byte[] charMap = new byte[128];
  1215. static readonly bool[] nameMap = new bool[128];
  1216. static readonly byte[] delimEscMap = new byte[128];
  1217. const byte SCHEME = 0x01;
  1218. const byte USER = 0x02;
  1219. const byte HOST = 0x04;
  1220. const byte PATH = 0x08;
  1221. const byte QUERY = 0x10;
  1222. const byte FRAG = 0x20;
  1223. const byte DIGIT = 0x40;
  1224. const byte HEX = 0x80;
  1225. static Uri()
  1226. {
  1227. // alpha/digits characters
  1228. byte unreserved = SCHEME | USER | HOST | PATH | QUERY | FRAG;
  1229. for (int i='a'; i<='z'; ++i) { charMap[i] = unreserved; nameMap[i] = true; }
  1230. for (int i='A'; i<='Z'; ++i) { charMap[i] = unreserved; nameMap[i] = true; }
  1231. for (int i='0'; i<='9'; ++i) { charMap[i] = unreserved; nameMap[i] = true; }
  1232. // unreserved symbols
  1233. charMap['-'] = unreserved; nameMap['-'] = true;
  1234. charMap['.'] = unreserved; nameMap['.'] = true;
  1235. charMap['_'] = unreserved; nameMap['_'] = true;
  1236. charMap['~'] = unreserved; nameMap['~'] = true;
  1237. // hex
  1238. for (int i='0'; i<='9'; ++i) charMap[i] |= HEX | DIGIT;
  1239. for (int i='a'; i<='f'; ++i) charMap[i] |= HEX;
  1240. for (int i='A'; i<='F'; ++i) charMap[i] |= HEX;
  1241. // sub-delimiter symbols
  1242. charMap['!'] = USER | HOST | PATH | QUERY | FRAG;
  1243. charMap['$'] = USER | HOST | PATH | QUERY | FRAG;
  1244. charMap['&'] = USER | HOST | PATH | QUERY | FRAG;
  1245. charMap['\''] = USER | HOST | PATH | QUERY | FRAG;
  1246. charMap['('] = USER | HOST | PATH | QUERY | FRAG;
  1247. charMap[')'] = USER | HOST | PATH | QUERY | FRAG;
  1248. charMap['*'] = USER | HOST | PATH | QUERY | FRAG;
  1249. charMap['+'] = SCHEME | USER | HOST | PATH | FRAG;
  1250. charMap[','] = USER | HOST | PATH | QUERY | FRAG;
  1251. charMap[';'] = USER | HOST | PATH | QUERY | FRAG;
  1252. charMap['='] = USER | HOST | PATH | QUERY | FRAG;
  1253. // gen-delimiter symbols
  1254. charMap[':'] = PATH | USER | QUERY | FRAG;
  1255. charMap['/'] = PATH | QUERY | FRAG;
  1256. charMap['?'] = QUERY | FRAG;
  1257. charMap['#'] = 0;
  1258. charMap['['] = 0;
  1259. charMap[']'] = 0;
  1260. charMap['@'] = PATH | QUERY | FRAG;
  1261. // delimiter escape map - which characters need to
  1262. // be backslashed escaped in each section
  1263. delimEscMap[':'] = PATH;
  1264. delimEscMap['/'] = PATH;
  1265. delimEscMap['?'] = PATH;
  1266. delimEscMap['#'] = PATH | QUERY;
  1267. delimEscMap['&'] = QUERY;
  1268. delimEscMap[';'] = QUERY;
  1269. delimEscMap['='] = QUERY;
  1270. delimEscMap['\\'] = SCHEME | USER | HOST | PATH | QUERY | FRAG;
  1271. }
  1272. //////////////////////////////////////////////////////////////////////////
  1273. // Empty Path/Query
  1274. //////////////////////////////////////////////////////////////////////////
  1275. static List emptyPath()
  1276. {
  1277. List p = m_emptyPath;
  1278. if (p == null) p = m_emptyPath = (List)new List(Sys.StrType).toImmutable();
  1279. return p;
  1280. }
  1281. static List m_emptyPath;
  1282. static Map emptyQuery()
  1283. {
  1284. Map q = m_emptyQuery;
  1285. if (q == null) q = m_emptyQuery = (Map)new Map(Sys.StrType, Sys.StrType).toImmutable();
  1286. return q;
  1287. }
  1288. static Map m_emptyQuery;
  1289. //////////////////////////////////////////////////////////////////////////
  1290. // Fields
  1291. //////////////////////////////////////////////////////////////////////////
  1292. static readonly Range parentRange = Range.make(0, -2, false);
  1293. static readonly string dotDot = "..";
  1294. public static readonly Uri m_defVal = fromStr("");
  1295. internal readonly string m_str;
  1296. internal readonly string m_scheme;
  1297. internal readonly string m_userInfo;
  1298. internal readonly string m_host;
  1299. internal readonly Long m_port;
  1300. internal readonly List m_path;
  1301. internal readonly string m_pathStr;
  1302. internal readonly Map m_query;
  1303. internal readonly string m_queryStr;
  1304. internal readonly string m_frag;
  1305. internal string m_encoded;
  1306. }
  1307. }