uri.nim 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Dominik Picheta
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements URI parsing as specified by RFC 3986.
  10. ##
  11. ## A Uniform Resource Identifier (URI) provides a simple and extensible
  12. ## means for identifying a resource. A URI can be further classified
  13. ## as a locator, a name, or both. The term “Uniform Resource Locator”
  14. ## (URL) refers to the subset of URIs.
  15. ##
  16. ## Basic usage
  17. ## ===========
  18. ##
  19. ## Combine URIs
  20. ## -------------
  21. ## .. code-block::
  22. ## import uri
  23. ## let host = parseUri("https://nim-lang.org")
  24. ## let blog = "/blog.html"
  25. ## let bloguri = host / blog
  26. ## assert $host == "https://nim-lang.org"
  27. ## assert $bloguri == "https://nim-lang.org/blog.html"
  28. ##
  29. ## Access URI item
  30. ## ---------------
  31. ## .. code-block::
  32. ## import uri
  33. ## let res = parseUri("sftp://127.0.0.1:4343")
  34. ## if isAbsolute(res):
  35. ## assert res.port == "4343"
  36. ## else:
  37. ## echo "Wrong format"
  38. import strutils, parseutils
  39. type
  40. Url* = distinct string
  41. Uri* = object
  42. scheme*, username*, password*: string
  43. hostname*, port*, path*, query*, anchor*: string
  44. opaque*: bool
  45. proc encodeUrl*(s: string, usePlus = true): string =
  46. ## Encodes a URL according to RFC3986.
  47. ##
  48. ## This means that characters in the set
  49. ## ``{'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~'}`` are
  50. ## carried over to the result.
  51. ## All other characters are encoded as ``%xx`` where ``xx``
  52. ## denotes its hexadecimal value.
  53. ##
  54. ## As a special rule, when the value of ``usePlus`` is true,
  55. ## spaces are encoded as ``+`` instead of ``%20``.
  56. ##
  57. ## **See also:**
  58. ## * `decodeUrl proc<#decodeUrl,string>`_
  59. runnableExamples:
  60. assert encodeUrl("https://nim-lang.org") == "https%3A%2F%2Fnim-lang.org"
  61. assert encodeUrl("https://nim-lang.org/this is a test") == "https%3A%2F%2Fnim-lang.org%2Fthis+is+a+test"
  62. assert encodeUrl("https://nim-lang.org/this is a test", false) == "https%3A%2F%2Fnim-lang.org%2Fthis%20is%20a%20test"
  63. result = newStringOfCap(s.len + s.len shr 2) # assume 12% non-alnum-chars
  64. let fromSpace = if usePlus: "+" else: "%20"
  65. for c in s:
  66. case c
  67. # https://tools.ietf.org/html/rfc3986#section-2.3
  68. of 'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~': add(result, c)
  69. of ' ': add(result, fromSpace)
  70. else:
  71. add(result, '%')
  72. add(result, toHex(ord(c), 2))
  73. proc decodeUrl*(s: string, decodePlus = true): string =
  74. ## Decodes a URL according to RFC3986.
  75. ##
  76. ## This means that any ``%xx`` (where ``xx`` denotes a hexadecimal
  77. ## value) are converted to the character with ordinal number ``xx``,
  78. ## and every other character is carried over.
  79. ##
  80. ## As a special rule, when the value of ``decodePlus`` is true, ``+``
  81. ## characters are converted to a space.
  82. ##
  83. ## **See also:**
  84. ## * `encodeUrl proc<#encodeUrl,string>`_
  85. runnableExamples:
  86. assert decodeUrl("https%3A%2F%2Fnim-lang.org") == "https://nim-lang.org"
  87. assert decodeUrl("https%3A%2F%2Fnim-lang.org%2Fthis+is+a+test") == "https://nim-lang.org/this is a test"
  88. assert decodeUrl("https%3A%2F%2Fnim-lang.org%2Fthis%20is%20a%20test",
  89. false) == "https://nim-lang.org/this is a test"
  90. proc handleHexChar(c: char, x: var int) {.inline.} =
  91. case c
  92. of '0'..'9': x = (x shl 4) or (ord(c) - ord('0'))
  93. of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10)
  94. of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10)
  95. else: assert(false)
  96. result = newString(s.len)
  97. var i = 0
  98. var j = 0
  99. while i < s.len:
  100. case s[i]
  101. of '%':
  102. var x = 0
  103. handleHexChar(s[i+1], x)
  104. handleHexChar(s[i+2], x)
  105. inc(i, 2)
  106. result[j] = chr(x)
  107. of '+':
  108. if decodePlus:
  109. result[j] = ' '
  110. else:
  111. result[j] = s[i]
  112. else: result[j] = s[i]
  113. inc(i)
  114. inc(j)
  115. setLen(result, j)
  116. proc encodeQuery*(query: openArray[(string, string)], usePlus = true,
  117. omitEq = true): string =
  118. ## Encodes a set of (key, value) parameters into a URL query string.
  119. ##
  120. ## Every (key, value) pair is URL-encoded and written as ``key=value``. If the
  121. ## value is an empty string then the ``=`` is omitted, unless ``omitEq`` is
  122. ## false.
  123. ## The pairs are joined together by a ``&`` character.
  124. ##
  125. ## The ``usePlus`` parameter is passed down to the `encodeUrl` function that
  126. ## is used for the URL encoding of the string values.
  127. ##
  128. ## **See also:**
  129. ## * `encodeUrl proc<#encodeUrl,string>`_
  130. runnableExamples:
  131. assert encodeQuery({: }) == ""
  132. assert encodeQuery({"a": "1", "b": "2"}) == "a=1&b=2"
  133. assert encodeQuery({"a": "1", "b": ""}) == "a=1&b"
  134. for elem in query:
  135. # Encode the `key = value` pairs and separate them with a '&'
  136. if result.len > 0: result.add('&')
  137. let (key, val) = elem
  138. result.add(encodeUrl(key, usePlus))
  139. # Omit the '=' if the value string is empty
  140. if not omitEq or val.len > 0:
  141. result.add('=')
  142. result.add(encodeUrl(val, usePlus))
  143. proc parseAuthority(authority: string, result: var Uri) =
  144. var i = 0
  145. var inPort = false
  146. var inIPv6 = false
  147. while i < authority.len:
  148. case authority[i]
  149. of '@':
  150. swap result.password, result.port
  151. result.port.setLen(0)
  152. swap result.username, result.hostname
  153. result.hostname.setLen(0)
  154. inPort = false
  155. of ':':
  156. if inIPv6:
  157. result.hostname.add(authority[i])
  158. else:
  159. inPort = true
  160. of '[':
  161. inIPv6 = true
  162. of ']':
  163. inIPv6 = false
  164. else:
  165. if inPort:
  166. result.port.add(authority[i])
  167. else:
  168. result.hostname.add(authority[i])
  169. i.inc
  170. proc parsePath(uri: string, i: var int, result: var Uri) =
  171. i.inc parseUntil(uri, result.path, {'?', '#'}, i)
  172. # The 'mailto' scheme's PATH actually contains the hostname/username
  173. if cmpIgnoreCase(result.scheme, "mailto") == 0:
  174. parseAuthority(result.path, result)
  175. result.path.setLen(0)
  176. if i < uri.len and uri[i] == '?':
  177. i.inc # Skip '?'
  178. i.inc parseUntil(uri, result.query, {'#'}, i)
  179. if i < uri.len and uri[i] == '#':
  180. i.inc # Skip '#'
  181. i.inc parseUntil(uri, result.anchor, {}, i)
  182. proc initUri*(): Uri =
  183. ## Initializes a URI with ``scheme``, ``username``, ``password``,
  184. ## ``hostname``, ``port``, ``path``, ``query`` and ``anchor``.
  185. ##
  186. ## **See also:**
  187. ## * `Uri type <#Uri>`_ for available fields in the URI type
  188. runnableExamples:
  189. var uri2: Uri
  190. assert initUri() == uri2
  191. result = Uri(scheme: "", username: "", password: "", hostname: "", port: "",
  192. path: "", query: "", anchor: "")
  193. proc resetUri(uri: var Uri) =
  194. for f in uri.fields:
  195. when f is string:
  196. f.setLen(0)
  197. else:
  198. f = false
  199. proc parseUri*(uri: string, result: var Uri) =
  200. ## Parses a URI. The `result` variable will be cleared before.
  201. ##
  202. ## **See also:**
  203. ## * `Uri type <#Uri>`_ for available fields in the URI type
  204. ## * `initUri proc <#initUri>`_ for initializing a URI
  205. runnableExamples:
  206. var res = initUri()
  207. parseUri("https://nim-lang.org/docs/manual.html", res)
  208. assert res.scheme == "https"
  209. assert res.hostname == "nim-lang.org"
  210. assert res.path == "/docs/manual.html"
  211. resetUri(result)
  212. var i = 0
  213. # Check if this is a reference URI (relative URI)
  214. let doubleSlash = uri.len > 1 and uri[1] == '/'
  215. if i < uri.len and uri[i] == '/':
  216. # Make sure ``uri`` doesn't begin with '//'.
  217. if not doubleSlash:
  218. parsePath(uri, i, result)
  219. return
  220. # Scheme
  221. i.inc parseWhile(uri, result.scheme, Letters + Digits + {'+', '-', '.'}, i)
  222. if (i >= uri.len or uri[i] != ':') and not doubleSlash:
  223. # Assume this is a reference URI (relative URI)
  224. i = 0
  225. result.scheme.setLen(0)
  226. parsePath(uri, i, result)
  227. return
  228. if not doubleSlash:
  229. i.inc # Skip ':'
  230. # Authority
  231. if i+1 < uri.len and uri[i] == '/' and uri[i+1] == '/':
  232. i.inc(2) # Skip //
  233. var authority = ""
  234. i.inc parseUntil(uri, authority, {'/', '?', '#'}, i)
  235. if authority.len > 0:
  236. parseAuthority(authority, result)
  237. else:
  238. result.opaque = true
  239. # Path
  240. parsePath(uri, i, result)
  241. proc parseUri*(uri: string): Uri =
  242. ## Parses a URI and returns it.
  243. ##
  244. ## **See also:**
  245. ## * `Uri type <#Uri>`_ for available fields in the URI type
  246. runnableExamples:
  247. let res = parseUri("ftp://Username:Password@Hostname")
  248. assert res.username == "Username"
  249. assert res.password == "Password"
  250. assert res.scheme == "ftp"
  251. result = initUri()
  252. parseUri(uri, result)
  253. proc removeDotSegments(path: string): string =
  254. if path.len == 0: return ""
  255. var collection: seq[string] = @[]
  256. let endsWithSlash = path[path.len-1] == '/'
  257. var i = 0
  258. var currentSegment = ""
  259. while i < path.len:
  260. case path[i]
  261. of '/':
  262. collection.add(currentSegment)
  263. currentSegment = ""
  264. of '.':
  265. if i+2 < path.len and path[i+1] == '.' and path[i+2] == '/':
  266. if collection.len > 0:
  267. discard collection.pop()
  268. i.inc 3
  269. continue
  270. elif path[i+1] == '/':
  271. i.inc 2
  272. continue
  273. currentSegment.add path[i]
  274. else:
  275. currentSegment.add path[i]
  276. i.inc
  277. if currentSegment != "":
  278. collection.add currentSegment
  279. result = collection.join("/")
  280. if endsWithSlash: result.add '/'
  281. proc merge(base, reference: Uri): string =
  282. # http://tools.ietf.org/html/rfc3986#section-5.2.3
  283. if base.hostname != "" and base.path == "":
  284. '/' & reference.path
  285. else:
  286. let lastSegment = rfind(base.path, "/")
  287. if lastSegment == -1:
  288. reference.path
  289. else:
  290. base.path[0 .. lastSegment] & reference.path
  291. proc combine*(base: Uri, reference: Uri): Uri =
  292. ## Combines a base URI with a reference URI.
  293. ##
  294. ## This uses the algorithm specified in
  295. ## `section 5.2.2 of RFC 3986 <http://tools.ietf.org/html/rfc3986#section-5.2.2>`_.
  296. ##
  297. ## This means that the slashes inside the base URIs path as well as reference
  298. ## URIs path affect the resulting URI.
  299. ##
  300. ## **See also:**
  301. ## * `/ proc <#/,Uri,string>`_ for building URIs
  302. runnableExamples:
  303. let foo = combine(parseUri("https://nim-lang.org/foo/bar"), parseUri("/baz"))
  304. assert foo.path == "/baz"
  305. let bar = combine(parseUri("https://nim-lang.org/foo/bar"), parseUri("baz"))
  306. assert bar.path == "/foo/baz"
  307. let qux = combine(parseUri("https://nim-lang.org/foo/bar/"), parseUri("baz"))
  308. assert qux.path == "/foo/bar/baz"
  309. template setAuthority(dest, src): untyped =
  310. dest.hostname = src.hostname
  311. dest.username = src.username
  312. dest.port = src.port
  313. dest.password = src.password
  314. result = initUri()
  315. if reference.scheme != base.scheme and reference.scheme != "":
  316. result = reference
  317. result.path = removeDotSegments(result.path)
  318. else:
  319. if reference.hostname != "":
  320. setAuthority(result, reference)
  321. result.path = removeDotSegments(reference.path)
  322. result.query = reference.query
  323. else:
  324. if reference.path == "":
  325. result.path = base.path
  326. if reference.query != "":
  327. result.query = reference.query
  328. else:
  329. result.query = base.query
  330. else:
  331. if reference.path.startsWith("/"):
  332. result.path = removeDotSegments(reference.path)
  333. else:
  334. result.path = removeDotSegments(merge(base, reference))
  335. result.query = reference.query
  336. setAuthority(result, base)
  337. result.scheme = base.scheme
  338. result.anchor = reference.anchor
  339. proc combine*(uris: varargs[Uri]): Uri =
  340. ## Combines multiple URIs together.
  341. ##
  342. ## **See also:**
  343. ## * `/ proc <#/,Uri,string>`_ for building URIs
  344. runnableExamples:
  345. let foo = combine(parseUri("https://nim-lang.org/"), parseUri("docs/"),
  346. parseUri("manual.html"))
  347. assert foo.hostname == "nim-lang.org"
  348. assert foo.path == "/docs/manual.html"
  349. result = uris[0]
  350. for i in 1 ..< uris.len:
  351. result = combine(result, uris[i])
  352. proc isAbsolute*(uri: Uri): bool =
  353. ## Returns true if URI is absolute, false otherwise.
  354. runnableExamples:
  355. let foo = parseUri("https://nim-lang.org")
  356. assert isAbsolute(foo) == true
  357. let bar = parseUri("nim-lang")
  358. assert isAbsolute(bar) == false
  359. return uri.scheme != "" and (uri.hostname != "" or uri.path != "")
  360. proc `/`*(x: Uri, path: string): Uri =
  361. ## Concatenates the path specified to the specified URIs path.
  362. ##
  363. ## Contrary to the `combine proc <#combine,Uri,Uri>`_ you do not have to worry about
  364. ## the slashes at the beginning and end of the path and URIs path
  365. ## respectively.
  366. ##
  367. ## **See also:**
  368. ## * `combine proc <#combine,Uri,Uri>`_
  369. runnableExamples:
  370. let foo = parseUri("https://nim-lang.org/foo/bar") / "/baz"
  371. assert foo.path == "/foo/bar/baz"
  372. let bar = parseUri("https://nim-lang.org/foo/bar") / "baz"
  373. assert bar.path == "/foo/bar/baz"
  374. let qux = parseUri("https://nim-lang.org/foo/bar/") / "baz"
  375. assert qux.path == "/foo/bar/baz"
  376. result = x
  377. if result.path.len == 0:
  378. if path.len == 0 or path[0] != '/':
  379. result.path = "/"
  380. result.path.add(path)
  381. return
  382. if result.path.len > 0 and result.path[result.path.len-1] == '/':
  383. if path.len > 0 and path[0] == '/':
  384. result.path.add(path[1 .. path.len-1])
  385. else:
  386. result.path.add(path)
  387. else:
  388. if path.len == 0 or path[0] != '/':
  389. result.path.add '/'
  390. result.path.add(path)
  391. proc `?`*(u: Uri, query: openArray[(string, string)]): Uri =
  392. ## Concatenates the query parameters to the specified URI object.
  393. runnableExamples:
  394. let foo = parseUri("https://example.com") / "foo" ? {"bar": "qux"}
  395. assert $foo == "https://example.com/foo?bar=qux"
  396. result = u
  397. result.query = encodeQuery(query)
  398. proc `$`*(u: Uri): string =
  399. ## Returns the string representation of the specified URI object.
  400. runnableExamples:
  401. let foo = parseUri("https://nim-lang.org")
  402. assert $foo == "https://nim-lang.org"
  403. result = ""
  404. if u.scheme.len > 0:
  405. result.add(u.scheme)
  406. if u.opaque:
  407. result.add(":")
  408. else:
  409. result.add("://")
  410. if u.username.len > 0:
  411. result.add(u.username)
  412. if u.password.len > 0:
  413. result.add(":")
  414. result.add(u.password)
  415. result.add("@")
  416. if u.hostname.endsWith('/'):
  417. result.add(u.hostname[0..^2])
  418. else:
  419. result.add(u.hostname)
  420. if u.port.len > 0:
  421. result.add(":")
  422. result.add(u.port)
  423. if u.path.len > 0:
  424. if u.hostname.len > 0 and u.path[0] != '/':
  425. result.add('/')
  426. result.add(u.path)
  427. if u.query.len > 0:
  428. result.add("?")
  429. result.add(u.query)
  430. if u.anchor.len > 0:
  431. result.add("#")
  432. result.add(u.anchor)
  433. when isMainModule:
  434. block:
  435. const test1 = "abc\L+def xyz"
  436. doAssert encodeUrl(test1) == "abc%0A%2Bdef+xyz"
  437. doAssert decodeUrl(encodeUrl(test1)) == test1
  438. doAssert encodeUrl(test1, false) == "abc%0A%2Bdef%20xyz"
  439. doAssert decodeUrl(encodeUrl(test1, false), false) == test1
  440. doAssert decodeUrl(encodeUrl(test1)) == test1
  441. block:
  442. let str = "http://localhost"
  443. let test = parseUri(str)
  444. doAssert test.path == ""
  445. block:
  446. let str = "http://localhost/"
  447. let test = parseUri(str)
  448. doAssert test.path == "/"
  449. block:
  450. let str = "http://localhost:8080/test"
  451. let test = parseUri(str)
  452. doAssert test.scheme == "http"
  453. doAssert test.port == "8080"
  454. doAssert test.path == "/test"
  455. doAssert test.hostname == "localhost"
  456. doAssert($test == str)
  457. block:
  458. let str = "foo://username:password@example.com:8042/over/there" &
  459. "/index.dtb?type=animal&name=narwhal#nose"
  460. let test = parseUri(str)
  461. doAssert test.scheme == "foo"
  462. doAssert test.username == "username"
  463. doAssert test.password == "password"
  464. doAssert test.hostname == "example.com"
  465. doAssert test.port == "8042"
  466. doAssert test.path == "/over/there/index.dtb"
  467. doAssert test.query == "type=animal&name=narwhal"
  468. doAssert test.anchor == "nose"
  469. doAssert($test == str)
  470. block:
  471. # IPv6 address
  472. let str = "foo://[::1]:1234/bar?baz=true&qux#quux"
  473. let uri = parseUri(str)
  474. doAssert uri.scheme == "foo"
  475. doAssert uri.hostname == "::1"
  476. doAssert uri.port == "1234"
  477. doAssert uri.path == "/bar"
  478. doAssert uri.query == "baz=true&qux"
  479. doAssert uri.anchor == "quux"
  480. block:
  481. let str = "urn:example:animal:ferret:nose"
  482. let test = parseUri(str)
  483. doAssert test.scheme == "urn"
  484. doAssert test.path == "example:animal:ferret:nose"
  485. doAssert($test == str)
  486. block:
  487. let str = "mailto:username@example.com?subject=Topic"
  488. let test = parseUri(str)
  489. doAssert test.scheme == "mailto"
  490. doAssert test.username == "username"
  491. doAssert test.hostname == "example.com"
  492. doAssert test.query == "subject=Topic"
  493. doAssert($test == str)
  494. block:
  495. let str = "magnet:?xt=urn:sha1:72hsga62ba515sbd62&dn=foobar"
  496. let test = parseUri(str)
  497. doAssert test.scheme == "magnet"
  498. doAssert test.query == "xt=urn:sha1:72hsga62ba515sbd62&dn=foobar"
  499. doAssert($test == str)
  500. block:
  501. let str = "/test/foo/bar?q=2#asdf"
  502. let test = parseUri(str)
  503. doAssert test.scheme == ""
  504. doAssert test.path == "/test/foo/bar"
  505. doAssert test.query == "q=2"
  506. doAssert test.anchor == "asdf"
  507. doAssert($test == str)
  508. block:
  509. let str = "test/no/slash"
  510. let test = parseUri(str)
  511. doAssert test.path == "test/no/slash"
  512. doAssert($test == str)
  513. block:
  514. let str = "//git@github.com:dom96/packages"
  515. let test = parseUri(str)
  516. doAssert test.scheme == ""
  517. doAssert test.username == "git"
  518. doAssert test.hostname == "github.com"
  519. doAssert test.port == "dom96"
  520. doAssert test.path == "/packages"
  521. block:
  522. let str = "file:///foo/bar/baz.txt"
  523. let test = parseUri(str)
  524. doAssert test.scheme == "file"
  525. doAssert test.username == ""
  526. doAssert test.hostname == ""
  527. doAssert test.port == ""
  528. doAssert test.path == "/foo/bar/baz.txt"
  529. # Remove dot segments tests
  530. block:
  531. doAssert removeDotSegments("/foo/bar/baz") == "/foo/bar/baz"
  532. # Combine tests
  533. block:
  534. let concat = combine(parseUri("http://google.com/foo/bar/"), parseUri("baz"))
  535. doAssert concat.path == "/foo/bar/baz"
  536. doAssert concat.hostname == "google.com"
  537. doAssert concat.scheme == "http"
  538. block:
  539. let concat = combine(parseUri("http://google.com/foo"), parseUri("/baz"))
  540. doAssert concat.path == "/baz"
  541. doAssert concat.hostname == "google.com"
  542. doAssert concat.scheme == "http"
  543. block:
  544. let concat = combine(parseUri("http://google.com/foo/test"), parseUri("bar"))
  545. doAssert concat.path == "/foo/bar"
  546. block:
  547. let concat = combine(parseUri("http://google.com/foo/test"), parseUri("/bar"))
  548. doAssert concat.path == "/bar"
  549. block:
  550. let concat = combine(parseUri("http://google.com/foo/test"), parseUri("bar"))
  551. doAssert concat.path == "/foo/bar"
  552. block:
  553. let concat = combine(parseUri("http://google.com/foo/test/"), parseUri("bar"))
  554. doAssert concat.path == "/foo/test/bar"
  555. block:
  556. let concat = combine(parseUri("http://google.com/foo/test/"), parseUri("bar/"))
  557. doAssert concat.path == "/foo/test/bar/"
  558. block:
  559. let concat = combine(parseUri("http://google.com/foo/test/"), parseUri("bar/"),
  560. parseUri("baz"))
  561. doAssert concat.path == "/foo/test/bar/baz"
  562. # `/` tests
  563. block:
  564. let test = parseUri("http://example.com/foo") / "bar/asd"
  565. doAssert test.path == "/foo/bar/asd"
  566. block:
  567. let test = parseUri("http://example.com/foo/") / "/bar/asd"
  568. doAssert test.path == "/foo/bar/asd"
  569. # removeDotSegments tests
  570. block:
  571. # empty test
  572. doAssert removeDotSegments("") == ""
  573. # bug #3207
  574. block:
  575. doAssert parseUri("http://qq/1").combine(parseUri("https://qqq")).`$` == "https://qqq"
  576. # bug #4959
  577. block:
  578. let foo = parseUri("http://example.com") / "/baz"
  579. doAssert foo.path == "/baz"
  580. # bug found on stream 13/10/17
  581. block:
  582. let foo = parseUri("http://localhost:9515") / "status"
  583. doAssert $foo == "http://localhost:9515/status"
  584. # bug #6649 #6652
  585. block:
  586. var foo = parseUri("http://example.com")
  587. foo.hostname = "example.com"
  588. foo.path = "baz"
  589. doAssert $foo == "http://example.com/baz"
  590. foo.hostname = "example.com/"
  591. foo.path = "baz"
  592. doAssert $foo == "http://example.com/baz"
  593. foo.hostname = "example.com"
  594. foo.path = "/baz"
  595. doAssert $foo == "http://example.com/baz"
  596. foo.hostname = "example.com/"
  597. foo.path = "/baz"
  598. doAssert $foo == "http://example.com/baz"
  599. foo.hostname = "example.com/"
  600. foo.port = "8000"
  601. foo.path = "baz"
  602. doAssert $foo == "http://example.com:8000/baz"
  603. foo = parseUri("file:/dir/file")
  604. foo.path = "relative"
  605. doAssert $foo == "file:relative"
  606. # isAbsolute tests
  607. block:
  608. doAssert "www.google.com".parseUri().isAbsolute() == false
  609. doAssert "http://www.google.com".parseUri().isAbsolute() == true
  610. doAssert "file:/dir/file".parseUri().isAbsolute() == true
  611. doAssert "file://localhost/dir/file".parseUri().isAbsolute() == true
  612. doAssert "urn:ISSN:1535-3613".parseUri().isAbsolute() == true
  613. # path-relative URL *relative
  614. doAssert "about".parseUri().isAbsolute == false
  615. doAssert "about/staff.html".parseUri().isAbsolute == false
  616. doAssert "about/staff.html?".parseUri().isAbsolute == false
  617. doAssert "about/staff.html?parameters".parseUri().isAbsolute == false
  618. # absolute-path-relative URL *relative
  619. doAssert "/".parseUri().isAbsolute == false
  620. doAssert "/about".parseUri().isAbsolute == false
  621. doAssert "/about/staff.html".parseUri().isAbsolute == false
  622. doAssert "/about/staff.html?".parseUri().isAbsolute == false
  623. doAssert "/about/staff.html?parameters".parseUri().isAbsolute == false
  624. # scheme-relative URL *relative
  625. doAssert "//username:password@example.com:8888".parseUri().isAbsolute == false
  626. doAssert "//username@example.com".parseUri().isAbsolute == false
  627. doAssert "//example.com".parseUri().isAbsolute == false
  628. doAssert "//example.com/".parseUri().isAbsolute == false
  629. doAssert "//example.com/about".parseUri().isAbsolute == false
  630. doAssert "//example.com/about/staff.html".parseUri().isAbsolute == false
  631. doAssert "//example.com/about/staff.html?".parseUri().isAbsolute == false
  632. doAssert "//example.com/about/staff.html?parameters".parseUri().isAbsolute == false
  633. # absolute URL *absolute
  634. doAssert "https://username:password@example.com:8888".parseUri().isAbsolute == true
  635. doAssert "https://username@example.com".parseUri().isAbsolute == true
  636. doAssert "https://example.com".parseUri().isAbsolute == true
  637. doAssert "https://example.com/".parseUri().isAbsolute == true
  638. doAssert "https://example.com/about".parseUri().isAbsolute == true
  639. doAssert "https://example.com/about/staff.html".parseUri().isAbsolute == true
  640. doAssert "https://example.com/about/staff.html?".parseUri().isAbsolute == true
  641. doAssert "https://example.com/about/staff.html?parameters".parseUri().isAbsolute == true
  642. # encodeQuery tests
  643. block:
  644. doAssert encodeQuery({:}) == ""
  645. doAssert encodeQuery({"foo": "bar"}) == "foo=bar"
  646. doAssert encodeQuery({"foo": "bar & baz"}) == "foo=bar+%26+baz"
  647. doAssert encodeQuery({"foo": "bar & baz"}, usePlus = false) == "foo=bar%20%26%20baz"
  648. doAssert encodeQuery({"foo": ""}) == "foo"
  649. doAssert encodeQuery({"foo": ""}, omitEq = false) == "foo="
  650. doAssert encodeQuery({"a": "1", "b": "", "c": "3"}) == "a=1&b&c=3"
  651. doAssert encodeQuery({"a": "1", "b": "", "c": "3"}, omitEq = false) == "a=1&b=&c=3"
  652. block:
  653. var foo = parseUri("http://example.com") / "foo" ? {"bar": "1", "baz": "qux"}
  654. var foo1 = parseUri("http://example.com/foo?bar=1&baz=qux")
  655. doAssert foo == foo1
  656. block:
  657. var foo = parseUri("http://example.com") / "foo" ? {"do": "do", "bar": ""}
  658. var foo1 = parseUri("http://example.com/foo?do=do&bar")
  659. doAssert foo == foo1
  660. echo("All good!")