Browse Source

add urlSpec

David Rose 23 years ago
parent
commit
ffc858f09e

+ 11 - 0
panda/src/downloader/Sources.pp

@@ -14,6 +14,7 @@
     config_downloader.h asyncUtility.I asyncUtility.h \
     config_downloader.h asyncUtility.I asyncUtility.h \
     extractor.h  multiplexStream.I multiplexStream.h \
     extractor.h  multiplexStream.I multiplexStream.h \
     multiplexStreamBuf.I multiplexStreamBuf.h \
     multiplexStreamBuf.I multiplexStreamBuf.h \
+    urlSpec.I urlSpec.h \
     $[if $[HAVE_NET], downloadDb.I downloadDb.h downloader.I downloader.h] \
     $[if $[HAVE_NET], downloadDb.I downloadDb.h downloader.I downloader.h] \
     $[if $[HAVE_ZLIB], decompressor.h zcompressor.I zcompressor.h download_utils.h] \
     $[if $[HAVE_ZLIB], decompressor.h zcompressor.I zcompressor.h download_utils.h] \
     $[if $[HAVE_CRYPTO], patcher.cxx patcher.h patcher.I]
     $[if $[HAVE_CRYPTO], patcher.cxx patcher.h patcher.I]
@@ -21,6 +22,7 @@
   #define INCLUDED_SOURCES                 \
   #define INCLUDED_SOURCES                 \
     config_downloader.cxx asyncUtility.cxx \
     config_downloader.cxx asyncUtility.cxx \
     extractor.cxx multiplexStream.cxx multiplexStreamBuf.cxx \
     extractor.cxx multiplexStream.cxx multiplexStreamBuf.cxx \
+    urlSpec.cxx \
     $[if $[HAVE_NET], downloadDb.cxx downloader.cxx] \
     $[if $[HAVE_NET], downloadDb.cxx downloader.cxx] \
     $[if $[HAVE_ZLIB], decompressor.cxx zcompressor.cxx download_utils.cxx]
     $[if $[HAVE_ZLIB], decompressor.cxx zcompressor.cxx download_utils.cxx]
 
 
@@ -34,8 +36,17 @@
     multiplexStream.I multiplexStream.h \
     multiplexStream.I multiplexStream.h \
     multiplexStreamBuf.I multiplexStreamBuf.I \
     multiplexStreamBuf.I multiplexStreamBuf.I \
     patcher.h patcher.I \
     patcher.h patcher.I \
+    urlSpec.h urlSpec.I \
     zcompressor.I zcompressor.h
     zcompressor.I zcompressor.h
     
     
   #define IGATESCAN all
   #define IGATESCAN all
 
 
 #end lib_target
 #end lib_target
+
+#begin test_bin_target
+  #define TARGET test_proxy
+
+  #define SOURCES test_proxy.cxx
+  #define LOCAL_LIBS $[LOCAL_LIBS] putil net
+  #define OTHER_LIBS $[OTHER_LIBS] pystub
+#end test_bin_target

+ 1 - 0
panda/src/downloader/downloader_composite1.cxx

@@ -3,4 +3,5 @@
 #include "extractor.cxx"
 #include "extractor.cxx"
 #include "multiplexStream.cxx"
 #include "multiplexStream.cxx"
 #include "multiplexStreamBuf.cxx"
 #include "multiplexStreamBuf.cxx"
+#include "urlSpec.cxx"
 
 

+ 204 - 0
panda/src/downloader/urlSpec.I

@@ -0,0 +1,204 @@
+// Filename: urlSpec.I
+// Created by:  drose (24Sep02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE URLSpec::
+URLSpec(const string &url) {
+  (*this) = url;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::Copy Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE URLSpec::
+URLSpec(const URLSpec &copy) {
+  (*this) = copy;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::Assignment Operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void URLSpec::
+operator = (const string &url) {
+  set_url(url);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::has_scheme
+//       Access: Published
+//  Description: Returns true if the URL specifies a scheme
+//               (e.g. "http:"), false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool URLSpec::
+has_scheme() const {
+  return (_flags & F_has_scheme) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::has_authority
+//       Access: Published
+//  Description: Returns true if the URL specifies an authority
+//               (this includes username, server, and/or port), false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool URLSpec::
+has_authority() const {
+  return (_flags & F_has_authority) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::has_username
+//       Access: Published
+//  Description: Returns true if the URL specifies a username
+//               (and/or password), false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool URLSpec::
+has_username() const {
+  return (_flags & F_has_username) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::has_server
+//       Access: Published
+//  Description: Returns true if the URL specifies a server name,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool URLSpec::
+has_server() const {
+  return (_flags & F_has_server) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::has_port
+//       Access: Published
+//  Description: Returns true if the URL specifies a port number,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool URLSpec::
+has_port() const {
+  return (_flags & F_has_port) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::has_path
+//       Access: Published
+//  Description: Returns true if the URL includes a path specification
+//               (that is, the particular filename on the server to
+//               retrieve), false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool URLSpec::
+has_path() const {
+  return (_flags & F_has_path) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::has_query
+//       Access: Published
+//  Description: Returns true if the URL includes a query
+//               specification, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool URLSpec::
+has_query() const {
+  return (_flags & F_has_query) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::get_authority
+//       Access: Published
+//  Description: Returns the authority specified by the URL (this
+//               includes username, server, and/or port), or empty
+//               string if no authority is specified.
+////////////////////////////////////////////////////////////////////
+INLINE string URLSpec::
+get_authority() const {
+  return _url.substr(_username_start, _port_end - _username_start);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::get_username
+//       Access: Published
+//  Description: Returns the username specified by the URL, if any.
+//               This might also include a password,
+//               e.g. "username:password", although putting a password
+//               on the URL is probably a bad idea.
+////////////////////////////////////////////////////////////////////
+INLINE string URLSpec::
+get_username() const {
+  return _url.substr(_username_start, _username_end - _username_start);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::get_server
+//       Access: Published
+//  Description: Returns the server name specified by the URL, if any.
+////////////////////////////////////////////////////////////////////
+INLINE string URLSpec::
+get_server() const {
+  return _url.substr(_server_start, _server_end - _server_start);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::get_port_str
+//       Access: Published
+//  Description: Returns the port specified by the URL as a string, or
+//               the empty string if no port is specified.  Compare
+//               this with get_port(), which returns a default port
+//               number if no port is specified.
+////////////////////////////////////////////////////////////////////
+INLINE string URLSpec::
+get_port_str() const {
+  return _url.substr(_port_start, _port_end - _port_start);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::get_query
+//       Access: Published
+//  Description: Returns the query specified by the URL, or empty
+//               string if no query is specified.
+////////////////////////////////////////////////////////////////////
+INLINE string URLSpec::
+get_query() const {
+  return _url.substr(_query_start);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::get_url
+//       Access: Published
+//  Description: Returns the complete URL specification.
+////////////////////////////////////////////////////////////////////
+INLINE const string &URLSpec::
+get_url() const {
+  return _url;
+}
+
+INLINE ostream &
+operator << (ostream &out, const URLSpec &url) {
+  url.output(out);
+  return out;
+}
+
+

+ 676 - 0
panda/src/downloader/urlSpec.cxx

@@ -0,0 +1,676 @@
+// Filename: urlSpec.cxx
+// Created by:  drose (24Sep02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "urlSpec.h"
+
+#include <ctype.h>
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+URLSpec::
+URLSpec() {
+  _port = 0;
+  _flags = 0;
+  _scheme_end = 0;
+  _username_start = 0;
+  _username_end = 0;
+  _server_start = 0;
+  _server_end = 0;
+  _port_start = 0;
+  _port_end = 0;
+  _path_start = 0;
+  _path_end = 0;
+  _query_start = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::Copy Assignment Operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+operator = (const URLSpec &copy) {
+  _url = copy._url;
+  _port = copy._port;
+  _flags = copy._flags;
+  _scheme_end = copy._scheme_end;
+  _username_start = copy._username_start;
+  _username_end = copy._username_end;
+  _server_start = copy._server_start;
+  _server_end = copy._server_end;
+  _port_start = copy._port_start;
+  _port_end = copy._port_end;
+  _path_start = copy._path_start;
+  _path_end = copy._path_end;
+  _query_start = copy._query_start;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::get_scheme
+//       Access: Published
+//  Description: Returns the scheme specified by the URL, or "http"
+//               if no scheme is specified.
+////////////////////////////////////////////////////////////////////
+string URLSpec::
+get_scheme() const {
+  if (has_scheme()) {
+    return _url.substr(0, _scheme_end);
+  }
+  return "http";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::get_port
+//       Access: Published
+//  Description: Returns the port number specified by the URL, or the
+//               default port if not specified.
+////////////////////////////////////////////////////////////////////
+int URLSpec::
+get_port() const {
+  if (has_port()) {
+    return _port;
+  }
+  string scheme = get_scheme();
+  if (scheme == "https") {
+    return 443;
+
+  } else { // == "http"
+    return 80;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::get_path
+//       Access: Published
+//  Description: Returns the path specified by the URL, or "/" if no
+//               path is specified.
+////////////////////////////////////////////////////////////////////
+string URLSpec::
+get_path() const {
+  if (has_path()) {
+    return _url.substr(_path_start, _path_end - _path_start);
+  }
+  return "/";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::set_scheme
+//       Access: Published
+//  Description: Replaces the scheme part of the URL specification.
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+set_scheme(const string &scheme) {
+  int length_adjust;
+
+  // The scheme is always converted to lowercase.
+  string lc_scheme;
+  lc_scheme.reserve(scheme.length());
+  for (string::const_iterator si = scheme.begin(); si != scheme.end(); ++si) {
+    lc_scheme += tolower(*si);
+  }
+
+  if (lc_scheme.empty()) {
+    // Remove the scheme specification.
+    if (!has_scheme()) {
+      return;
+    }
+    _scheme_end++;
+    length_adjust = -(int)_scheme_end;
+    _url = _url.substr(_scheme_end);
+    _flags &= ~F_has_scheme;
+
+  } else if (!has_scheme()) {
+    // Insert a new scheme specification.
+    length_adjust = lc_scheme.length() + 1;
+
+    _url = lc_scheme + ":" + _url;
+    _flags |= F_has_scheme;
+
+  } else {
+    int old_length = (int)_scheme_end;
+    length_adjust = scheme.length() - old_length;
+    _url = lc_scheme + _url.substr(_scheme_end);
+  }
+
+  _scheme_end += length_adjust;
+  _username_start += length_adjust;
+  _username_end += length_adjust;
+  _server_start += length_adjust;
+  _server_end += length_adjust;
+  _port_start += length_adjust;
+  _port_end += length_adjust;
+  _path_start += length_adjust;
+  _path_end += length_adjust;
+  _query_start += length_adjust;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::set_authority
+//       Access: Published
+//  Description: Replaces the authority part of the URL specification.
+//               This includes the username, server, and port.
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+set_authority(const string &authority) {
+  int length_adjust;
+
+  if (authority.empty()) {
+    // Remove the authority specification.
+    if (!has_authority()) {
+      return;
+    }
+    _username_start -= 2;
+    length_adjust = -((int)_port_end - (int)_username_start);
+    _url = _url.substr(0, _username_start) + _url.substr(_port_end);
+    _flags &= ~(F_has_authority | F_has_username | F_has_server | F_has_port);
+
+    _username_end = _username_start;
+    _server_start = _username_start;
+    _server_end = _username_start;
+    _port_start = _username_start;
+
+  } else if (!has_authority()) {
+    // Insert a new authority specification.
+    length_adjust = authority.length() + 2;
+
+    _url = _url.substr(0, _username_start) + "//" + authority + _url.substr(_port_end);
+    _flags |= F_has_authority;
+    _username_start += 2;
+
+  } else {
+    // Replace an existing authority specification.
+    int old_length = (int)_port_end - (int)_username_start;
+    length_adjust = authority.length() - old_length;
+    _url = _url.substr(0, _username_start) + authority + _url.substr(_port_end);
+  }
+
+  _port_end += length_adjust;
+  _path_start += length_adjust;
+  _path_end += length_adjust;
+  _query_start += length_adjust;
+
+  parse_authority();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::set_username
+//       Access: Published
+//  Description: Replaces the username part of the URL specification.
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+set_username(const string &username) {
+  if (username.empty() && !has_authority()) {
+    return;
+  }
+  string authority;
+
+  if (!username.empty()) {
+    authority = username + "@";
+  }
+  authority += get_server();
+  if (has_port()) {
+    authority += ":";
+    authority += get_port_str();
+  }
+
+  set_authority(authority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::set_server
+//       Access: Published
+//  Description: Replaces the server part of the URL specification.
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+set_server(const string &server) {
+  if (server.empty() && !has_authority()) {
+    return;
+  }
+  string authority;
+
+  if (has_username()) {
+    authority = get_username() + "@";
+  }
+  authority += server;
+  if (has_port()) {
+    authority += ":";
+    authority += get_port_str();
+  }
+
+  set_authority(authority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::set_port
+//       Access: Published
+//  Description: Replaces the port part of the URL specification.
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+set_port(const string &port) {
+  if (port.empty() && !has_authority()) {
+    return;
+  }
+  string authority;
+
+  if (has_username()) {
+    authority = get_username() + "@";
+  }
+  authority += get_server();
+
+  if (!port.empty()) {
+    authority += ":";
+    authority += port;
+  }
+
+  set_authority(authority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::set_port
+//       Access: Published
+//  Description: Replaces the port part of the URL specification,
+//               given a numeric port number.
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+set_port(int port) {
+  ostringstream str;
+  str << port;
+  set_port(str.str());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::set_path
+//       Access: Published
+//  Description: Replaces the path part of the URL specification.
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+set_path(const string &path) {
+  int length_adjust;
+
+  if (path.empty()) {
+    // Remove the path specification.
+    if (!has_path()) {
+      return;
+    }
+    length_adjust = -((int)_path_end - (int)_path_start);
+    _url = _url.substr(0, _path_start) + _url.substr(_path_end);
+    _flags &= ~F_has_path;
+
+  } else if (!has_path()) {
+    // Insert a new path specification.
+    length_adjust = path.length();
+
+    _url = _url.substr(0, _path_start) + path + _url.substr(_path_end);
+    _flags |= F_has_path;
+
+  } else {
+    // Replace an existing path specification.
+    int old_length = (int)_path_end - (int)_path_start;
+    length_adjust = path.length() - old_length;
+    _url = _url.substr(0, _path_start) + path + _url.substr(_path_end);
+  }
+
+  _path_end += length_adjust;
+  _query_start += length_adjust;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::set_query
+//       Access: Published
+//  Description: Replaces the query part of the URL specification.
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+set_query(const string &query) {
+  if (query.empty()) {
+    // Remove the query specification.
+    if (!has_query()) {
+      return;
+    }
+    _query_start--;
+    _url = _url.substr(0, _query_start);
+    _flags &= ~F_has_query;
+
+  } else if (!has_query()) {
+    // Insert a new query specification.
+    _url = _url.substr(0, _query_start) + "?" + query;
+    _flags |= F_has_query;
+    _query_start++;
+
+  } else {
+    // Replace an existing query specification.
+    _url = _url.substr(0, _query_start) + query;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::set_url
+//       Access: Published
+//  Description: Completely replaces the URL with the indicated
+//               string.
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+set_url(const string &url) {
+  _url = url;
+  _flags = 0;
+
+  // First, replace backslashes with forward slashes, since this is a
+  // common mistake among Windows users.
+  size_t p;
+  for (p = 0; p < _url.length(); p++) {
+    if (_url[p] == '\\') {
+      _url[p] = '/';
+    }
+  }
+
+  // What have we got?
+  _flags = 0;
+  _port = 0;
+
+  // Look for the scheme specification.
+  size_t start = 0;
+
+  _scheme_end = start;
+  size_t next = _url.find_first_of(":/", start);
+  if (next != string::npos && _url[next] == ':') {
+    // We have a scheme.
+    _flags |= F_has_scheme;
+    _scheme_end = next;
+
+    // Ensure the scheme is lowercase.
+    for (size_t p = 0; p < _scheme_end; ++p) {
+      _url[p] = tolower(_url[p]);
+    }
+
+    start = next + 1;
+  }
+
+  // Look for the authority specification, which may include any of
+  // username, server, and/or port.
+  _username_start = start;
+  _username_end = start;
+  _server_start = start;
+  _server_end = start;
+  _port_start = start;
+  _port_end = start;
+  if (start + 1 < _url.length() && _url.substr(start, 2) == "//") {
+    // We have an authority specification.
+    start += 2;
+    _flags |= F_has_authority;
+    _username_start = start;
+    _port_end = _url.find_first_of("/?", start);
+    if (_port_end == string::npos) {
+      _port_end = _url.length();
+    }
+    parse_authority();
+    start = _port_end;
+  }
+
+  // Everything up to the ?, if any, is the path.
+  _path_start = start;
+  _path_end = start;
+  if (start < _url.length() && url[start] != '?') {
+    // We have a path.
+    _flags |= F_has_path;
+    _path_start = start;
+    _path_end = _url.find("?", _path_start);
+    if (_path_end == string::npos) {
+      _path_end = _url.length();
+    }
+    start = _path_end;
+  }
+
+  // Everything after the ? is the query.
+  _query_start = start;
+  if (start < _url.length()) {
+    nassertv(_url[start] == '?');
+    _flags |= F_has_query;
+    _query_start++;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::output
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+output(ostream &out) const {
+  out << get_url();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::quote
+//       Access: Published, Static
+//  Description: Returns the source string with all "unsafe"
+//               characters quoted, making a string suitable for
+//               placing in a URL.  Letters, digits, and the
+//               underscore, comma, period, and hyphen characters, as
+//               well as any included in the safe string, are left
+//               alone; all others are converted to hex
+//               representation.
+////////////////////////////////////////////////////////////////////
+string URLSpec::
+quote(const string &source, const string &safe) {
+  ostringstream result;
+  result << hex << setw(2) << setfill('0');
+
+  for (string::const_iterator si = source.begin(); si != source.end(); ++si) {
+    char ch = (*si);
+    switch (ch) {
+    case '_':
+    case ',':
+    case '.':
+    case '-':
+      // Safe character.
+      result << ch;
+      break;
+
+    default:
+      if (isalnum(ch)) {
+        // Letters and digits are safe.
+        result << ch;
+
+      } else if (safe.find(ch) != string::npos) {
+        // If it's listed in "safe", it's safe.
+        result << ch;
+
+      } else {
+        // Otherwise, escape it.
+        result << "0x" << ch;
+      }
+    }
+  }
+
+  return result.str();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::quote_plus
+//       Access: Published, Static
+//  Description: Behaves like quote() with the additional behavior of
+//               replacing spaces with plus signs.
+////////////////////////////////////////////////////////////////////
+string URLSpec::
+quote_plus(const string &source, const string &safe) {
+  ostringstream result;
+  result << hex << setw(2) << setfill('0');
+
+  for (string::const_iterator si = source.begin(); si != source.end(); ++si) {
+    char ch = (*si);
+    switch (ch) {
+    case '_':
+    case ',':
+    case '.':
+    case '-':
+      // Safe character.
+      result << ch;
+      break;
+
+    case ' ':
+      result << '+';
+      break;
+
+    default:
+      if (isalnum(ch)) {
+        // Letters and digits are safe.
+        result << ch;
+
+      } else if (safe.find(ch) != string::npos) {
+        // If it's listed in "safe", it's safe.
+        result << ch;
+
+      } else {
+        // Otherwise, escape it.
+        result << "0x" << ch;
+      }
+    }
+  }
+
+  return result.str();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::unquote
+//       Access: Published, Static
+//  Description: Reverses the operation of quote(): converts escaped
+//               characters of the form "%xx" to their ascii
+//               equivalent.
+////////////////////////////////////////////////////////////////////
+string URLSpec::
+unquote(const string &source) {
+  string result;
+
+  string::const_iterator si = source.begin();
+  while (si != source.end()) {
+    if ((*si) == '%') {
+      ++si;
+      int hex = 0;
+      while (si != source.end() && isxdigit(*si)) {
+        int value;
+        char ch = *si;
+        if (isdigit(ch)) {
+          value = ch - '0';
+        } else {
+          value = tolower(ch) - 'a';
+        }
+        hex = (hex << 8) | value;
+        ++si;
+      }
+      result += (char)hex;
+
+    } else {
+      result += (*si);
+      ++si;
+    }
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::unquote_plus
+//       Access: Published, Static
+//  Description: Reverses the operation of quote_plus(): converts escaped
+//               characters of the form "%xx" to their ascii
+//               equivalent, and also converts plus signs to spaces.
+////////////////////////////////////////////////////////////////////
+string URLSpec::
+unquote_plus(const string &source) {
+  string result;
+
+  string::const_iterator si = source.begin();
+  while (si != source.end()) {
+    if ((*si) == '%') {
+      ++si;
+      int hex = 0;
+      while (si != source.end() && isxdigit(*si)) {
+        int value;
+        char ch = *si;
+        if (isdigit(ch)) {
+          value = ch - '0';
+        } else {
+          value = tolower(ch) - 'a';
+        }
+        hex = (hex << 8) | value;
+        ++si;
+      }
+      result += (char)hex;
+
+    } else if ((*si) == '+') {
+      result += ' ';
+
+    } else {
+      result += (*si);
+      ++si;
+    }
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::parse_authority
+//       Access: Private
+//  Description: Assumes _url[_username_start .. _port_end - 1] is
+//               the authority component if the URL, consisting of
+//               [username@]server[:port].  Parses out the three
+//               pieces and updates the various _start and _end
+//               parameters accordingly.
+////////////////////////////////////////////////////////////////////
+void URLSpec::
+parse_authority() {
+  if (!has_authority()) {
+    return;
+  }
+
+  // Assume we don't have a username or port unless we find them.
+  _username_end = _username_start;
+  _port_start = _port_end;
+
+  // We assume we have a server, even if it becomes the empty string.
+  _flags |= F_has_server;
+  _server_start = _username_start;
+  _server_end = _port_end;
+
+  // Is there a username?
+  size_t at_sign = _url.find('@', _username_start);
+  if (at_sign < _port_end) {
+    // We have a username.
+    _flags |= F_has_username;
+    _username_end = at_sign;
+    _server_start = at_sign + 1;
+  }
+
+  // Is there a port?
+  size_t colon = _url.find(':', _server_start);
+  if (colon < _port_end) {
+    // Yep.
+    _flags |= F_has_port;
+    _server_end = colon;
+    _port_start = colon + 1;
+        
+    // Decode the port into an integer.  Don't bother to error
+    // check if it's not really an integer.
+    string port_str = _url.substr(_port_start, _port_end - _port_start);
+    _port = atoi(port_str.c_str());
+  }
+}

+ 111 - 0
panda/src/downloader/urlSpec.h

@@ -0,0 +1,111 @@
+// Filename: urlSpec.h
+// Created by:  drose (24Sep02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef URLSPEC_H
+#define URLSPEC_H
+
+#include "pandabase.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : URLSpec
+// Description : A container for a URL, e.g. "http://server:port/path".
+//
+//               The URLSpec object is similar to a Filename in that
+//               it contains logic to identify the various parts of a
+//               URL and return (or modify) them separately.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS URLSpec {
+PUBLISHED:
+  URLSpec();
+  INLINE URLSpec(const string &url);
+  INLINE URLSpec(const URLSpec &copy);
+  INLINE void operator = (const string &url);
+  void operator = (const URLSpec &copy);
+
+  INLINE bool has_scheme() const;
+  INLINE bool has_authority() const;
+  INLINE bool has_username() const;
+  INLINE bool has_server() const;
+  INLINE bool has_port() const;
+  INLINE bool has_path() const;
+  INLINE bool has_query() const;
+
+  string get_scheme() const;
+  INLINE string get_authority() const;
+  INLINE string get_username() const;
+  INLINE string get_server() const;
+  INLINE string get_port_str() const;
+  int get_port() const;
+  string get_path() const;
+  INLINE string get_query() const;
+
+  INLINE const string &get_url() const;
+
+  void set_scheme(const string &scheme);
+  void set_authority(const string &authority);
+  void set_username(const string &username);
+  void set_server(const string &server);
+  void set_port(const string &port);
+  void set_port(int port);
+  void set_path(const string &path);
+  void set_query(const string &query);
+
+  void set_url(const string &url);
+
+  void output(ostream &out) const;
+
+  static string quote(const string &source, const string &safe = "/");
+  static string quote_plus(const string &source, const string &safe = "/");
+  static string unquote(const string &source);
+  static string unquote_plus(const string &source);
+
+private:
+  void parse_authority();
+
+  enum Flags {
+    F_has_scheme     = 0x0001,
+    F_has_authority  = 0x0002,
+    F_has_username   = 0x0004,
+    F_has_server     = 0x0008,
+    F_has_port       = 0x0010,
+    F_has_path       = 0x0020,
+    F_has_query      = 0x0040,
+  };
+
+  string _url;
+  int _port;
+  int _flags;
+
+  size_t _scheme_end;
+  size_t _username_start;
+  size_t _username_end;
+  size_t _server_start;
+  size_t _server_end;
+  size_t _port_start;
+  size_t _port_end;
+  size_t _path_start;
+  size_t _path_end;
+  size_t _query_start;
+};
+
+INLINE ostream &operator << (ostream &out, const URLSpec &url);
+
+#include "urlSpec.I"
+
+#endif
+