URL.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. #include "../../Include/RmlUi/Core/URL.h"
  2. #include "../../Include/RmlUi/Core/Log.h"
  3. #include "../../Include/RmlUi/Core/StringUtilities.h"
  4. #include <stdio.h>
  5. #include <string.h>
  6. namespace Rml {
  7. const char* DEFAULT_PROTOCOL = "file";
  8. URL::URL()
  9. {
  10. port = 0;
  11. url_dirty = false;
  12. }
  13. URL::URL(const String& _url)
  14. {
  15. port = 0;
  16. RMLUI_VERIFY(SetURL(_url));
  17. }
  18. URL::URL(const char* _url)
  19. {
  20. port = 0;
  21. RMLUI_VERIFY(SetURL(_url));
  22. }
  23. URL::~URL() {}
  24. bool URL::SetURL(const String& _url)
  25. {
  26. url_dirty = false;
  27. url = _url;
  28. // Make sure an Empty URL is completely Empty.
  29. if (url.empty())
  30. {
  31. protocol.clear();
  32. login.clear();
  33. password.clear();
  34. host.clear();
  35. port = 0;
  36. path.clear();
  37. file_name.clear();
  38. extension.clear();
  39. return true;
  40. }
  41. // Find the protocol. This consists of the string appearing before the
  42. // '://' token (ie, file://, http://).
  43. const char* host_begin = strchr(_url.c_str(), ':');
  44. if (nullptr != host_begin)
  45. {
  46. protocol = String(_url.c_str(), host_begin);
  47. if (0 != strncmp(host_begin, "://", 3))
  48. {
  49. char malformed_terminator[4] = {0, 0, 0, 0};
  50. strncpy(malformed_terminator, host_begin, 3);
  51. Log::Message(Log::LT_ERROR, "Malformed protocol identifier found in URL %s; expected %s://, found %s%s.\n", _url.c_str(),
  52. protocol.c_str(), protocol.c_str(), malformed_terminator);
  53. return false;
  54. }
  55. host_begin += 3;
  56. }
  57. else
  58. {
  59. protocol = DEFAULT_PROTOCOL;
  60. host_begin = _url.c_str();
  61. }
  62. // We only want to look for a host if a protocol was specified.
  63. const char* path_begin;
  64. if (host_begin != _url.c_str())
  65. {
  66. // Find the host. This is the string appearing after the protocol or after
  67. // the username:password combination, and terminated either with a colon,
  68. // if a port is specified, or a forward slash if there is no port.
  69. // Check for a login pair
  70. const char* at_symbol = strchr(host_begin, '@');
  71. if (at_symbol)
  72. {
  73. String login_password;
  74. login_password = String(host_begin, at_symbol);
  75. host_begin = at_symbol + 1;
  76. const char* password_ptr = strchr(login_password.c_str(), ':');
  77. if (password_ptr)
  78. {
  79. login = String(login_password.c_str(), password_ptr);
  80. password = String(password_ptr + 1);
  81. }
  82. else
  83. {
  84. login = login_password;
  85. }
  86. }
  87. // Get the host portion
  88. path_begin = strchr(host_begin, '/');
  89. // Search for the colon in the host name, which will indicate a port.
  90. const char* port_begin = strchr(host_begin, ':');
  91. if (nullptr != port_begin && (nullptr == path_begin || port_begin < path_begin))
  92. {
  93. if (1 != sscanf(port_begin, ":%d", &port))
  94. {
  95. Log::Message(Log::LT_ERROR, "Malformed port number found in URL %s.\n", _url.c_str());
  96. return false;
  97. }
  98. host = String(host_begin, port_begin);
  99. // Don't continue if there is no path.
  100. if (nullptr == path_begin)
  101. {
  102. return true;
  103. }
  104. // Increment the path string past the trailing slash.
  105. ++path_begin;
  106. }
  107. else
  108. {
  109. port = -1;
  110. if (nullptr == path_begin)
  111. {
  112. host = host_begin;
  113. return true;
  114. }
  115. else
  116. {
  117. // Assign the host name, then increment the path string past the
  118. // trailing slash.
  119. host = String(host_begin, path_begin);
  120. ++path_begin;
  121. }
  122. }
  123. }
  124. else
  125. {
  126. path_begin = _url.c_str();
  127. }
  128. // Check for parameters
  129. String path_segment;
  130. const char* parameters = strchr(path_begin, '?');
  131. if (parameters)
  132. {
  133. // Pull the path segment out, so further processing doesn't read the parameters
  134. path_segment = String(path_begin, parameters);
  135. path_begin = path_segment.c_str();
  136. // Loop through all parameters, loading them
  137. StringList parameter_list;
  138. StringUtilities::ExpandString(parameter_list, parameters + 1, '&');
  139. for (size_t i = 0; i < parameter_list.size(); i++)
  140. {
  141. // Split into key and value
  142. StringList key_value;
  143. StringUtilities::ExpandString(key_value, parameter_list[i], '=');
  144. key_value[0] = UrlDecode(key_value[0]);
  145. if (key_value.size() == 2)
  146. this->parameters[key_value[0]] = UrlDecode(key_value[1]);
  147. else
  148. this->parameters[key_value[0]] = "";
  149. }
  150. }
  151. // Find the path. This is the string appearing after the host, terminated
  152. // by the last forward slash.
  153. const char* file_name_begin = strrchr(path_begin, '/');
  154. if (nullptr == file_name_begin)
  155. {
  156. // No path!
  157. file_name_begin = path_begin;
  158. path = "";
  159. }
  160. else
  161. {
  162. // Copy the path including the trailing slash.
  163. path = String(path_begin, ++file_name_begin);
  164. // Normalise the path, stripping any ../'s from it
  165. size_t parent_dir_pos = String::npos;
  166. while ((parent_dir_pos = path.find("/../")) != String::npos && parent_dir_pos != 0)
  167. {
  168. // Find the start of the parent directory.
  169. size_t parent_dir_start_pos = path.rfind('/', parent_dir_pos - 1);
  170. if (parent_dir_start_pos == String::npos)
  171. parent_dir_start_pos = 0;
  172. else
  173. parent_dir_start_pos += 1;
  174. // Strip out the parent dir and the /..
  175. path.erase(parent_dir_start_pos, parent_dir_pos - parent_dir_start_pos + 4);
  176. // We've altered the URL, mark it dirty
  177. url_dirty = true;
  178. }
  179. }
  180. // Find the file name. This is the string after the trailing slash of the
  181. // path, and just before the extension.
  182. const char* extension_begin = strrchr(file_name_begin, '.');
  183. if (nullptr == extension_begin)
  184. {
  185. file_name = file_name_begin;
  186. extension = "";
  187. }
  188. else
  189. {
  190. file_name = String(file_name_begin, extension_begin);
  191. extension = extension_begin + 1;
  192. }
  193. return true;
  194. }
  195. const String& URL::GetURL() const
  196. {
  197. if (url_dirty)
  198. ConstructURL();
  199. return url;
  200. }
  201. bool URL::SetProtocol(const String& _protocol)
  202. {
  203. protocol = _protocol;
  204. url_dirty = true;
  205. return true;
  206. }
  207. const String& URL::GetProtocol() const
  208. {
  209. return protocol;
  210. }
  211. bool URL::SetLogin(const String& _login)
  212. {
  213. login = _login;
  214. url_dirty = true;
  215. return true;
  216. }
  217. const String& URL::GetLogin() const
  218. {
  219. return login;
  220. }
  221. bool URL::SetPassword(const String& _password)
  222. {
  223. password = _password;
  224. url_dirty = true;
  225. return true;
  226. }
  227. const String& URL::GetPassword() const
  228. {
  229. return password;
  230. }
  231. bool URL::SetHost(const String& _host)
  232. {
  233. host = _host;
  234. url_dirty = true;
  235. return true;
  236. }
  237. const String& URL::GetHost() const
  238. {
  239. return host;
  240. }
  241. bool URL::SetPort(int _port)
  242. {
  243. port = _port;
  244. url_dirty = true;
  245. return true;
  246. }
  247. int URL::GetPort() const
  248. {
  249. return port;
  250. }
  251. bool URL::SetPath(const String& _path)
  252. {
  253. path = _path;
  254. url_dirty = true;
  255. return true;
  256. }
  257. bool URL::PrefixPath(const String& prefix)
  258. {
  259. // If there's no trailing slash on the end of the prefix, add one.
  260. if (!prefix.empty() && prefix[prefix.size() - 1] != '/')
  261. path = prefix + "/" + path;
  262. else
  263. path = prefix + path;
  264. url_dirty = true;
  265. return true;
  266. }
  267. const String& URL::GetPath() const
  268. {
  269. return path;
  270. }
  271. bool URL::SetFileName(const String& _file_name)
  272. {
  273. file_name = _file_name;
  274. url_dirty = true;
  275. return true;
  276. }
  277. const String& URL::GetFileName() const
  278. {
  279. return file_name;
  280. }
  281. bool URL::SetExtension(const String& _extension)
  282. {
  283. extension = _extension;
  284. url_dirty = true;
  285. return true;
  286. }
  287. const String& URL::GetExtension() const
  288. {
  289. return extension;
  290. }
  291. const URL::Parameters& URL::GetParameters() const
  292. {
  293. return parameters;
  294. }
  295. void URL::SetParameter(const String& key, const String& value)
  296. {
  297. parameters[key] = value;
  298. url_dirty = true;
  299. }
  300. void URL::SetParameters(const Parameters& _parameters)
  301. {
  302. parameters = _parameters;
  303. url_dirty = true;
  304. }
  305. void URL::ClearParameters()
  306. {
  307. parameters.clear();
  308. }
  309. String URL::GetPathedFileName() const
  310. {
  311. String pathed_file_name = path;
  312. // Append the file name.
  313. pathed_file_name += file_name;
  314. // Append the extension.
  315. if (!extension.empty())
  316. {
  317. pathed_file_name += ".";
  318. pathed_file_name += extension;
  319. }
  320. return pathed_file_name;
  321. }
  322. String URL::GetQueryString() const
  323. {
  324. String query_string;
  325. int count = 0;
  326. for (Parameters::const_iterator itr = parameters.begin(); itr != parameters.end(); ++itr)
  327. {
  328. query_string += (count == 0) ? "" : "&";
  329. query_string += UrlEncode((*itr).first);
  330. query_string += "=";
  331. query_string += UrlEncode((*itr).second);
  332. count++;
  333. }
  334. return query_string;
  335. }
  336. bool URL::operator<(const URL& rhs) const
  337. {
  338. if (url_dirty)
  339. ConstructURL();
  340. if (rhs.url_dirty)
  341. rhs.ConstructURL();
  342. return url < rhs.url;
  343. }
  344. void URL::ConstructURL() const
  345. {
  346. url = "";
  347. // Append the protocol.
  348. if (!protocol.empty() && !host.empty())
  349. {
  350. url = protocol;
  351. url += "://";
  352. }
  353. // Append login and password
  354. if (!login.empty())
  355. {
  356. url += login;
  357. if (!password.empty())
  358. {
  359. url += ":";
  360. url += password;
  361. }
  362. url += "@";
  363. }
  364. RMLUI_ASSERTMSG(password.empty() || (!password.empty() && !login.empty()), "Can't have a password without a login!");
  365. // Append the host.
  366. url += host;
  367. // Only check ports if there is some host/protocol part
  368. if (!url.empty())
  369. {
  370. if (port > 0)
  371. {
  372. RMLUI_ASSERTMSG(!host.empty(), "Can't have a port without a host!");
  373. constexpr size_t port_buffer_size = 16;
  374. char port_string[port_buffer_size];
  375. snprintf(port_string, port_buffer_size, ":%d/", port);
  376. url += port_string;
  377. }
  378. else
  379. {
  380. url += "/";
  381. }
  382. }
  383. // Append the path.
  384. if (!path.empty())
  385. {
  386. url += path;
  387. }
  388. // Append the file name.
  389. url += file_name;
  390. // Append the extension.
  391. if (!extension.empty())
  392. {
  393. url += ".";
  394. url += extension;
  395. }
  396. // Append parameters
  397. if (!parameters.empty())
  398. {
  399. url += "?";
  400. url += GetQueryString();
  401. }
  402. url_dirty = false;
  403. }
  404. String URL::UrlEncode(const String& value)
  405. {
  406. String encoded;
  407. constexpr size_t hex_buffer_size = 4;
  408. char hex[hex_buffer_size] = {0, 0, 0, 0};
  409. encoded.clear();
  410. const char* value_c = value.c_str();
  411. for (String::size_type i = 0; value_c[i]; i++)
  412. {
  413. char c = value_c[i];
  414. if (IsUnreservedChar(c))
  415. encoded += c;
  416. else
  417. {
  418. snprintf(hex, hex_buffer_size, "%%%02X", c);
  419. encoded += hex;
  420. }
  421. }
  422. return encoded;
  423. }
  424. String URL::UrlDecode(const String& value)
  425. {
  426. String decoded;
  427. decoded.clear();
  428. const char* value_c = value.c_str();
  429. String::size_type value_len = value.size();
  430. for (String::size_type i = 0; i < value_len; i++)
  431. {
  432. char c = value_c[i];
  433. if (c == '+')
  434. {
  435. decoded += ' ';
  436. }
  437. else if (c == '%')
  438. {
  439. char* endp;
  440. String t = value.substr(i + 1, 2);
  441. int ch = strtol(t.c_str(), &endp, 16);
  442. if (*endp == '\0')
  443. decoded += char(ch);
  444. else
  445. decoded += t;
  446. i += 2;
  447. }
  448. else
  449. {
  450. decoded += c;
  451. }
  452. }
  453. return decoded;
  454. }
  455. bool URL::IsUnreservedChar(const char in)
  456. {
  457. switch (in)
  458. {
  459. case '0':
  460. case '1':
  461. case '2':
  462. case '3':
  463. case '4':
  464. case '5':
  465. case '6':
  466. case '7':
  467. case '8':
  468. case '9':
  469. case 'a':
  470. case 'b':
  471. case 'c':
  472. case 'd':
  473. case 'e':
  474. case 'f':
  475. case 'g':
  476. case 'h':
  477. case 'i':
  478. case 'j':
  479. case 'k':
  480. case 'l':
  481. case 'm':
  482. case 'n':
  483. case 'o':
  484. case 'p':
  485. case 'q':
  486. case 'r':
  487. case 's':
  488. case 't':
  489. case 'u':
  490. case 'v':
  491. case 'w':
  492. case 'x':
  493. case 'y':
  494. case 'z':
  495. case 'A':
  496. case 'B':
  497. case 'C':
  498. case 'D':
  499. case 'E':
  500. case 'F':
  501. case 'G':
  502. case 'H':
  503. case 'I':
  504. case 'J':
  505. case 'K':
  506. case 'L':
  507. case 'M':
  508. case 'N':
  509. case 'O':
  510. case 'P':
  511. case 'Q':
  512. case 'R':
  513. case 'S':
  514. case 'T':
  515. case 'U':
  516. case 'V':
  517. case 'W':
  518. case 'X':
  519. case 'Y':
  520. case 'Z':
  521. case '-':
  522. case '.':
  523. case '_':
  524. case '~': return true;
  525. default: break;
  526. }
  527. return false;
  528. }
  529. } // namespace Rml