Panda3D
urlSpec.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file urlSpec.cxx
10  * @author drose
11  * @date 2002-09-24
12  */
13 
14 #include "urlSpec.h"
15 #include "filename.h"
16 #include "string_utils.h"
17 
18 #include <ctype.h>
19 
20 using std::istream;
21 using std::ostream;
22 using std::ostringstream;
23 using std::setfill;
24 using std::setw;
25 using std::string;
26 
27 /**
28  *
29  */
30 URLSpec::
31 URLSpec() {
32  _port = 0;
33  _flags = 0;
34  _scheme_end = 0;
35  _username_start = 0;
36  _username_end = 0;
37  _server_start = 0;
38  _server_end = 0;
39  _port_start = 0;
40  _port_end = 0;
41  _path_start = 0;
42  _path_end = 0;
43  _query_start = 0;
44 }
45 
46 /**
47  * Creates a URLSpec by appending a path to the end of the old URLSpec,
48  * inserting an intervening forward slash if necessary.
49  */
50 URLSpec::
51 URLSpec(const URLSpec &url, const Filename &path) {
52  (*this) = url;
53  if (!path.empty()) {
54  string dirname = get_path();
55 
56  // Check if the path already ends in a slash.
57  if (!dirname.empty() && dirname[dirname.size() - 1] == '/') {
58  if (path[0] == '/') {
59  // And the filename begins with one. Remove the extra slash.
60  dirname.resize(dirname.size() - 1);
61  }
62  } else {
63  if (path[0] != '/') {
64  // Neither has a slash, so insert one.
65  dirname += '/';
66  }
67  }
68  set_path(dirname + path.get_fullpath());
69  }
70 }
71 
72 /**
73  * Returns a number less than zero if this URLSpec sorts before the other one,
74  * greater than zero if it sorts after, or zero if they are equivalent.
75  */
76 int URLSpec::
77 compare_to(const URLSpec &other) const {
78  int cmp;
79  if (has_scheme() != other.has_scheme()) {
80  return (has_scheme() < other.has_scheme()) ? -1 : 1;
81  }
82  if (has_scheme()) {
83  cmp = cmp_nocase(get_scheme(), other.get_scheme());
84  if (cmp != 0) {
85  return cmp;
86  }
87  }
88  if (has_username() != other.has_username()) {
89  return (has_username() < other.has_username()) ? -1 : 1;
90  }
91  if (has_username()) {
92  cmp = get_username().compare(other.get_username());
93  if (cmp != 0) {
94  return cmp;
95  }
96  }
97  if (has_server() != other.has_server()) {
98  return (has_server() < other.has_server()) ? -1 : 1;
99  }
100  if (has_server()) {
101  cmp = cmp_nocase(get_server(), other.get_server());
102  if (cmp != 0) {
103  return cmp;
104  }
105  }
106  return get_path_and_query().compare(other.get_path_and_query());
107 }
108 
109 /**
110  *
111  */
112 size_t URLSpec::
113 get_hash() const {
114  size_t hash = 0;
115  hash = int_hash::add_hash(hash, _flags & (F_has_scheme | F_has_username | F_has_server));
116  if (has_scheme()) {
117  hash = string_hash::add_hash(hash, downcase(get_scheme()));
118  }
119  if (has_username()) {
120  hash = string_hash::add_hash(hash, get_username());
121  }
122  if (has_server()) {
123  hash = string_hash::add_hash(hash, downcase(get_server()));
124  }
125  hash = int_hash::add_hash(hash, get_port());
127  return hash;
128 }
129 
130 /**
131  * Returns the scheme specified by the URL, or empty string if no scheme is
132  * specified.
133  */
134 string URLSpec::
135 get_scheme() const {
136  if (has_scheme()) {
137  return _url.substr(0, _scheme_end);
138  }
139  return string();
140 }
141 
142 /**
143  * Returns the port number specified by the URL, or the default port if not
144  * specified.
145  */
146 uint16_t URLSpec::
147 get_port() const {
148  if (has_port()) {
149  return _port;
150  }
152 }
153 
154 /**
155  * Returns true if the port number encoded in this URL is the default port
156  * number for the scheme (or if there is no port number), or false if it is a
157  * nonstandard port.
158  */
159 bool URLSpec::
161  if (!has_port()) {
162  return true;
163  }
164  return (_port == get_default_port_for_scheme(get_scheme()));
165 }
166 
167 /**
168  * Returns the default port number for the indicated scheme, or 0 if there is
169  * no known default.
170  */
171 int URLSpec::
172 get_default_port_for_scheme(const string &scheme) {
173  if (scheme == "http" || scheme.empty()) {
174  return 80;
175 
176  } else if (scheme == "https") {
177  return 443;
178 
179  } else if (scheme == "socks") {
180  return 1080;
181  }
182 
183  return 0;
184 }
185 
186 /**
187  * Returns a string consisting of the server name, followed by a colon,
188  * followed by the port number. If the port number is not explicitly given in
189  * the URL, this string will include the implicit port number.
190  * If the server is an IPv6 address, it will be enclosed in square brackets.
191  */
192 string URLSpec::
193 get_server_and_port() const {
194  ostringstream strm;
195  string server = get_server();
196  if (server.find(':') != string::npos) {
197  // Protect an IPv6 address by enclosing it in square brackets.
198  strm << '[' << server << ']';
199 
200  } else {
201  if (!has_port()) {
202  return server;
203  }
204  strm << server;
205  }
206  if (has_port()) {
207  strm << ":" << get_port();
208  }
209  return strm.str();
210 }
211 
212 /**
213  * Returns the path specified by the URL, or "/" if no path is specified.
214  */
215 string URLSpec::
216 get_path() const {
217  if (has_path()) {
218  return _url.substr(_path_start, _path_end - _path_start);
219  }
220  return "/";
221 }
222 
223 /**
224  * Returns the path (or "/" if no path is specified), followed by the query if
225  * it is specified.
226  */
227 string URLSpec::
229  if (has_path()) {
230  return _url.substr(_path_start);
231  }
232  if (has_query()) {
233  return "/?" + get_query();
234  }
235  return "/";
236 }
237 
238 /**
239  * Replaces the scheme part of the URL specification.
240  */
241 void URLSpec::
242 set_scheme(const string &scheme) {
243  int length_adjust;
244 
245  // The scheme is always converted to lowercase.
246  string lc_scheme;
247  lc_scheme.reserve(scheme.length());
248  for (string::const_iterator si = scheme.begin(); si != scheme.end(); ++si) {
249  lc_scheme += tolower(*si);
250  }
251 
252  if (lc_scheme.empty()) {
253  // Remove the scheme specification.
254  if (!has_scheme()) {
255  return;
256  }
257  // Increment over the trailing colon so we can remove that too.
258  _scheme_end++;
259  length_adjust = -(int)_scheme_end;
260  _url = _url.substr(_scheme_end);
261  _flags &= ~F_has_scheme;
262 
263  } else if (!has_scheme()) {
264  // Insert a new scheme specification. The user may or may not have
265  // specified a colon.
266  if (lc_scheme[lc_scheme.length() - 1] == ':') {
267  length_adjust = lc_scheme.length();
268  _url = lc_scheme + _url;
269 
270  } else {
271  length_adjust = lc_scheme.length() + 1;
272  _url = lc_scheme + ":" + _url;
273  }
274 
275  // Since the length_adjust flag, above, now accounts for the colon,
276  // subtract one from _scheme_end (which should not include the colon).
277  _scheme_end--;
278  _flags |= F_has_scheme;
279 
280  } else {
281  // Replace the existing scheme specification. Since the existing scheme
282  // will already be trailed by a colon, remove the colon from the string if
283  // the user appended one.
284  if (lc_scheme[lc_scheme.length() - 1] == ':') {
285  lc_scheme = lc_scheme.substr(0, lc_scheme.length() - 1);
286  }
287 
288  int old_length = (int)_scheme_end;
289  length_adjust = scheme.length() - old_length;
290  _url = lc_scheme + _url.substr(_scheme_end);
291  }
292 
293  _scheme_end += length_adjust;
294  _username_start += length_adjust;
295  _username_end += length_adjust;
296  _server_start += length_adjust;
297  _server_end += length_adjust;
298  _port_start += length_adjust;
299  _port_end += length_adjust;
300  _path_start += length_adjust;
301  _path_end += length_adjust;
302  _query_start += length_adjust;
303 }
304 
305 /**
306  * Replaces the authority part of the URL specification. This includes the
307  * username, server, and port.
308  */
309 void URLSpec::
310 set_authority(const string &authority) {
311  int length_adjust;
312  int extra_slash_adjust = 0;
313 
314  if (authority.empty()) {
315  // Remove the authority specification.
316  if (!has_authority()) {
317  return;
318  }
319  _username_start -= 2;
320  length_adjust = -((int)_port_end - (int)_username_start);
321  _url = _url.substr(0, _username_start) + _url.substr(_port_end);
322  _flags &= ~(F_has_authority | F_has_username | F_has_server | F_has_port);
323 
324  _username_end = _username_start;
325  _server_start = _username_start;
326  _server_end = _username_start;
327  _port_start = _username_start;
328 
329  } else if (!has_authority()) {
330  // Insert a new authority specification.
331  length_adjust = authority.length() + 2;
332 
333  string extra_slash;
334  if (has_path() && _url[_path_start] != '/') {
335  // If we have a path but it doesn't begin with a slash, it should.
336  extra_slash = '/';
337  extra_slash_adjust = 1;
338  }
339  _url = _url.substr(0, _username_start) + "//" + authority + extra_slash + _url.substr(_port_end);
340  _flags |= F_has_authority;
341  _username_start += 2;
342 
343  } else {
344  // Replace an existing authority specification.
345  int old_length = (int)_port_end - (int)_username_start;
346  length_adjust = authority.length() - old_length;
347  _url = _url.substr(0, _username_start) + authority + _url.substr(_port_end);
348  }
349 
350  _port_end += length_adjust;
351  _path_start += length_adjust;
352  _path_end += length_adjust + extra_slash_adjust;
353  _query_start += length_adjust + extra_slash_adjust;
354 
355  parse_authority();
356 }
357 
358 /**
359  * Replaces the username part of the URL specification.
360  */
361 void URLSpec::
362 set_username(const string &username) {
363  if (username.empty() && !has_authority()) {
364  return;
365  }
366  string authority;
367 
368  if (!username.empty()) {
369  authority = username + "@";
370  }
371 
372  string server = get_server();
373  if (server.find(':') != string::npos) {
374  // Protect an IPv6 address by enclosing it in square brackets.
375  authority += "[" + server + "]";
376  } else {
377  authority += server;
378  }
379 
380  if (has_port()) {
381  authority += ":";
382  authority += get_port_str();
383  }
384 
385  set_authority(authority);
386 }
387 
388 /**
389  * Replaces the server part of the URL specification.
390  * Unlike set_server_and_port, this method does not require IPv6 addresses to
391  * be enclosed in square brackets.
392  */
393 void URLSpec::
394 set_server(const string &server) {
395  if (server.empty() && !has_authority()) {
396  return;
397  }
398  string authority;
399 
400  if (has_username()) {
401  authority = get_username() + "@";
402  }
403 
404  if (server.find(':') != string::npos) {
405  // Protect an IPv6 address by enclosing it in square brackets.
406  authority += "[" + server + "]";
407  } else {
408  authority += server;
409  }
410 
411  if (has_port()) {
412  authority += ":";
413  authority += get_port_str();
414  }
415 
416  set_authority(authority);
417 }
418 
419 /**
420  * Replaces the port part of the URL specification.
421  */
422 void URLSpec::
423 set_port(const string &port) {
424  if (port.empty() && !has_authority()) {
425  return;
426  }
427  string authority;
428 
429  if (has_username()) {
430  authority = get_username() + "@";
431  }
432 
433  string server = get_server();
434  if (server.find(':') != string::npos) {
435  // Protect an IPv6 address by enclosing it in square brackets.
436  authority += "[" + server + "]";
437  } else {
438  authority += server;
439  }
440 
441  if (!port.empty()) {
442  authority += ":";
443  authority += port;
444  }
445 
446  set_authority(authority);
447 }
448 
449 /**
450  * Replaces the port part of the URL specification, given a numeric port
451  * number.
452  */
453 void URLSpec::
454 set_port(uint16_t port) {
455  set_port(format_string(port));
456 }
457 
458 /**
459  * Replaces the server and port parts of the URL specification simultaneously.
460  * The input string should be of the form "server:port", or just "server" to
461  * make the port number implicit.
462  * Any IPv6 address must be enclosed in square brackets.
463  */
464 void URLSpec::
465 set_server_and_port(const string &server_and_port) {
466  if (server_and_port.empty() && !has_authority()) {
467  return;
468  }
469  string authority;
470 
471  if (has_username()) {
472  authority = get_username() + "@";
473  }
474  authority += server_and_port;
475  set_authority(authority);
476 }
477 
478 /**
479  * Replaces the path part of the URL specification.
480  */
481 void URLSpec::
482 set_path(const string &path) {
483  int length_adjust;
484 
485  if (path.empty()) {
486  // Remove the path specification.
487  if (!has_path()) {
488  return;
489  }
490  length_adjust = -((int)_path_end - (int)_path_start);
491  _url = _url.substr(0, _path_start) + _url.substr(_path_end);
492  _flags &= ~F_has_path;
493 
494  } else if (!has_path()) {
495  // Insert a new path specification.
496  string cpath = path;
497  if (cpath[0] != '/') {
498  // Paths must always begin with a slash.
499  cpath = '/' + cpath;
500  }
501  length_adjust = cpath.length();
502 
503  _url = _url.substr(0, _path_start) + cpath + _url.substr(_path_end);
504  _flags |= F_has_path;
505 
506  } else {
507  // Replace an existing path specification.
508  string cpath = path;
509  if (cpath[0] != '/') {
510  // Paths must always begin with a slash.
511  cpath = '/' + cpath;
512  }
513  int old_length = (int)_path_end - (int)_path_start;
514  length_adjust = cpath.length() - old_length;
515  _url = _url.substr(0, _path_start) + cpath + _url.substr(_path_end);
516  }
517 
518  _path_end += length_adjust;
519  _query_start += length_adjust;
520 }
521 
522 /**
523  * Replaces the query part of the URL specification.
524  */
525 void URLSpec::
526 set_query(const string &query) {
527  if (query.empty()) {
528  // Remove the query specification.
529  if (!has_query()) {
530  return;
531  }
532  _query_start--;
533  _url = _url.substr(0, _query_start);
534  _flags &= ~F_has_query;
535 
536  } else if (!has_query()) {
537  // Insert a new query specification.
538  _url = _url.substr(0, _query_start) + "?" + query;
539  _flags |= F_has_query;
540  _query_start++;
541 
542  } else {
543  // Replace an existing query specification.
544  _url = _url.substr(0, _query_start) + query;
545  }
546 }
547 
548 /**
549  * Completely replaces the URL with the indicated string. If
550  * server_name_expected is true, it is a hint that an undecorated URL is
551  * probably a server name, not a local filename.
552  */
553 void URLSpec::
554 set_url(const string &url, bool server_name_expected) {
555  size_t p, q;
556 
557  // Omit leading and trailing whitespace.
558  p = 0;
559  while (p < url.length() && isspace(url[p])) {
560  p++;
561  }
562  q = url.length();
563  while (q > p && isspace(url[q - 1])) {
564  q--;
565  }
566 
567  _url = url.substr(p, q - p);
568  _flags = 0;
569 
570  if (url.empty()) {
571  // No server name on an empty string.
572  server_name_expected = false;
573  }
574 
575  // First, replace backslashes with forward slashes, since this is a common
576  // mistake among Windows users. But don't do this after an embedded
577  // question mark, which begins parameters sent directly to the host (and
578  // maybe these parameters should include backslashes).
579  for (p = 0; p < _url.length() && _url[p] != '?'; p++) {
580  if (_url[p] == '\\') {
581  _url[p] = '/';
582  }
583  }
584 
585  // Now normalize the percent-encoding sequences by making them uppercase.
586  for (p = 0; p + 2 < _url.length();) {
587  if (_url[p++] == '%') {
588  if (_url[p] != '%') {
589  _url[p] = toupper(_url[p]);
590  ++p;
591  _url[p] = toupper(_url[p]);
592  }
593  ++p;
594  }
595  }
596 
597  // What have we got?
598  _flags = 0;
599  _port = 0;
600 
601  // Look for the scheme specification.
602  size_t start = 0;
603 
604  _scheme_end = start;
605  size_t next = _url.find_first_of(":/", start);
606  if (next < _url.length() - 1 && _url.substr(next, 2) == ":/") {
607  // We have a scheme.
608  _flags |= F_has_scheme;
609  _scheme_end = next;
610 
611  // Ensure the scheme is lowercase.
612  for (size_t p = 0; p < _scheme_end; ++p) {
613  _url[p] = tolower(_url[p]);
614  }
615 
616  start = next + 1;
617  }
618 
619  // Look for the authority specification, which may include any of username,
620  // server, andor port.
621  _username_start = start;
622  _username_end = start;
623  _server_start = start;
624  _server_end = start;
625  _port_start = start;
626  _port_end = start;
627 
628  // Try to determine if an authority is present. It is will generally be
629  // present if a scheme was present; also, we have a hint passed in from the
630  // context as to whether we expect an authority (e.g. a server name) to be
631  // present.
632  bool has_authority = (has_scheme() || server_name_expected);
633 
634  // We also know we have an authority if the url contains two slashes at this
635  // point.
636  bool leading_slashes =
637  (start < _url.length() - 1 && _url.substr(start, 2) == "//");
638  if (leading_slashes) {
639  has_authority = true;
640  }
641 
642  if (has_authority) {
643  // Now that we know we have an authority, we should ensure there are two
644  // slashes here, since there should be before the authority.
645  if (!leading_slashes) {
646  if (start < _url.length() && _url[start] == '/') {
647  // Well, at least we had one slash. Double it.
648  _url = _url.substr(0, start + 1) + _url.substr(start);
649  } else {
650  // No slashes at all. Insert them.
651  _url = _url.substr(0, start) + "//" + _url.substr(start);
652  }
653  }
654 
655  // Begin the actual authority specification.
656  start += 2;
657  _flags |= F_has_authority;
658  _username_start = start;
659  _port_end = _url.find_first_of("/?", start);
660  if (_port_end == string::npos) {
661  _port_end = _url.length();
662  }
663  parse_authority();
664  start = _port_end;
665  }
666 
667  // Everything up to the ?, if any, is the path.
668  _path_start = start;
669  _path_end = start;
670  if (start < _url.length() && url[start] != '?') {
671  // We have a path.
672  _flags |= F_has_path;
673  _path_start = start;
674  _path_end = _url.find("?", _path_start);
675  if (_path_end == string::npos) {
676  _path_end = _url.length();
677  }
678  start = _path_end;
679  }
680 
681  // Everything after the ? is the query.
682  _query_start = start;
683  if (start < _url.length()) {
684  nassertv(_url[start] == '?');
685  _flags |= F_has_query;
686  _query_start++;
687  }
688 }
689 
690 /**
691  *
692  */
693 bool URLSpec::
694 input(istream &in) {
695  string url;
696  in >> url;
697  if (!in) {
698  return false;
699  }
700  set_url(url);
701  return true;
702 }
703 
704 /**
705  *
706  */
707 void URLSpec::
708 output(ostream &out) const {
709  out << get_url();
710 }
711 
712 /**
713  * Returns the source string with all "unsafe" characters quoted, making a
714  * string suitable for placing in a URL. Letters, digits, and the underscore,
715  * comma, period, and hyphen characters, as well as any included in the safe
716  * string, are left alone; all others are converted to hex representation.
717  */
718 string URLSpec::
719 quote(const string &source, const string &safe) {
720  ostringstream result;
721  result << std::hex << setfill('0');
722 
723  for (string::const_iterator si = source.begin(); si != source.end(); ++si) {
724  char ch = (*si);
725  switch (ch) {
726  case '_':
727  case ',':
728  case '.':
729  case '-':
730  // Safe character.
731  result << ch;
732  break;
733 
734  default:
735  if (isalnum(ch)) {
736  // Letters and digits are safe.
737  result << ch;
738 
739  } else if (safe.find(ch) != string::npos) {
740  // If it's listed in "safe", it's safe.
741  result << ch;
742 
743  } else {
744  // Otherwise, escape it.
745  result << '%' << setw(2) << (int)ch;
746  }
747  }
748  }
749 
750  return result.str();
751 }
752 
753 /**
754  * Behaves like quote() with the additional behavior of replacing spaces with
755  * plus signs.
756  */
757 string URLSpec::
758 quote_plus(const string &source, const string &safe) {
759  ostringstream result;
760  result << std::hex << setfill('0');
761 
762  for (string::const_iterator si = source.begin(); si != source.end(); ++si) {
763  char ch = (*si);
764  switch (ch) {
765  case '_':
766  case ',':
767  case '.':
768  case '-':
769  // Safe character.
770  result << ch;
771  break;
772 
773  case ' ':
774  result << '+';
775  break;
776 
777  default:
778  if (isalnum(ch)) {
779  // Letters and digits are safe.
780  result << ch;
781 
782  } else if (safe.find(ch) != string::npos) {
783  // If it's listed in "safe", it's safe.
784  result << ch;
785 
786  } else {
787  // Otherwise, escape it.
788  result << '%' << setw(2) << (int)ch;
789  }
790  }
791  }
792 
793  return result.str();
794 }
795 
796 /**
797  * Reverses the operation of quote(): converts escaped characters of the form
798  * "%xx" to their ascii equivalent.
799  */
800 string URLSpec::
801 unquote(const string &source) {
802  string result;
803 
804  size_t p = 0;
805  while (p < source.length()) {
806  if (source[p] == '%' && p + 2 < source.length()) {
807  int hex = 0;
808  p++;
809  for (int i = 0; i < 2; i++) {
810  int value;
811  char ch = source[p + i];
812  if (isdigit(ch)) {
813  value = ch - '0';
814  } else {
815  value = tolower(ch) - 'a' + 10;
816  }
817  hex = (hex << 4) | value;
818  }
819  result += (char)hex;
820  p += 2;
821 
822  } else {
823  result += source[p];
824  p++;
825  }
826  }
827 
828  return result;
829 }
830 
831 /**
832  * Reverses the operation of quote_plus(): converts escaped characters of the
833  * form "%xx" to their ascii equivalent, and also converts plus signs to
834  * spaces.
835  */
836 string URLSpec::
837 unquote_plus(const string &source) {
838  string result;
839 
840  size_t p = 0;
841  while (p < source.length()) {
842  if (source[p] == '%' && p + 2 < source.length()) {
843  int hex = 0;
844  p++;
845  for (int i = 0; i < 2; i++) {
846  int value;
847  char ch = source[p + i];
848  if (isdigit(ch)) {
849  value = ch - '0';
850  } else {
851  value = tolower(ch) - 'a' + 10;
852  }
853  hex = (hex << 4) | value;
854  }
855  result += (char)hex;
856  p += 2;
857 
858  } else if (source[p] == '+') {
859  result += ' ';
860  p++;
861 
862  } else {
863  result += source[p];
864  p++;
865  }
866  }
867 
868  return result;
869 }
870 
871 /**
872  * Assumes _url[_username_start .. _port_end - 1] is the authority component
873  * if the URL, consisting of [username@]server[:port]. Parses out the three
874  * pieces and updates the various _start and _end parameters accordingly.
875  */
876 void URLSpec::
877 parse_authority() {
878  _flags &= ~(F_has_username | F_has_server | F_has_port);
879 
880  if (!has_authority()) {
881  return;
882  }
883 
884  // Assume we don't have a username or port unless we find them.
885  _username_end = _username_start;
886  _port_start = _port_end;
887 
888  // We assume we have a server, even if it becomes the empty string.
889  _flags |= F_has_server;
890  _server_start = _username_start;
891  _server_end = _port_end;
892 
893  // Is there a username?
894  size_t at_sign = _url.find('@', _username_start);
895  if (at_sign < _port_end) {
896  // We have a username.
897  _flags |= F_has_username;
898  _username_end = at_sign;
899  _server_start = at_sign + 1;
900  }
901 
902  // Is this an IPv6 address in square brackets?
903  size_t bracket = _url.find('[', _server_start);
904  if (bracket < _port_end) {
905  // We won't include the brackets in the server name.
906  ++_server_start;
907 
908  bracket = _url.find(']');
909  if (bracket < _server_end) {
910  _server_end = bracket;
911 
912  // Is it followed directly by a port colon?
913  size_t colon = _url.find(':', _server_end);
914  if (colon < _port_end) {
915  _port_start = colon + 1;
916  }
917  }
918  } else {
919  // Is there a port?
920  size_t colon = _url.find(':', _server_start);
921  if (colon < _port_end) {
922  // Yep.
923  _server_end = colon;
924  _port_start = colon + 1;
925  }
926  }
927 
928  if (_port_start < _port_end) {
929  _flags |= F_has_port;
930 
931  // Decode the port into an integer. Don't bother to error check if it's
932  // not really an integer.
933  string port_str = _url.substr(_port_start, _port_end - _port_start);
934  _port = (uint16_t)atoi(port_str.c_str());
935  }
936 
937  // Make sure the server name is lowercase only.
938  for (size_t si = _server_start; si != _server_end; ++si) {
939  _url[si] = tolower(_url[si]);
940  }
941 
942  // Also make sure the server name doesn't end with a dot. It's happened!
943  // Silly users.
944  if (_server_end > _server_start && _url[_server_end - 1] == '.') {
945  _url = _url.substr(0, _server_end - 1) + _url.substr(_server_end);
946  _server_end--;
947  _port_start--;
948  _port_end--;
949  _path_start--;
950  _path_end--;
951  _query_start--;
952  }
953 }
A container for a URL, e.g.
Definition: urlSpec.h:28
set_query
Replaces the query part of the URL specification.
Definition: urlSpec.h:100
bool has_username() const
Returns true if the URL specifies a username (and/or password), false otherwise.
Definition: urlSpec.I:77
bool has_authority() const
Returns true if the URL specifies an authority (this includes username, server, and/or port),...
Definition: urlSpec.I:68
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool has_path() const
Returns true if the URL includes a path specification (that is, the particular filename on the server...
Definition: urlSpec.I:102
string downcase(const string &s)
Returns the input string with all uppercase letters converted to lowercase.
std::string get_port_str() const
Returns the port specified by the URL as a string, or the empty string if no port is specified.
Definition: urlSpec.I:148
bool is_default_port() const
Returns true if the port number encoded in this URL is the default port number for the scheme (or if ...
Definition: urlSpec.cxx:160
static std::string unquote(const std::string &source)
Reverses the operation of quote(): converts escaped characters of the form "%xx" to their ascii equiv...
Definition: urlSpec.cxx:801
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
set_server_and_port
Replaces the server and port parts of the URL specification simultaneously.
Definition: urlSpec.h:98
static size_t add_hash(size_t start, const Key &key)
Adds the indicated key into a running hash.
Definition: stl_compares.I:101
std::string get_path_and_query() const
Returns the path (or "/" if no path is specified), followed by the query if it is specified.
Definition: urlSpec.cxx:228
get_scheme
Returns the scheme specified by the URL, or empty string if no scheme is specified.
Definition: urlSpec.h:93
set_server
Replaces the server part of the URL specification.
Definition: urlSpec.h:96
bool has_scheme() const
Returns true if the URL specifies a scheme (e.g.
Definition: urlSpec.I:59
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
set_path
Replaces the path part of the URL specification.
Definition: urlSpec.h:99
static int get_default_port_for_scheme(const std::string &scheme)
Returns the default port number for the indicated scheme, or 0 if there is no known default.
Definition: urlSpec.cxx:172
std::string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition: filename.I:338
bool has_port() const
Returns true if the URL specifies a port number, false otherwise.
Definition: urlSpec.I:93
get_path
Returns the path specified by the URL, or "/" if no path is specified.
Definition: urlSpec.h:99
set_authority
Replaces the authority part of the URL specification.
Definition: urlSpec.h:94
bool has_server() const
Returns true if the URL specifies a server name, false otherwise.
Definition: urlSpec.I:85
set_username
Replaces the username part of the URL specification.
Definition: urlSpec.h:95
get_port
Returns the port number specified by the URL, or the default port if not specified.
Definition: urlSpec.h:97
bool has_query() const
Returns true if the URL includes a query specification, false otherwise.
Definition: urlSpec.I:110
int compare_to(const URLSpec &other) const
Returns a number less than zero if this URLSpec sorts before the other one, greater than zero if it s...
Definition: urlSpec.cxx:77
void set_url(const std::string &url, bool server_name_expected=false)
Completely replaces the URL with the indicated string.
Definition: urlSpec.cxx:554
const std::string & get_url() const
Returns the complete URL specification.
Definition: urlSpec.I:184
set_scheme
Replaces the scheme part of the URL specification.
Definition: urlSpec.h:93
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_server
Returns the server name specified by the URL, if any.
Definition: urlSpec.h:96
get_username
Returns the username specified by the URL, if any.
Definition: urlSpec.h:95
static std::string quote_plus(const std::string &source, const std::string &safe="/")
Behaves like quote() with the additional behavior of replacing spaces with plus signs.
Definition: urlSpec.cxx:758
static std::string quote(const std::string &source, const std::string &safe="/")
Returns the source string with all "unsafe" characters quoted, making a string suitable for placing i...
Definition: urlSpec.cxx:719
set_port
Replaces the port part of the URL specification.
Definition: urlSpec.h:97
static size_t add_hash(size_t start, const Key &key)
Adds the elements of the indicated key into a running hash.
Definition: stl_compares.I:168
static std::string unquote_plus(const std::string &source)
Reverses the operation of quote_plus(): converts escaped characters of the form "%xx" to their ascii ...
Definition: urlSpec.cxx:837
get_query
Returns the query specified by the URL, or empty string if no query is specified.
Definition: urlSpec.h:100