uri.nim 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  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. import strutils, parseutils
  11. type
  12. Url* = distinct string
  13. Uri* = object
  14. scheme*, username*, password*: string
  15. hostname*, port*, path*, query*, anchor*: string
  16. opaque*: bool
  17. {.push warning[deprecated]: off.}
  18. proc `$`*(url: Url): string {.deprecated: "use Uri instead".} =
  19. ## **Deprecated since 0.9.6**: Use ``Uri`` instead.
  20. return string(url)
  21. proc `/`*(a, b: Url): Url {.deprecated: "use Uri instead".} =
  22. ## Joins two URLs together, separating them with / if needed.
  23. ##
  24. ## **Deprecated since 0.9.6**: Use ``Uri`` instead.
  25. var urlS = $a
  26. var bS = $b
  27. if urlS == "": return b
  28. if urlS[urlS.len-1] != '/':
  29. urlS.add('/')
  30. if bS[0] == '/':
  31. urlS.add(bS.substr(1))
  32. else:
  33. urlS.add(bs)
  34. result = Url(urlS)
  35. proc add*(url: var Url, a: Url) {.deprecated: "use Uri instead".} =
  36. ## Appends url to url.
  37. ##
  38. ## **Deprecated since 0.9.6**: Use ``Uri`` instead.
  39. url = url / a
  40. {.pop.}
  41. proc encodeUrl*(s: string, usePlus=true): string =
  42. ## Encodes a URL according to RFC3986.
  43. ##
  44. ## This means that characters in the set
  45. ## ``{'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~'}`` are
  46. ## carried over to the result.
  47. ## All other characters are encoded as ``''%xx'`` where ``xx``
  48. ## denotes its hexadecimal value.
  49. ##
  50. ## As a special rule, when the value of ``usePlus`` is true,
  51. ## spaces are encoded as ``'+'`` instead of ``'%20'``.
  52. result = newStringOfCap(s.len + s.len shr 2) # assume 12% non-alnum-chars
  53. let fromSpace = if usePlus: "+" else: "%20"
  54. for c in s:
  55. case c
  56. # https://tools.ietf.org/html/rfc3986#section-2.3
  57. of 'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~': add(result, c)
  58. of ' ': add(result, fromSpace)
  59. else:
  60. add(result, '%')
  61. add(result, toHex(ord(c), 2))
  62. proc decodeUrl*(s: string, decodePlus=true): string =
  63. ## Decodes a URL according to RFC3986.
  64. ##
  65. ## This means that any ``'%xx'`` (where ``xx`` denotes a hexadecimal
  66. ## value) are converted to the character with ordinal number ``xx``,
  67. ## and every other character is carried over.
  68. ##
  69. ## As a special rule, when the value of ``decodePlus`` is true, ``'+'``
  70. ## characters are converted to a space.
  71. proc handleHexChar(c: char, x: var int) {.inline.} =
  72. case c
  73. of '0'..'9': x = (x shl 4) or (ord(c) - ord('0'))
  74. of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10)
  75. of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10)
  76. else: assert(false)
  77. result = newString(s.len)
  78. var i = 0
  79. var j = 0
  80. while i < s.len:
  81. case s[i]
  82. of '%':
  83. var x = 0
  84. handleHexChar(s[i+1], x)
  85. handleHexChar(s[i+2], x)
  86. inc(i, 2)
  87. result[j] = chr(x)
  88. of '+':
  89. if decodePlus:
  90. result[j] = ' '
  91. else:
  92. result[j] = s[i]
  93. else: result[j] = s[i]
  94. inc(i)
  95. inc(j)
  96. setLen(result, j)
  97. proc parseAuthority(authority: string, result: var Uri) =
  98. var i = 0
  99. var inPort = false
  100. var inIPv6 = false
  101. while i < authority.len:
  102. case authority[i]
  103. of '@':
  104. swap result.password, result.port
  105. result.port.setLen(0)
  106. swap result.username, result.hostname
  107. result.hostname.setLen(0)
  108. inPort = false
  109. of ':':
  110. if inIPv6:
  111. result.hostname.add(authority[i])
  112. else:
  113. inPort = true
  114. of '[':
  115. inIPv6 = true
  116. of ']':
  117. inIPv6 = false
  118. else:
  119. if inPort:
  120. result.port.add(authority[i])
  121. else:
  122. result.hostname.add(authority[i])
  123. i.inc
  124. proc parsePath(uri: string, i: var int, result: var Uri) =
  125. i.inc parseUntil(uri, result.path, {'?', '#'}, i)
  126. # The 'mailto' scheme's PATH actually contains the hostname/username
  127. if cmpIgnoreCase(result.scheme, "mailto") == 0:
  128. parseAuthority(result.path, result)
  129. result.path.setLen(0)
  130. if i < uri.len and uri[i] == '?':
  131. i.inc # Skip '?'
  132. i.inc parseUntil(uri, result.query, {'#'}, i)
  133. if i < uri.len and uri[i] == '#':
  134. i.inc # Skip '#'
  135. i.inc parseUntil(uri, result.anchor, {}, i)
  136. proc initUri*(): Uri =
  137. ## Initializes a URI.
  138. result = Uri(scheme: "", username: "", password: "", hostname: "", port: "",
  139. path: "", query: "", anchor: "")
  140. proc resetUri(uri: var Uri) =
  141. for f in uri.fields:
  142. when f is string:
  143. f.setLen(0)
  144. else:
  145. f = false
  146. proc parseUri*(uri: string, result: var Uri) =
  147. ## Parses a URI. The `result` variable will be cleared before.
  148. resetUri(result)
  149. var i = 0
  150. # Check if this is a reference URI (relative URI)
  151. let doubleSlash = uri.len > 1 and uri[1] == '/'
  152. if i < uri.len and uri[i] == '/':
  153. # Make sure ``uri`` doesn't begin with '//'.
  154. if not doubleSlash:
  155. parsePath(uri, i, result)
  156. return
  157. # Scheme
  158. i.inc parseWhile(uri, result.scheme, Letters + Digits + {'+', '-', '.'}, i)
  159. if (i >= uri.len or uri[i] != ':') and not doubleSlash:
  160. # Assume this is a reference URI (relative URI)
  161. i = 0
  162. result.scheme.setLen(0)
  163. parsePath(uri, i, result)
  164. return
  165. if not doubleSlash:
  166. i.inc # Skip ':'
  167. # Authority
  168. if i+1 < uri.len and uri[i] == '/' and uri[i+1] == '/':
  169. i.inc(2) # Skip //
  170. var authority = ""
  171. i.inc parseUntil(uri, authority, {'/', '?', '#'}, i)
  172. if authority.len > 0:
  173. parseAuthority(authority, result)
  174. else:
  175. result.opaque = true
  176. # Path
  177. parsePath(uri, i, result)
  178. proc parseUri*(uri: string): Uri =
  179. ## Parses a URI and returns it.
  180. result = initUri()
  181. parseUri(uri, result)
  182. proc removeDotSegments(path: string): string =
  183. if path.len == 0: return ""
  184. var collection: seq[string] = @[]
  185. let endsWithSlash = path[path.len-1] == '/'
  186. var i = 0
  187. var currentSegment = ""
  188. while i < path.len:
  189. case path[i]
  190. of '/':
  191. collection.add(currentSegment)
  192. currentSegment = ""
  193. of '.':
  194. if i+2 < path.len and path[i+1] == '.' and path[i+2] == '/':
  195. if collection.len > 0:
  196. discard collection.pop()
  197. i.inc 3
  198. continue
  199. elif path[i+1] == '/':
  200. i.inc 2
  201. continue
  202. currentSegment.add path[i]
  203. else:
  204. currentSegment.add path[i]
  205. i.inc
  206. if currentSegment != "":
  207. collection.add currentSegment
  208. result = collection.join("/")
  209. if endsWithSlash: result.add '/'
  210. proc merge(base, reference: Uri): string =
  211. # http://tools.ietf.org/html/rfc3986#section-5.2.3
  212. if base.hostname != "" and base.path == "":
  213. '/' & reference.path
  214. else:
  215. let lastSegment = rfind(base.path, "/")
  216. if lastSegment == -1:
  217. reference.path
  218. else:
  219. base.path[0 .. lastSegment] & reference.path
  220. proc combine*(base: Uri, reference: Uri): Uri =
  221. ## Combines a base URI with a reference URI.
  222. ##
  223. ## This uses the algorithm specified in
  224. ## `section 5.2.2 of RFC 3986 <http://tools.ietf.org/html/rfc3986#section-5.2.2>`_.
  225. ##
  226. ## This means that the slashes inside the base URI's path as well as reference
  227. ## URI's path affect the resulting URI.
  228. ##
  229. ## For building URIs you may wish to use \`/\` instead.
  230. ##
  231. ## Examples:
  232. ##
  233. ## .. code-block::
  234. ## let foo = combine(parseUri("http://example.com/foo/bar"), parseUri("/baz"))
  235. ## assert foo.path == "/baz"
  236. ##
  237. ## let bar = combine(parseUri("http://example.com/foo/bar"), parseUri("baz"))
  238. ## assert bar.path == "/foo/baz"
  239. ##
  240. ## let bar = combine(parseUri("http://example.com/foo/bar/"), parseUri("baz"))
  241. ## assert bar.path == "/foo/bar/baz"
  242. template setAuthority(dest, src): untyped =
  243. dest.hostname = src.hostname
  244. dest.username = src.username
  245. dest.port = src.port
  246. dest.password = src.password
  247. result = initUri()
  248. if reference.scheme != base.scheme and reference.scheme != "":
  249. result = reference
  250. result.path = removeDotSegments(result.path)
  251. else:
  252. if reference.hostname != "":
  253. setAuthority(result, reference)
  254. result.path = removeDotSegments(reference.path)
  255. result.query = reference.query
  256. else:
  257. if reference.path == "":
  258. result.path = base.path
  259. if reference.query != "":
  260. result.query = reference.query
  261. else:
  262. result.query = base.query
  263. else:
  264. if reference.path.startsWith("/"):
  265. result.path = removeDotSegments(reference.path)
  266. else:
  267. result.path = removeDotSegments(merge(base, reference))
  268. result.query = reference.query
  269. setAuthority(result, base)
  270. result.scheme = base.scheme
  271. result.anchor = reference.anchor
  272. proc combine*(uris: varargs[Uri]): Uri =
  273. ## Combines multiple URIs together.
  274. result = uris[0]
  275. for i in 1 ..< uris.len:
  276. result = combine(result, uris[i])
  277. proc isAbsolute*(uri: Uri): bool =
  278. ## returns true if URI is absolute, false otherwise
  279. return uri.scheme != "" and (uri.hostname != "" or uri.path != "")
  280. proc `/`*(x: Uri, path: string): Uri =
  281. ## Concatenates the path specified to the specified URI's path.
  282. ##
  283. ## Contrary to the ``combine`` procedure you do not have to worry about
  284. ## the slashes at the beginning and end of the path and URI's path
  285. ## respectively.
  286. ##
  287. ## Examples:
  288. ##
  289. ## .. code-block::
  290. ## let foo = parseUri("http://example.com/foo/bar") / "/baz"
  291. ## assert foo.path == "/foo/bar/baz"
  292. ##
  293. ## let bar = parseUri("http://example.com/foo/bar") / "baz"
  294. ## assert bar.path == "/foo/bar/baz"
  295. ##
  296. ## let bar = parseUri("http://example.com/foo/bar/") / "baz"
  297. ## assert bar.path == "/foo/bar/baz"
  298. result = x
  299. if result.path.len == 0:
  300. if path.len == 0 or path[0] != '/':
  301. result.path = "/"
  302. result.path.add(path)
  303. return
  304. if result.path.len > 0 and result.path[result.path.len-1] == '/':
  305. if path.len > 0 and path[0] == '/':
  306. result.path.add(path[1 .. path.len-1])
  307. else:
  308. result.path.add(path)
  309. else:
  310. if path.len == 0 or path[0] != '/':
  311. result.path.add '/'
  312. result.path.add(path)
  313. proc `$`*(u: Uri): string =
  314. ## Returns the string representation of the specified URI object.
  315. result = ""
  316. if u.scheme.len > 0:
  317. result.add(u.scheme)
  318. if u.opaque:
  319. result.add(":")
  320. else:
  321. result.add("://")
  322. if u.username.len > 0:
  323. result.add(u.username)
  324. if u.password.len > 0:
  325. result.add(":")
  326. result.add(u.password)
  327. result.add("@")
  328. if u.hostname.endswith('/'):
  329. result.add(u.hostname[0..^2])
  330. else:
  331. result.add(u.hostname)
  332. if u.port.len > 0:
  333. result.add(":")
  334. result.add(u.port)
  335. if u.path.len > 0:
  336. if u.hostname.len > 0 and u.path[0] != '/':
  337. result.add('/')
  338. result.add(u.path)
  339. if u.query.len > 0:
  340. result.add("?")
  341. result.add(u.query)
  342. if u.anchor.len > 0:
  343. result.add("#")
  344. result.add(u.anchor)
  345. when isMainModule:
  346. block:
  347. const test1 = "abc\L+def xyz"
  348. doAssert encodeUrl(test1) == "abc%0A%2Bdef+xyz"
  349. doAssert decodeUrl(encodeUrl(test1)) == test1
  350. doAssert encodeUrl(test1, false) == "abc%0A%2Bdef%20xyz"
  351. doAssert decodeUrl(encodeUrl(test1, false), false) == test1
  352. doAssert decodeUrl(encodeUrl(test1)) == test1
  353. block:
  354. let str = "http://localhost"
  355. let test = parseUri(str)
  356. doAssert test.path == ""
  357. block:
  358. let str = "http://localhost/"
  359. let test = parseUri(str)
  360. doAssert test.path == "/"
  361. block:
  362. let str = "http://localhost:8080/test"
  363. let test = parseUri(str)
  364. doAssert test.scheme == "http"
  365. doAssert test.port == "8080"
  366. doAssert test.path == "/test"
  367. doAssert test.hostname == "localhost"
  368. doAssert($test == str)
  369. block:
  370. let str = "foo://username:password@example.com:8042/over/there" &
  371. "/index.dtb?type=animal&name=narwhal#nose"
  372. let test = parseUri(str)
  373. doAssert test.scheme == "foo"
  374. doAssert test.username == "username"
  375. doAssert test.password == "password"
  376. doAssert test.hostname == "example.com"
  377. doAssert test.port == "8042"
  378. doAssert test.path == "/over/there/index.dtb"
  379. doAssert test.query == "type=animal&name=narwhal"
  380. doAssert test.anchor == "nose"
  381. doAssert($test == str)
  382. block:
  383. # IPv6 address
  384. let str = "foo://[::1]:1234/bar?baz=true&qux#quux"
  385. let uri = parseUri(str)
  386. doAssert uri.scheme == "foo"
  387. doAssert uri.hostname == "::1"
  388. doAssert uri.port == "1234"
  389. doAssert uri.path == "/bar"
  390. doAssert uri.query == "baz=true&qux"
  391. doAssert uri.anchor == "quux"
  392. block:
  393. let str = "urn:example:animal:ferret:nose"
  394. let test = parseUri(str)
  395. doAssert test.scheme == "urn"
  396. doAssert test.path == "example:animal:ferret:nose"
  397. doAssert($test == str)
  398. block:
  399. let str = "mailto:username@example.com?subject=Topic"
  400. let test = parseUri(str)
  401. doAssert test.scheme == "mailto"
  402. doAssert test.username == "username"
  403. doAssert test.hostname == "example.com"
  404. doAssert test.query == "subject=Topic"
  405. doAssert($test == str)
  406. block:
  407. let str = "magnet:?xt=urn:sha1:72hsga62ba515sbd62&dn=foobar"
  408. let test = parseUri(str)
  409. doAssert test.scheme == "magnet"
  410. doAssert test.query == "xt=urn:sha1:72hsga62ba515sbd62&dn=foobar"
  411. doAssert($test == str)
  412. block:
  413. let str = "/test/foo/bar?q=2#asdf"
  414. let test = parseUri(str)
  415. doAssert test.scheme == ""
  416. doAssert test.path == "/test/foo/bar"
  417. doAssert test.query == "q=2"
  418. doAssert test.anchor == "asdf"
  419. doAssert($test == str)
  420. block:
  421. let str = "test/no/slash"
  422. let test = parseUri(str)
  423. doAssert test.path == "test/no/slash"
  424. doAssert($test == str)
  425. block:
  426. let str = "//git@github.com:dom96/packages"
  427. let test = parseUri(str)
  428. doAssert test.scheme == ""
  429. doAssert test.username == "git"
  430. doAssert test.hostname == "github.com"
  431. doAssert test.port == "dom96"
  432. doAssert test.path == "/packages"
  433. block:
  434. let str = "file:///foo/bar/baz.txt"
  435. let test = parseUri(str)
  436. doAssert test.scheme == "file"
  437. doAssert test.username == ""
  438. doAssert test.hostname == ""
  439. doAssert test.port == ""
  440. doAssert test.path == "/foo/bar/baz.txt"
  441. # Remove dot segments tests
  442. block:
  443. doAssert removeDotSegments("/foo/bar/baz") == "/foo/bar/baz"
  444. # Combine tests
  445. block:
  446. let concat = combine(parseUri("http://google.com/foo/bar/"), parseUri("baz"))
  447. doAssert concat.path == "/foo/bar/baz"
  448. doAssert concat.hostname == "google.com"
  449. doAssert concat.scheme == "http"
  450. block:
  451. let concat = combine(parseUri("http://google.com/foo"), parseUri("/baz"))
  452. doAssert concat.path == "/baz"
  453. doAssert concat.hostname == "google.com"
  454. doAssert concat.scheme == "http"
  455. block:
  456. let concat = combine(parseUri("http://google.com/foo/test"), parseUri("bar"))
  457. doAssert concat.path == "/foo/bar"
  458. block:
  459. let concat = combine(parseUri("http://google.com/foo/test"), parseUri("/bar"))
  460. doAssert concat.path == "/bar"
  461. block:
  462. let concat = combine(parseUri("http://google.com/foo/test"), parseUri("bar"))
  463. doAssert concat.path == "/foo/bar"
  464. block:
  465. let concat = combine(parseUri("http://google.com/foo/test/"), parseUri("bar"))
  466. doAssert concat.path == "/foo/test/bar"
  467. block:
  468. let concat = combine(parseUri("http://google.com/foo/test/"), parseUri("bar/"))
  469. doAssert concat.path == "/foo/test/bar/"
  470. block:
  471. let concat = combine(parseUri("http://google.com/foo/test/"), parseUri("bar/"),
  472. parseUri("baz"))
  473. doAssert concat.path == "/foo/test/bar/baz"
  474. # `/` tests
  475. block:
  476. let test = parseUri("http://example.com/foo") / "bar/asd"
  477. doAssert test.path == "/foo/bar/asd"
  478. block:
  479. let test = parseUri("http://example.com/foo/") / "/bar/asd"
  480. doAssert test.path == "/foo/bar/asd"
  481. # removeDotSegments tests
  482. block:
  483. # empty test
  484. doAssert removeDotSegments("") == ""
  485. # bug #3207
  486. block:
  487. doAssert parseUri("http://qq/1").combine(parseUri("https://qqq")).`$` == "https://qqq"
  488. # bug #4959
  489. block:
  490. let foo = parseUri("http://example.com") / "/baz"
  491. doAssert foo.path == "/baz"
  492. # bug found on stream 13/10/17
  493. block:
  494. let foo = parseUri("http://localhost:9515") / "status"
  495. doAssert $foo == "http://localhost:9515/status"
  496. # bug #6649 #6652
  497. block:
  498. var foo = parseUri("http://example.com")
  499. foo.hostname = "example.com"
  500. foo.path = "baz"
  501. doAssert $foo == "http://example.com/baz"
  502. foo.hostname = "example.com/"
  503. foo.path = "baz"
  504. doAssert $foo == "http://example.com/baz"
  505. foo.hostname = "example.com"
  506. foo.path = "/baz"
  507. doAssert $foo == "http://example.com/baz"
  508. foo.hostname = "example.com/"
  509. foo.path = "/baz"
  510. doAssert $foo == "http://example.com/baz"
  511. foo.hostname = "example.com/"
  512. foo.port = "8000"
  513. foo.path = "baz"
  514. doAssert $foo == "http://example.com:8000/baz"
  515. foo = parseUri("file:/dir/file")
  516. foo.path = "relative"
  517. doAssert $foo == "file:relative"
  518. # isAbsolute tests
  519. block:
  520. doAssert "www.google.com".parseUri().isAbsolute() == false
  521. doAssert "http://www.google.com".parseUri().isAbsolute() == true
  522. doAssert "file:/dir/file".parseUri().isAbsolute() == true
  523. doAssert "file://localhost/dir/file".parseUri().isAbsolute() == true
  524. doAssert "urn:ISSN:1535-3613".parseUri().isAbsolute() == true
  525. # path-relative URL *relative
  526. doAssert "about".parseUri().isAbsolute == false
  527. doAssert "about/staff.html".parseUri().isAbsolute == false
  528. doAssert "about/staff.html?".parseUri().isAbsolute == false
  529. doAssert "about/staff.html?parameters".parseUri().isAbsolute == false
  530. # absolute-path-relative URL *relative
  531. doAssert "/".parseUri().isAbsolute == false
  532. doAssert "/about".parseUri().isAbsolute == false
  533. doAssert "/about/staff.html".parseUri().isAbsolute == false
  534. doAssert "/about/staff.html?".parseUri().isAbsolute == false
  535. doAssert "/about/staff.html?parameters".parseUri().isAbsolute == false
  536. # scheme-relative URL *relative
  537. doAssert "//username:password@example.com:8888".parseUri().isAbsolute == false
  538. doAssert "//username@example.com".parseUri().isAbsolute == false
  539. doAssert "//example.com".parseUri().isAbsolute == false
  540. doAssert "//example.com/".parseUri().isAbsolute == false
  541. doAssert "//example.com/about".parseUri().isAbsolute == false
  542. doAssert "//example.com/about/staff.html".parseUri().isAbsolute == false
  543. doAssert "//example.com/about/staff.html?".parseUri().isAbsolute == false
  544. doAssert "//example.com/about/staff.html?parameters".parseUri().isAbsolute == false
  545. # absolute URL *absolute
  546. doAssert "https://username:password@example.com:8888".parseUri().isAbsolute == true
  547. doAssert "https://username@example.com".parseUri().isAbsolute == true
  548. doAssert "https://example.com".parseUri().isAbsolute == true
  549. doAssert "https://example.com/".parseUri().isAbsolute == true
  550. doAssert "https://example.com/about".parseUri().isAbsolute == true
  551. doAssert "https://example.com/about/staff.html".parseUri().isAbsolute == true
  552. doAssert "https://example.com/about/staff.html?".parseUri().isAbsolute == true
  553. doAssert "https://example.com/about/staff.html?parameters".parseUri().isAbsolute == true
  554. echo("All good!")