32 #if defined(WIN32_VC) || defined(WIN64_VC) 41 using std::ostringstream;
46 #define _NOTIFY_HTTP_CHANNEL_ID "[" << this << "] " 52 HTTPChannel(HTTPClient *client) :
55 if (downloader_cat.is_debug()) {
56 downloader_cat.debug()
57 << _NOTIFY_HTTP_CHANNEL_ID
62 (
"extra-ssl-handshake-time", 0.0,
63 PRC_DESC(
"This specifies how much extra time to try to establish" 64 "the ssl handshake before we bail."));
65 _extra_ssl_handshake_time = extra_ssl_handshake_time;
66 _proxy_next_index = 0;
67 _persistent_connection =
false;
69 _proxy_tunnel = http_proxy_tunnel;
70 _connect_timeout = http_connect_timeout;
71 _http_timeout = http_timeout;
72 _skip_body_size = http_skip_body_size;
73 _idle_timeout = http_idle_timeout;
74 _blocking_connect =
false;
75 _download_throttle = download_throttle;
76 _max_bytes_per_second = downloader_byte_rate;
77 _seconds_per_update = downloader_frequency;
78 _max_updates_per_second = 1.0f / _seconds_per_update;
79 _bytes_per_update = int(_max_bytes_per_second * _seconds_per_update);
87 _wanted_nonblocking =
false;
90 _proxy_serves_document =
false;
91 _proxy_tunnel_now =
false;
92 _first_byte_requested = 0;
93 _last_byte_requested = 0;
94 _first_byte_delivered = 0;
95 _last_byte_delivered = 0;
97 _expected_file_size = 0;
99 _transfer_file_size = 0;
100 _got_expected_file_size =
false;
101 _got_file_size =
false;
102 _got_transfer_file_size =
false;
103 _bytes_downloaded = 0;
104 _bytes_requested = 0;
105 _status_entry = StatusEntry();
106 _response_type = RT_none;
107 _http_version = _client->get_http_version();
108 _http_version_string = _client->get_http_version_string();
109 _content_type =
"application/x-www-form-urlencoded";
112 _started_download =
false;
114 _body_stream =
nullptr;
115 _owns_body_stream =
false;
117 _cipher_list = _client->get_cipher_list();
118 _last_status_code = 0;
119 _last_run_time = 0.0f;
120 _download_to_ramfile =
nullptr;
121 _download_to_stream =
nullptr;
129 if (downloader_cat.is_debug()) {
130 downloader_cat.debug()
131 << _NOTIFY_HTTP_CHANNEL_ID
144 get_status_string()
const {
145 switch (_status_entry._status_code) {
147 return "Connection in progress";
149 case SC_internal_error:
150 return "Internal error";
152 case SC_no_connection:
153 return "No connection";
156 return "Timeout on connection";
158 case SC_lost_connection:
159 return "Lost connection";
161 case SC_non_http_response:
162 return "Non-HTTP response";
164 case SC_invalid_http:
165 return "Could not understand HTTP response";
167 case SC_socks_invalid_version:
168 return "Unsupported SOCKS version";
170 case SC_socks_no_acceptable_login_method:
171 return "No acceptable SOCKS login method";
173 case SC_socks_refused:
174 return "SOCKS proxy refused connection";
176 case SC_socks_no_connection:
177 return "SOCKS proxy unable to connect";
179 case SC_ssl_internal_failure:
180 return "SSL internal failure";
182 case SC_ssl_no_handshake:
183 return "No SSL handshake";
185 case SC_http_error_watermark:
187 return "Internal error";
189 case SC_ssl_invalid_server_certificate:
190 return "SSL invalid server certificate";
192 case SC_ssl_unexpected_server:
193 return "Unexpected SSL server";
195 case SC_download_open_error:
196 return "Error opening file";
198 case SC_download_write_error:
199 return "Error writing to disk";
201 case SC_download_invalid_range:
202 return "Invalid subrange requested";
205 return _status_entry._status_string;
213 get_header_value(
const string &key)
const {
214 Headers::const_iterator hi = _headers.find(
downcase(key));
215 if (hi != _headers.end()) {
227 will_close_connection()
const {
228 if (get_http_version() < HTTPEnum::HV_11) {
233 string connection = get_header_value(
"Connection");
234 if (
downcase(connection) ==
"close") {
239 if (connection.empty() && !get_persistent_connection()) {
259 std::streamsize HTTPChannel::
260 get_file_size()
const {
261 if (_got_file_size) {
263 }
else if (_got_transfer_file_size) {
264 return _transfer_file_size;
265 }
else if (_got_expected_file_size) {
266 return _expected_file_size;
277 write_headers(ostream &out)
const {
278 Headers::const_iterator hi;
279 for (hi = _headers.begin(); hi != _headers.end(); ++hi) {
280 out << (*hi).first <<
": " << (*hi).second <<
"\n";
296 if (downloader_cat.is_spam()) {
297 downloader_cat.spam()
298 << _NOTIFY_HTTP_CHANNEL_ID
302 if (_state == _done_state || _state == S_failure) {
303 clear_extra_headers();
304 if (!reached_done_state()) {
309 if (_started_download) {
310 if (_wanted_nonblocking && _download_throttle) {
312 double elapsed = now - _last_run_time;
313 if (elapsed < _seconds_per_update) {
318 int num_potential_updates = (int)(elapsed / _seconds_per_update);
319 _last_run_time = now;
320 _bytes_requested += _bytes_per_update * num_potential_updates;
321 if (downloader_cat.is_spam()) {
322 downloader_cat.spam()
323 << _NOTIFY_HTTP_CHANNEL_ID
324 <<
"elapsed = " << elapsed <<
" num_potential_updates = " 325 << num_potential_updates <<
" bytes_requested = " 326 << _bytes_requested <<
"\n";
330 bool repeat_later =
false;
331 switch (_download_dest) {
337 repeat_later = run_download_to_file();
341 repeat_later = run_download_to_ram();
345 repeat_later = run_download_to_stream();
363 if (_state == _done_state) {
364 return reached_done_state();
372 if (_bio.is_null() && _state != S_try_next_proxy) {
373 if (_connect_count > http_max_connect_count) {
378 downloader_cat.warning()
379 << _NOTIFY_HTTP_CHANNEL_ID
380 <<
"Too many lost connections, giving up.\n";
381 _status_entry._status_code = SC_lost_connection;
388 if (_proxy.empty()) {
393 _bio =
new BioPtr(url);
394 _source =
new BioStreamPtr(
new BioStream(_bio));
396 _bio->set_nbio(
true);
399 if (downloader_cat.is_debug()) {
400 if (_connect_count > 0) {
401 downloader_cat.debug()
402 << _NOTIFY_HTTP_CHANNEL_ID
403 <<
"Reconnecting to " << _bio->get_server_name() <<
" port " 404 << _bio->get_port() <<
"\n";
406 downloader_cat.debug()
407 << _NOTIFY_HTTP_CHANNEL_ID
408 <<
"Connecting to " << _bio->get_server_name() <<
" port " 409 << _bio->get_port() <<
"\n";
413 _state = S_connecting;
414 _started_connecting_time =
428 case S_try_next_proxy:
429 repeat_later = run_try_next_proxy();
433 repeat_later = run_connecting();
436 case S_connecting_wait:
437 repeat_later = run_connecting_wait();
440 case S_http_proxy_ready:
441 repeat_later = run_http_proxy_ready();
444 case S_http_proxy_request_sent:
445 repeat_later = run_http_proxy_request_sent();
448 case S_http_proxy_reading_header:
449 repeat_later = run_http_proxy_reading_header();
452 case S_socks_proxy_greet:
453 repeat_later = run_socks_proxy_greet();
456 case S_socks_proxy_greet_reply:
457 repeat_later = run_socks_proxy_greet_reply();
460 case S_socks_proxy_connect:
461 repeat_later = run_socks_proxy_connect();
464 case S_socks_proxy_connect_reply:
465 repeat_later = run_socks_proxy_connect_reply();
469 repeat_later = run_setup_ssl();
472 case S_ssl_handshake:
473 repeat_later = run_ssl_handshake();
477 repeat_later = run_ready();
481 repeat_later = run_request_sent();
484 case S_reading_header:
485 repeat_later = run_reading_header();
488 case S_start_direct_file_read:
489 repeat_later = run_start_direct_file_read();
493 repeat_later = run_read_header();
497 repeat_later = run_begin_body();
501 repeat_later = run_reading_body();
505 repeat_later = run_read_body();
509 repeat_later = run_read_trailer();
513 downloader_cat.warning()
514 << _NOTIFY_HTTP_CHANNEL_ID
515 <<
"Unhandled state " << _state <<
"\n";
519 if (_state == _done_state || _state == S_failure) {
520 clear_extra_headers();
522 return reached_done_state();
524 thread_consider_yield();
525 }
while (!repeat_later || _bio.is_null());
553 ISocketStream *HTTPChannel::
557 if ((_state != S_read_header && _state != S_begin_body) || _source.is_null()) {
561 string transfer_coding =
downcase(get_header_value(
"Transfer-Encoding"));
563 ISocketStream *result;
564 if (transfer_coding ==
"chunked") {
568 _state = S_reading_body;
570 result =
new IChunkedStream(_source,
this);
577 _state = S_reading_body;
579 result =
new IIdentityStream(_source,
this, _got_file_size, _file_size);
582 result->_channel =
this;
583 _body_stream = result;
584 _owns_body_stream =
false;
596 close_read_body(istream *stream)
const {
597 if (stream !=
nullptr) {
602 #if !defined(USE_MEMORY_NOWRAPPERS) && defined(REDEFINE_GLOBAL_OPERATOR_NEW) 604 (*global_operator_delete)(stream);
636 download_to_file(
const Filename &filename,
bool subdocument_resumes) {
638 _download_to_filename = filename;
640 _subdocument_resumes = subdocument_resumes;
642 _download_dest = DD_file;
644 if (_wanted_nonblocking && _state != S_read_header) {
651 if (!open_download_file()) {
658 return is_download_complete() && is_valid();
685 download_to_ram(
Ramfile *ramfile,
bool subdocument_resumes) {
686 nassertr(ramfile !=
nullptr,
false);
689 _download_to_ramfile = ramfile;
690 _download_dest = DD_ram;
691 _subdocument_resumes = (subdocument_resumes && _first_byte_delivered != 0);
693 if (_wanted_nonblocking && _state != S_read_header) {
700 if (!open_download_file()) {
707 return is_download_complete() && is_valid();
735 download_to_stream(ostream *strm,
bool subdocument_resumes) {
737 _download_to_stream = strm;
738 _download_to_stream->clear();
739 _subdocument_resumes = subdocument_resumes;
741 _download_dest = DD_stream;
743 if (_wanted_nonblocking && _state != S_read_header) {
750 if (!open_download_file()) {
757 return is_download_complete() && is_valid();
768 SocketStream *HTTPChannel::
770 if (!is_connection_ready()) {
774 BioStream *stream = _source->get_stream();
775 _source->set_stream(
nullptr);
778 if (downloader_cat.is_debug()) {
779 downloader_cat.debug()
780 << _NOTIFY_HTTP_CHANNEL_ID
781 <<
"passing ownership of connection to caller.\n";
794 result.reserve(s.size());
795 string::const_iterator p;
796 for (p = s.begin(); p != s.end(); ++p) {
797 result += tolower(*p);
806 body_stream_destructs(ISocketStream *stream) {
807 if (stream == _body_stream) {
808 if (_state == S_reading_body) {
809 switch (_body_stream->get_read_state()) {
810 case ISocketStream::RS_complete:
811 finished_body(
false);
814 case ISocketStream::RS_error:
815 _state = HTTPChannel::S_failure;
816 _status_entry._status_code = HTTPChannel::SC_lost_connection;
823 _body_stream =
nullptr;
824 _owns_body_stream =
false;
834 reached_done_state() {
844 if (_state == S_failure) {
848 if (!_status_list.empty()) {
849 _status_list.push_back(_status_entry);
850 if (downloader_cat.is_debug()) {
851 downloader_cat.debug()
852 << _NOTIFY_HTTP_CHANNEL_ID
853 <<
"Reexamining failure responses.\n";
856 if (downloader_cat.is_debug()) {
857 downloader_cat.debug()
858 << _NOTIFY_HTTP_CHANNEL_ID
859 <<
" " << 0 <<
". " << _status_list[0]._status_code <<
" " 860 << _status_list[0]._status_string <<
"\n";
862 for (
size_t i = 1; i < _status_list.size(); i++) {
863 if (downloader_cat.is_debug()) {
864 downloader_cat.debug()
865 << _NOTIFY_HTTP_CHANNEL_ID
866 <<
" " << i <<
". " << _status_list[i]._status_code <<
" " 867 << _status_list[i]._status_string <<
"\n";
869 if (more_useful_status_code(_status_list[i]._status_code,
870 _status_list[best_i]._status_code)) {
874 if (downloader_cat.is_debug()) {
875 downloader_cat.debug()
876 << _NOTIFY_HTTP_CHANNEL_ID
877 <<
"chose index " << best_i <<
", above.\n";
879 _status_entry = _status_list[best_i];
880 _status_list.clear();
887 _status_list.clear();
889 if (_download_dest == DD_none) {
896 if (_body_stream ==
nullptr) {
897 if (downloader_cat.is_debug()) {
898 downloader_cat.debug()
899 << _NOTIFY_HTTP_CHANNEL_ID
900 <<
"Unable to download body: " << _request.get_url() <<
"\n";
905 _owns_body_stream =
true;
906 if (_state != S_reading_body) {
909 _started_download =
true;
911 _done_state = S_read_trailer;
924 run_try_next_proxy() {
925 if (_proxy_next_index < _proxies.size()) {
928 _status_list.push_back(_status_entry);
929 _status_entry = StatusEntry();
932 _proxy = _proxies[_proxy_next_index];
933 _proxy_auth =
nullptr;
937 _state = S_connecting;
953 _status_entry = StatusEntry();
955 if (!_bio->connect()) {
956 if (_bio->should_retry()) {
957 _state = S_connecting_wait;
960 downloader_cat.info()
961 << _NOTIFY_HTTP_CHANNEL_ID
962 <<
"Could not connect to " << _bio->get_server_name() <<
" port " 963 << _bio->get_port() <<
"\n";
964 OpenSSLWrapper::get_global_ptr()->notify_ssl_errors();
965 _status_entry._status_code = SC_no_connection;
966 _state = S_try_next_proxy;
970 if (downloader_cat.is_debug()) {
971 downloader_cat.debug()
972 << _NOTIFY_HTTP_CHANNEL_ID
973 <<
"Connected to " << _bio->get_server_name() <<
" port " 974 << _bio->get_port() <<
"\n";
977 if (_proxy_tunnel_now) {
978 if (_proxy.get_scheme() ==
"socks") {
979 _state = S_socks_proxy_greet;
981 _state = S_http_proxy_ready;
986 _state = S_setup_ssl;
1000 run_connecting_wait() {
1002 BIO_get_fd(*_bio, &fd);
1004 downloader_cat.warning()
1005 << _NOTIFY_HTTP_CHANNEL_ID
1006 <<
"nonblocking socket BIO has no file descriptor.\n";
1008 _status_entry._status_code = SC_internal_error;
1009 _state = S_try_next_proxy;
1013 if (downloader_cat.is_spam()) {
1014 downloader_cat.spam()
1015 << _NOTIFY_HTTP_CHANNEL_ID
1016 <<
"waiting to connect to " << _request.get_url().get_server_and_port() <<
".\n";
1022 if (get_blocking_connect()) {
1025 tv.tv_sec = (int)_connect_timeout;
1026 tv.tv_usec = (int)((_connect_timeout - tv.tv_sec) * 1000000.0);
1032 int errcode = select(fd + 1,
nullptr, &wset,
nullptr, &tv);
1034 downloader_cat.warning()
1035 << _NOTIFY_HTTP_CHANNEL_ID
1036 <<
"Error in select.\n";
1038 _status_entry._status_code = SC_internal_error;
1039 _state = S_try_next_proxy;
1045 if (get_blocking_connect() ||
1047 _started_connecting_time > get_connect_timeout())) {
1049 downloader_cat.info()
1050 << _NOTIFY_HTTP_CHANNEL_ID
1051 <<
"Timeout connecting to " 1052 << _request.get_url().get_server_and_port()
1053 <<
" for " << _request.get_url()
1055 _status_entry._status_code = SC_timeout;
1056 _state = S_try_next_proxy;
1063 _state = S_connecting;
1074 run_http_proxy_ready() {
1076 nassertr(!_proxy_request_text.empty(),
false);
1077 if (!server_send(_proxy_request_text,
false)) {
1082 _state = S_http_proxy_request_sent;
1094 run_http_proxy_request_sent() {
1097 if (!server_getline_failsafe(line)) {
1102 while (line.empty()) {
1103 if (!server_getline_failsafe(line)) {
1108 if (!parse_http_response(line)) {
1112 _state = S_http_proxy_reading_header;
1113 _current_field_name = string();
1114 _current_field_value = string();
1116 _got_file_size =
false;
1117 _got_transfer_file_size =
false;
1126 run_http_proxy_reading_header() {
1127 if (parse_http_header()) {
1131 _redirect = get_header_value(
"Location");
1135 _server_response_has_no_body =
1136 (get_status_code() / 100 == 1 ||
1137 get_status_code() == 204 ||
1138 get_status_code() == 304);
1140 int last_status = _last_status_code;
1141 _last_status_code = get_status_code();
1143 if (get_status_code() == 407 && last_status != 407 && !_proxy.empty()) {
1145 string authenticate_request = get_header_value(
"Proxy-Authenticate");
1146 _proxy_auth = _client->generate_auth(_proxy,
true, authenticate_request);
1147 if (_proxy_auth !=
nullptr) {
1148 _proxy_realm = _proxy_auth->get_realm();
1149 _proxy_username = _client->select_username(_proxy,
true, _proxy_realm);
1150 if (!_proxy_username.empty()) {
1151 make_proxy_request_text();
1154 _state = S_begin_body;
1166 if (get_status_code() != 407) {
1167 _status_entry._status_code += 1000;
1170 _state = S_try_next_proxy;
1175 make_request_text();
1178 _state = S_setup_ssl;
1191 run_socks_proxy_greet() {
1192 static const char socks_greeting[] = {
1201 static const int socks_greeting_len =
sizeof(socks_greeting);
1202 if (!server_send(
string(socks_greeting, socks_greeting_len),
true)) {
1208 _state = S_socks_proxy_greet_reply;
1216 run_socks_proxy_greet_reply() {
1220 if (!server_get_failsafe(reply, 2)) {
1224 if (reply[0] != 0x05) {
1226 downloader_cat.info()
1227 << _NOTIFY_HTTP_CHANNEL_ID
1228 <<
"Rejecting Socks version " << (int)reply[0] <<
"\n";
1229 _status_entry._status_code = SC_socks_invalid_version;
1230 _state = S_try_next_proxy;
1234 if (reply[1] == (
char)0xff) {
1235 downloader_cat.info()
1236 << _NOTIFY_HTTP_CHANNEL_ID
1237 <<
"Socks server does not accept our available login methods.\n";
1238 _status_entry._status_code = SC_socks_no_acceptable_login_method;
1239 _state = S_try_next_proxy;
1243 if (reply[1] == 0x00) {
1245 _state = S_socks_proxy_connect;
1250 downloader_cat.info()
1251 << _NOTIFY_HTTP_CHANNEL_ID
1252 <<
"Socks server accepted unrequested login method " 1253 << (int)reply[1] <<
"\n";
1254 _status_entry._status_code = SC_socks_no_acceptable_login_method;
1255 _state = S_try_next_proxy;
1263 run_socks_proxy_connect() {
1264 static const char socks_connect[] = {
1270 static const int socks_connect_len =
sizeof(socks_connect);
1272 string hostname = _request.get_url().get_server();
1273 int port = _request.get_url().get_port();
1275 if (downloader_cat.is_debug()) {
1276 downloader_cat.debug()
1277 << _NOTIFY_HTTP_CHANNEL_ID
1278 <<
"Requesting SOCKS5 connection to " 1279 << _request.get_url().get_server_and_port() <<
"\n";
1283 string(socks_connect, socks_connect_len) +
1284 string(1, (
char)hostname.length()) +
1286 string(1, (
char)((port >> 8) & 0xff)) +
1287 string(1, (
char)(port & 0xff));
1289 if (!server_send(connect,
true)) {
1294 _state = S_socks_proxy_connect_reply;
1302 run_socks_proxy_connect_reply() {
1306 if (!server_get_failsafe(reply, 2)) {
1310 if (reply[0] != 0x05) {
1312 downloader_cat.info()
1313 << _NOTIFY_HTTP_CHANNEL_ID
1314 <<
"Rejecting Socks version " << (int)reply[0] <<
"\n";
1316 _status_entry._status_code = SC_socks_invalid_version;
1317 _state = S_try_next_proxy;
1321 if (reply[1] != 0x00) {
1322 downloader_cat.info()
1323 << _NOTIFY_HTTP_CHANNEL_ID
1324 <<
"Connection refused, SOCKS code " << (int)reply[1] <<
"\n";
1346 _status_entry._status_code = SC_socks_no_connection;
1350 _status_entry._status_code = SC_socks_refused;
1354 _state = S_try_next_proxy;
1359 _working_get = reply;
1360 if (!server_get_failsafe(reply, 5)) {
1365 int total_bytes = 6;
1373 total_bytes += (
unsigned int)reply[4];
1381 downloader_cat.info()
1382 << _NOTIFY_HTTP_CHANNEL_ID
1383 <<
"Unsupported SOCKS address type: " << (int)reply[3] <<
"\n";
1384 _status_entry._status_code = SC_socks_invalid_version;
1385 _state = S_try_next_proxy;
1390 _working_get = reply;
1391 if (!server_get_failsafe(reply, total_bytes)) {
1395 if (downloader_cat.is_debug()) {
1397 string connect_host;
1403 strm << (
unsigned int)(
unsigned char)reply[4] <<
"." 1404 << (
unsigned int)(
unsigned char)reply[5] <<
"." 1405 << (
unsigned int)(
unsigned char)reply[6] <<
"." 1406 << (
unsigned int)(
unsigned char)reply[7];
1407 connect_host = strm.str();
1412 connect_host = string(&reply[5], (
unsigned int)reply[4]);
1418 sprintf(buf,
"[%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx" 1419 ":%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx:%02hhx%02hhx]",
1420 reply[4], reply[5], reply[6], reply[7], reply[8], reply[9],
1421 reply[10], reply[11], reply[12], reply[13], reply[14],
1422 reply[15], reply[16], reply[17], reply[18], reply[19]);
1429 (((
unsigned int)(
unsigned char)reply[total_bytes - 2]) << 8) |
1430 ((
unsigned int)(
unsigned char)reply[total_bytes - 1]);
1432 downloader_cat.debug()
1433 << _NOTIFY_HTTP_CHANNEL_ID
1434 << _proxy <<
" directed us to " << connect_host <<
":" 1435 << connect_port <<
"\n";
1439 _state = S_setup_ssl;
1453 _sbio = BIO_new_ssl(_client->get_ssl_ctx(),
true);
1454 BIO_push(_sbio, *_bio);
1457 BIO_get_ssl(_sbio, &ssl);
1458 nassertr(ssl !=
nullptr,
false);
1462 string cipher_list = _cipher_list;
1463 if (!cipher_list.empty()) {
1464 size_t space = cipher_list.find(
" ");
1465 if (space != string::npos) {
1466 cipher_list = cipher_list.substr(0, space);
1470 if (downloader_cat.is_debug()) {
1471 downloader_cat.debug()
1472 << _NOTIFY_HTTP_CHANNEL_ID
1473 <<
"Setting ssl-cipher-list '" << cipher_list <<
"'\n";
1475 int result = SSL_set_cipher_list(ssl, cipher_list.c_str());
1477 downloader_cat.error()
1478 << _NOTIFY_HTTP_CHANNEL_ID
1479 <<
"Invalid cipher list: '" << cipher_list <<
"'\n";
1480 OpenSSLWrapper::get_global_ptr()->notify_ssl_errors();
1481 _status_entry._status_code = SC_ssl_internal_failure;
1486 string hostname = _request.get_url().get_server();
1487 result = SSL_set_tlsext_host_name(ssl, hostname.c_str());
1489 downloader_cat.error()
1490 << _NOTIFY_HTTP_CHANNEL_ID
1491 <<
"Could not set TLS SNI hostname to '" << hostname <<
"'\n";
1503 if (_client->load_client_certificate()) {
1504 SSL_use_certificate(ssl, _client->_client_certificate_pub);
1505 SSL_use_PrivateKey(ssl, _client->_client_certificate_priv);
1506 if (!SSL_check_private_key(ssl)) {
1507 downloader_cat.warning()
1508 << _NOTIFY_HTTP_CHANNEL_ID
1509 <<
"Client private key does not match public key!\n";
1513 if (downloader_cat.is_spam()) {
1514 downloader_cat.spam()
1515 << _NOTIFY_HTTP_CHANNEL_ID
1516 <<
"SSL Ciphers available:\n";
1519 name = SSL_get_cipher_list(ssl, pri);
1520 while (name !=
nullptr) {
1521 downloader_cat.spam()
1522 << _NOTIFY_HTTP_CHANNEL_ID
1523 <<
" " << pri + 1 <<
". " << name <<
"\n";
1525 name = SSL_get_cipher_list(ssl, pri);
1529 if (downloader_cat.is_debug()) {
1530 downloader_cat.debug()
1531 << _NOTIFY_HTTP_CHANNEL_ID
1532 <<
"performing SSL handshake\n";
1534 _state = S_ssl_handshake;
1537 _started_connecting_time =
1548 run_ssl_handshake() {
1549 if (BIO_do_handshake(_sbio) <= 0) {
1550 if (BIO_should_retry(_sbio)) {
1553 _started_connecting_time;
1554 if (elapsed <= get_connect_timeout() + _extra_ssl_handshake_time) {
1561 downloader_cat.info()
1562 << _NOTIFY_HTTP_CHANNEL_ID
1563 <<
"Could not establish SSL handshake with " 1564 << _request.get_url().get_server_and_port() <<
"\n";
1565 OpenSSLWrapper::get_global_ptr()->notify_ssl_errors();
1570 if (!_cipher_list.empty()) {
1572 size_t space = _cipher_list.find(
" ");
1573 if (space != string::npos) {
1574 while (space < _cipher_list.length() && _cipher_list[space] ==
' ') {
1577 _cipher_list = _cipher_list.substr(space);
1578 if (!_cipher_list.empty()) {
1581 _state = S_connecting;
1588 _cipher_list = _client->get_cipher_list();
1589 _status_entry._status_code = SC_ssl_no_handshake;
1595 BIO_get_ssl(_sbio, &ssl);
1596 nassertr(ssl !=
nullptr,
false);
1598 if (!_nonblocking) {
1599 SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
1602 const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
1603 if (cipher ==
nullptr) {
1604 downloader_cat.warning()
1605 << _NOTIFY_HTTP_CHANNEL_ID
1606 <<
"No current cipher on SSL connection.\n";
1608 if (downloader_cat.is_debug()) {
1609 downloader_cat.debug()
1610 << _NOTIFY_HTTP_CHANNEL_ID
1611 <<
"Using cipher " << SSL_CIPHER_get_name((SSL_CIPHER *) cipher) <<
"\n";
1617 _bio->set_bio(_sbio);
1620 X509 *cert = SSL_get_peer_certificate(ssl);
1621 if (cert ==
nullptr) {
1622 downloader_cat.info()
1623 << _NOTIFY_HTTP_CHANNEL_ID
1624 <<
"No certificate was presented by server.\n";
1627 _status_entry._status_code = SC_ssl_invalid_server_certificate;
1632 X509_NAME *subject = X509_get_subject_name(cert);
1633 if (downloader_cat.is_debug()) {
1634 string org_name = get_x509_name_component(subject, NID_organizationName);
1635 string org_unit_name = get_x509_name_component(subject, NID_organizationalUnitName);
1636 string common_name = get_x509_name_component(subject, NID_commonName);
1638 downloader_cat.debug()
1639 << _NOTIFY_HTTP_CHANNEL_ID
1640 <<
"Server is " << common_name <<
" from " << org_unit_name
1641 <<
" / " << org_name <<
"\n";
1643 if (downloader_cat.is_spam()) {
1644 downloader_cat.spam()
1645 << _NOTIFY_HTTP_CHANNEL_ID
1646 <<
"Received certificate from server:\n" << std::flush;
1647 X509_print_fp(stderr, cert);
1652 bool cert_preapproved =
false;
1653 bool cert_name_preapproved =
false;
1654 check_preapproved_server_certificate(cert, cert_preapproved, cert_name_preapproved);
1657 long verify_result = SSL_get_verify_result(ssl);
1658 bool cert_valid =
true;
1660 if (verify_result == X509_V_ERR_CERT_HAS_EXPIRED) {
1661 downloader_cat.info()
1662 << _NOTIFY_HTTP_CHANNEL_ID
1663 <<
"Expired certificate from " << _request.get_url().get_server_and_port() <<
"\n";
1664 if (_client->get_verify_ssl() == HTTPClient::VS_normal && !cert_preapproved) {
1668 }
else if (verify_result == X509_V_ERR_CERT_NOT_YET_VALID) {
1669 downloader_cat.info()
1670 << _NOTIFY_HTTP_CHANNEL_ID
1671 <<
"Premature certificate from " << _request.get_url().get_server_and_port() <<
"\n";
1672 if (_client->get_verify_ssl() == HTTPClient::VS_normal && !cert_preapproved) {
1676 }
else if (verify_result == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
1677 verify_result == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) {
1678 downloader_cat.info()
1679 << _NOTIFY_HTTP_CHANNEL_ID
1680 <<
"Self-signed certificate from " << _request.get_url().get_server_and_port() <<
"\n";
1681 if (_client->get_verify_ssl() != HTTPClient::VS_no_verify && !cert_preapproved) {
1685 }
else if (verify_result != X509_V_OK) {
1686 downloader_cat.info()
1687 << _NOTIFY_HTTP_CHANNEL_ID
1688 <<
"Unable to verify identity of " << _request.get_url().get_server_and_port()
1689 <<
", verify error code " << verify_result <<
"\n";
1690 if (_client->get_verify_ssl() != HTTPClient::VS_no_verify && !cert_preapproved) {
1696 _status_entry._status_code = SC_ssl_invalid_server_certificate;
1701 if (_client->get_verify_ssl() != HTTPClient::VS_no_verify && !cert_name_preapproved) {
1703 if (!validate_server_name(cert)) {
1704 _status_entry._status_code = SC_ssl_unexpected_server;
1727 if (!_request_text.empty()) {
1728 if (!server_send(_request_text,
false)) {
1734 _state = S_request_sent;
1744 run_request_sent() {
1747 if (!server_getline_failsafe(line)) {
1752 while (line.empty()) {
1753 if (!server_getline_failsafe(line)) {
1758 if (!parse_http_response(line)) {
1763 _state = S_reading_header;
1764 _current_field_name = string();
1765 _current_field_value = string();
1767 _got_file_size =
false;
1768 _got_transfer_file_size =
false;
1778 run_reading_header() {
1779 if (parse_http_header()) {
1780 if (_bio.is_null()) {
1781 downloader_cat.info()
1782 << _NOTIFY_HTTP_CHANNEL_ID
1783 <<
"Connection lost while reading HTTP response.\n";
1784 if (_response_type == RT_http_hangup) {
1786 _status_entry._status_code = SC_lost_connection;
1787 _state = S_try_next_proxy;
1791 _response_type = RT_http_hangup;
1798 if (elapsed > get_http_timeout()) {
1800 downloader_cat.info()
1801 << _NOTIFY_HTTP_CHANNEL_ID
1802 <<
"Timeout waiting for " 1803 << _request.get_url().get_server_and_port()
1804 <<
" in run_reading_header (" << elapsed
1805 <<
" seconds elapsed).\n";
1806 _status_entry._status_code = SC_timeout;
1807 _state = S_try_next_proxy;
1812 _response_type = RT_http_complete;
1816 clear_extra_headers();
1818 _server_response_has_no_body =
1819 (get_status_code() / 100 == 1 ||
1820 get_status_code() == 204 ||
1821 get_status_code() == 304 ||
1822 _method == HTTPEnum::M_head);
1825 if (get_status_code() == 206) {
1826 string content_range = get_header_value(
"Content-Range");
1827 if (content_range.empty()) {
1828 downloader_cat.warning()
1829 << _NOTIFY_HTTP_CHANNEL_ID
1830 <<
"Got 206 response without Content-Range header!\n";
1831 _status_entry._status_code = SC_invalid_http;
1836 if (!parse_content_range(content_range)) {
1837 downloader_cat.warning()
1838 << _NOTIFY_HTTP_CHANNEL_ID
1839 <<
"Couldn't parse Content-Range: " << content_range <<
"\n";
1840 _status_entry._status_code = SC_invalid_http;
1847 _first_byte_delivered = 0;
1848 _last_byte_delivered = 0;
1850 if (downloader_cat.is_debug()) {
1851 if (_first_byte_requested != 0 || _last_byte_requested != 0 ||
1852 _first_byte_delivered != 0 || _last_byte_delivered != 0) {
1853 downloader_cat.debug()
1854 << _NOTIFY_HTTP_CHANNEL_ID
1855 <<
"Requested byte range " << _first_byte_requested
1856 <<
" to " << _last_byte_delivered
1857 <<
"; server delivers range " << _first_byte_delivered
1858 <<
" to " << _last_byte_delivered
1865 string tag = get_header_value(
"ETag");
1869 string date = get_header_value(
"Last-Modified");
1870 if (!date.empty()) {
1871 _document_spec.set_date(
HTTPDate(date));
1877 if (_server_response_has_no_body) {
1879 reset_download_to();
1882 if (!open_download_file()) {
1886 _got_expected_file_size =
false;
1887 _got_file_size =
false;
1888 _got_transfer_file_size =
false;
1890 string content_length = get_header_value(
"Content-Length");
1891 if (!content_length.empty()) {
1892 _file_size = atoi(content_length.c_str());
1893 _got_file_size =
true;
1895 }
else if (get_status_code() == 206) {
1898 _file_size = _last_byte_delivered - _first_byte_delivered + 1;
1899 _got_file_size =
true;
1901 _redirect = get_header_value(
"Location");
1906 if (_redirect.has_path() && !_redirect.has_authority()) {
1908 Filename path = _redirect.get_path();
1911 _redirect.set_path(
Filename(rel_to, path));
1917 _state = S_read_header;
1919 if (_server_response_has_no_body && will_close_connection()) {
1926 int last_status = _last_status_code;
1927 _last_status_code = get_status_code();
1929 if (get_status_code() == 407 && last_status != 407 && !_proxy.empty()) {
1931 string authenticate_request = get_header_value(
"Proxy-Authenticate");
1933 _client->generate_auth(_proxy,
true, authenticate_request);
1934 if (_proxy_auth !=
nullptr) {
1935 _proxy_realm = _proxy_auth->get_realm();
1936 _proxy_username = _client->select_username(_proxy,
true, _proxy_realm);
1937 if (!_proxy_username.empty()) {
1938 make_request_text();
1941 _state = S_begin_body;
1947 if (get_status_code() == 401 && last_status != 401) {
1949 string authenticate_request = get_header_value(
"WWW-Authenticate");
1950 _www_auth = _client->generate_auth(_request.get_url(),
false, authenticate_request);
1951 if (_www_auth !=
nullptr) {
1952 _www_realm = _www_auth->get_realm();
1953 _www_username = _client->select_username(_request.get_url(),
false, _www_realm);
1954 if (!_www_username.empty()) {
1955 make_request_text();
1958 _state = S_begin_body;
1964 if ((get_status_code() == 300 ||
1965 get_status_code() == 301 ||
1966 get_status_code() == 302 ||
1967 get_status_code() == 303 ||
1968 get_status_code() == 307) && !get_redirect().empty()) {
1976 if (_method == HTTPEnum::M_post) {
1977 _method = HTTPEnum::M_get;
1981 if (_method == HTTPEnum::M_get || _method == HTTPEnum::M_head) {
1983 URLSpec new_url = get_redirect();
1984 if (find(_redirect_trail.begin(), _redirect_trail.end(),
1985 new_url) != _redirect_trail.end()) {
1986 downloader_cat.warning()
1987 << _NOTIFY_HTTP_CHANNEL_ID
1988 <<
"cycle detected in redirect to " << new_url <<
"\n";
1991 _redirect_trail.push_back(new_url);
1993 if (downloader_cat.is_debug()) {
1994 downloader_cat.debug()
1995 << _NOTIFY_HTTP_CHANNEL_ID
1996 <<
"following redirect to " << new_url <<
"\n";
1998 if (_request.get_url().has_username()) {
1999 new_url.
set_username(_request.get_url().get_username());
2001 reset_url(_request.get_url(), new_url);
2002 _request.set_url(new_url);
2003 _want_ssl = _request.get_url().is_ssl();
2006 make_request_text();
2009 _state = S_begin_body;
2015 if (_state == S_read_header &&
2016 ((get_status_code() / 100) == 4 || (get_status_code() / 100) == 5) &&
2017 _proxy_serves_document && _proxy_next_index < _proxies.size()) {
2024 _state = S_try_next_proxy;
2037 run_start_direct_file_read() {
2038 _state = S_read_header;
2039 if (!open_download_file()) {
2059 _state = S_begin_body;
2069 if (will_close_connection()) {
2072 if (downloader_cat.is_debug()) {
2073 downloader_cat.debug()
2074 << _NOTIFY_HTTP_CHANNEL_ID
2075 <<
"resetting to begin body; server would close anyway.\n";
2081 if (_server_response_has_no_body) {
2083 _state = S_read_trailer;
2085 }
else if (get_file_size() > (
int)_skip_body_size) {
2089 if (downloader_cat.is_debug()) {
2090 downloader_cat.debug()
2091 << _NOTIFY_HTTP_CHANNEL_ID
2092 <<
"Dropping connection rather than skipping past " 2093 << get_file_size() <<
" bytes.\n";
2099 if (_body_stream ==
nullptr) {
2100 if (downloader_cat.is_debug()) {
2101 downloader_cat.debug()
2102 << _NOTIFY_HTTP_CHANNEL_ID
2103 <<
"Unable to skip body.\n";
2108 _owns_body_stream =
true;
2109 if (_state != S_reading_body) {
2110 reset_body_stream();
2127 run_reading_body() {
2128 if (will_close_connection()) {
2131 if (downloader_cat.is_debug()) {
2132 downloader_cat.debug()
2133 << _NOTIFY_HTTP_CHANNEL_ID
2134 <<
"resetting to read body; server would close anyway.\n";
2141 if (_body_stream ==
nullptr || !_owns_body_stream) {
2143 if (downloader_cat.is_debug()) {
2144 downloader_cat.debug()
2145 << _NOTIFY_HTTP_CHANNEL_ID
2146 <<
"resetting, not in skip-body mode.\n";
2153 std::getline(*_body_stream, line);
2154 while (!_body_stream->fail() && !_body_stream->eof()) {
2155 if (downloader_cat.is_spam()) {
2156 downloader_cat.spam()
2157 << _NOTIFY_HTTP_CHANNEL_ID
2158 <<
"skip: " << line <<
"\n";
2160 std::getline(*_body_stream, line);
2163 if (!_body_stream->is_closed()) {
2168 reset_body_stream();
2171 nassertr(_state != S_reading_body,
false);
2186 if (will_close_connection()) {
2189 if (downloader_cat.is_debug()) {
2190 downloader_cat.debug()
2191 << _NOTIFY_HTTP_CHANNEL_ID
2192 <<
"resetting to read body; server would close anyway.\n";
2200 if (!server_getline(line)) {
2203 while (!line.empty()) {
2204 if (!server_getline(line)) {
2209 _state = S_read_trailer;
2218 run_read_trailer() {
2219 if (will_close_connection()) {
2222 if (downloader_cat.is_debug()) {
2223 downloader_cat.debug()
2224 << _NOTIFY_HTTP_CHANNEL_ID
2225 <<
"resetting to read trailer; server would close anyway.\n";
2240 run_download_to_file() {
2241 nassertr(_body_stream !=
nullptr && _owns_body_stream,
false);
2243 bool do_throttle = _wanted_nonblocking && _download_throttle;
2245 static const size_t buffer_size = 4096;
2246 char buffer[buffer_size];
2248 size_t remaining_this_pass = buffer_size;
2250 remaining_this_pass = _bytes_per_update;
2253 _body_stream->read(buffer, min(buffer_size, remaining_this_pass));
2254 size_t count = _body_stream->gcount();
2255 while (count != 0) {
2256 _download_to_stream->write(buffer, count);
2257 _bytes_downloaded += count;
2259 nassertr(count <= remaining_this_pass,
false);
2260 remaining_this_pass -= count;
2261 if (remaining_this_pass == 0) {
2267 thread_consider_yield();
2268 _body_stream->read(buffer, min(buffer_size, remaining_this_pass));
2269 count = _body_stream->gcount();
2272 if (_download_to_stream->fail()) {
2273 downloader_cat.warning()
2274 << _NOTIFY_HTTP_CHANNEL_ID
2275 <<
"Error writing to " << _download_to_filename <<
"\n";
2276 _status_entry._status_code = SC_download_write_error;
2278 reset_download_to();
2282 _download_to_stream->flush();
2284 if (_body_stream->is_closed()) {
2286 reset_body_stream();
2287 close_download_stream();
2288 _started_download =
false;
2301 run_download_to_ram() {
2302 nassertr(_body_stream !=
nullptr && _owns_body_stream,
false);
2303 nassertr(_download_to_ramfile !=
nullptr,
false);
2305 bool do_throttle = _wanted_nonblocking && _download_throttle;
2307 static const size_t buffer_size = 4096;
2308 char buffer[buffer_size];
2310 size_t remaining_this_pass = buffer_size;
2312 remaining_this_pass = _bytes_per_update;
2315 _body_stream->read(buffer, min(buffer_size, remaining_this_pass));
2316 size_t count = _body_stream->gcount();
2317 while (count != 0) {
2318 _download_to_ramfile->_data += string(buffer, count);
2319 _bytes_downloaded += count;
2321 nassertr(count <= remaining_this_pass,
false);
2322 remaining_this_pass -= count;
2323 if (remaining_this_pass == 0) {
2329 thread_consider_yield();
2330 _body_stream->read(buffer, min(buffer_size, remaining_this_pass));
2331 count = _body_stream->gcount();
2334 if (_body_stream->is_closed()) {
2336 reset_body_stream();
2337 close_download_stream();
2338 _started_download =
false;
2351 run_download_to_stream() {
2352 nassertr(_body_stream !=
nullptr && _owns_body_stream,
false);
2354 bool do_throttle = _wanted_nonblocking && _download_throttle;
2356 static const size_t buffer_size = 4096;
2357 char buffer[buffer_size];
2359 size_t remaining_this_pass = buffer_size;
2361 remaining_this_pass = _bytes_per_update;
2364 _body_stream->read(buffer, min(buffer_size, remaining_this_pass));
2365 size_t count = _body_stream->gcount();
2366 while (count != 0) {
2367 _download_to_stream->write(buffer, count);
2368 _bytes_downloaded += count;
2370 nassertr(count <= remaining_this_pass,
false);
2371 remaining_this_pass -= count;
2372 if (remaining_this_pass == 0) {
2378 thread_consider_yield();
2379 _body_stream->read(buffer, min(buffer_size, remaining_this_pass));
2380 count = _body_stream->gcount();
2383 if (_download_to_stream->fail()) {
2384 downloader_cat.warning()
2385 << _NOTIFY_HTTP_CHANNEL_ID
2386 <<
"Error writing to stream\n";
2387 _status_entry._status_code = SC_download_write_error;
2389 reset_download_to();
2393 _download_to_stream->flush();
2395 if (_body_stream->is_closed()) {
2397 reset_body_stream();
2398 close_download_stream();
2399 _started_download =
false;
2413 begin_request(HTTPEnum::Method method,
const DocumentSpec &url,
2414 const string &body,
bool nonblocking,
2415 size_t first_byte,
size_t last_byte) {
2417 downloader_cat.info()
2418 << _NOTIFY_HTTP_CHANNEL_ID
2419 <<
"begin " << method <<
" " << url <<
"\n";
2421 reset_for_new_request();
2423 _wanted_nonblocking = nonblocking;
2424 #if defined(HAVE_THREADS) && defined(SIMPLE_THREADS) 2432 _proxy_next_index = 0;
2433 if (get_allow_proxy()) {
2434 _client->get_proxies_for_url(url.get_url(), _proxies);
2440 if (!_bio.is_null() && !_proxies.empty() && !_proxy.empty()) {
2441 Proxies::iterator pi = find(_proxies.begin(), _proxies.end(), _proxy);
2442 if (pi != _proxies.end()) {
2444 _proxies.insert(_proxies.begin(), _proxy);
2449 if (_proxy_next_index < _proxies.size()) {
2450 new_proxy = _proxies[_proxy_next_index];
2451 _proxy_next_index++;
2455 if (_proxy != new_proxy) {
2457 _proxy_auth =
nullptr;
2458 if (downloader_cat.is_debug()) {
2459 downloader_cat.debug()
2460 << _NOTIFY_HTTP_CHANNEL_ID
2461 <<
"resetting to change proxy to " << _proxy <<
"\n";
2467 if (_nonblocking != nonblocking) {
2468 _nonblocking = nonblocking;
2469 if (downloader_cat.is_debug()) {
2470 downloader_cat.debug()
2471 << _NOTIFY_HTTP_CHANNEL_ID
2472 <<
"resetting to change nonblocking state to " << _nonblocking <<
".\n";
2477 reset_url(_request.get_url(), url.get_url());
2484 _want_ssl = _request.get_url().is_ssl();
2486 _first_byte_requested = first_byte;
2487 _last_byte_requested = last_byte;
2493 if (_request.get_url().get_scheme() ==
"file") {
2498 _bio =
new BioPtr(_request.get_url());
2499 if (_bio->get_bio() !=
nullptr) {
2501 _source =
new BioStreamPtr(
new BioStream(_bio));
2502 _status_entry._status_code = 200;
2503 _state = S_start_direct_file_read;
2507 BIO_get_fp(_bio->get_bio(), &fp);
2508 if (fp !=
nullptr) {
2509 if (fseek(fp, 0, SEEK_END) == 0) {
2510 _file_size = ftell(fp);
2511 _got_file_size =
true;
2512 fseek(fp, 0, SEEK_SET);
2518 OpenSSLWrapper::get_global_ptr()->notify_ssl_errors();
2519 _status_entry._status_code = SC_no_connection;
2525 if (_state == S_failure || (_state < S_read_header && _state != S_ready)) {
2526 if (downloader_cat.is_debug()) {
2527 downloader_cat.debug()
2528 << _NOTIFY_HTTP_CHANNEL_ID
2529 <<
"resetting to clear previous request.\n";
2534 if (downloader_cat.is_debug()) {
2535 downloader_cat.debug()
2536 << _NOTIFY_HTTP_CHANNEL_ID
2537 <<
"resetting old connection: " 2543 }
else if (_state == S_read_header) {
2545 _state = S_begin_body;
2549 if (_method == HTTPEnum::M_connect) {
2550 _done_state = S_ready;
2552 _done_state = S_read_header;
2563 reconsider_proxy() {
2564 _proxy_tunnel_now =
false;
2565 _proxy_serves_document =
false;
2567 if (!_proxy.empty()) {
2574 (get_proxy_tunnel() || _want_ssl ||
2575 _method == HTTPEnum::M_connect || _proxy.get_scheme() ==
"socks");
2579 _proxy_serves_document = !_proxy_tunnel_now;
2583 make_request_text();
2585 if (_proxy_tunnel_now) {
2588 ostringstream request;
2590 <<
"CONNECT " << _request.get_url().get_server_and_port()
2591 <<
" " << _client->get_http_version_string() <<
"\r\n";
2592 if (_client->get_http_version() >= HTTPEnum::HV_11) {
2594 <<
"Host: " << _request.get_url().get_server_and_port() <<
"\r\n";
2596 _proxy_header = request.str();
2597 make_proxy_request_text();
2600 _proxy_header = string();
2601 _proxy_request_text = string();
2611 reset_for_new_request() {
2612 if (downloader_cat.is_spam()) {
2613 downloader_cat.spam()
2614 << _NOTIFY_HTTP_CHANNEL_ID
2615 <<
"reset_for_new_request.\n";
2618 reset_download_to();
2619 reset_body_stream();
2621 _last_status_code = 0;
2622 _status_entry = StatusEntry();
2624 _response_type = RT_none;
2625 _redirect_trail.clear();
2626 _bytes_downloaded = 0;
2627 _bytes_requested = 0;
2639 finished_body(
bool has_trailer) {
2640 if (will_close_connection() && _download_dest == DD_none) {
2641 if (downloader_cat.is_debug()) {
2642 downloader_cat.debug()
2643 << _NOTIFY_HTTP_CHANNEL_ID
2644 <<
"resetting to finish body; server would close anyway.\n";
2650 _state = HTTPChannel::S_read_body;
2652 _state = HTTPChannel::S_read_trailer;
2666 open_download_file() {
2667 _subdocument_resumes = (_subdocument_resumes && _first_byte_delivered != 0);
2669 if (_download_dest == DD_file) {
2671 _download_to_stream = vfs->
open_write_file(_download_to_filename,
false, !_subdocument_resumes);
2672 if (_download_to_stream ==
nullptr) {
2673 downloader_cat.info()
2674 << _NOTIFY_HTTP_CHANNEL_ID
2675 <<
"Could not open " << _download_to_filename <<
" for writing.\n";
2676 _status_entry._status_code = SC_download_open_error;
2682 if (_subdocument_resumes) {
2683 if (_download_dest == DD_file) {
2687 _download_to_stream->seekp(0, std::ios::end);
2688 if (_first_byte_delivered > (
size_t)_download_to_stream->tellp()) {
2689 downloader_cat.info()
2690 << _NOTIFY_HTTP_CHANNEL_ID
2691 <<
"Invalid starting position of byte " << _first_byte_delivered
2692 <<
" within " << _download_to_filename <<
" (which has " 2693 << _download_to_stream->tellp() <<
" bytes)\n";
2694 close_download_stream();
2695 _status_entry._status_code = SC_download_invalid_range;
2700 _download_to_stream->seekp(_first_byte_delivered);
2702 }
else if (_download_dest == DD_ram) {
2703 if (_first_byte_delivered > _download_to_ramfile->_data.length()) {
2704 downloader_cat.info()
2705 << _NOTIFY_HTTP_CHANNEL_ID
2706 <<
"Invalid starting position of byte " << _first_byte_delivered
2707 <<
" within Ramfile (which has " 2708 << _download_to_ramfile->_data.length() <<
" bytes)\n";
2709 close_download_stream();
2710 _status_entry._status_code = SC_download_invalid_range;
2715 if (_first_byte_delivered == 0) {
2716 _download_to_ramfile->_data = string();
2718 _download_to_ramfile->_data =
2719 _download_to_ramfile->_data.substr(0, _first_byte_delivered);
2721 }
else if (_download_dest == DD_stream) {
2725 _download_to_stream->seekp(0, std::ios::end);
2726 if (_first_byte_delivered > (
size_t)_download_to_stream->tellp()) {
2727 downloader_cat.info()
2728 << _NOTIFY_HTTP_CHANNEL_ID
2729 <<
"Invalid starting position of byte " << _first_byte_delivered
2730 <<
" within stream (which has " 2731 << _download_to_stream->tellp() <<
" bytes)\n";
2732 close_download_stream();
2733 _status_entry._status_code = SC_download_invalid_range;
2738 _download_to_stream->seekp(_first_byte_delivered);
2745 if (_download_dest == DD_file || _download_dest == DD_stream) {
2746 _download_to_stream->seekp(0);
2747 }
else if (_download_dest == DD_ram) {
2748 _download_to_ramfile->_data = string();
2762 server_getline(
string &str) {
2763 nassertr(!_source.is_null(),
false);
2764 int ch = (*_source)->get();
2765 while (!(*_source)->eof() && !(*_source)->fail()) {
2770 _working_get = string();
2774 size_t p = str.length();
2775 while (p > 0 && isspace(str[p - 1])) {
2778 str = str.substr(0, p);
2780 if (downloader_cat.is_spam()) {
2781 downloader_cat.spam()
2782 << _NOTIFY_HTTP_CHANNEL_ID
2783 <<
"recv: " << str <<
"\n";
2792 _working_get += (char)ch;
2794 ch = (*_source)->get();
2807 server_getline_failsafe(
string &str) {
2808 if (!server_getline(str)) {
2809 if (_bio.is_null()) {
2811 if (_response_type == RT_hangup) {
2813 _status_entry._status_code = SC_lost_connection;
2814 _state = S_try_next_proxy;
2818 _response_type = RT_hangup;
2825 if (elapsed > get_http_timeout()) {
2827 downloader_cat.info()
2828 << _NOTIFY_HTTP_CHANNEL_ID
2829 <<
"Timeout waiting for " 2830 << _request.get_url().get_server_and_port()
2831 <<
" in server_getline_failsafe (" << elapsed
2832 <<
" seconds elapsed).\n";
2833 _status_entry._status_code = SC_timeout;
2834 _state = S_try_next_proxy;
2850 server_get(
string &str,
size_t num_bytes) {
2851 nassertr(!_source.is_null(),
false);
2852 int ch = (*_source)->get();
2853 while (!(*_source)->eof() && !(*_source)->fail()) {
2854 _working_get += (char)ch;
2855 if (_working_get.length() >= num_bytes) {
2857 _working_get = string();
2861 ch = (*_source)->get();
2874 server_get_failsafe(
string &str,
size_t num_bytes) {
2875 if (!server_get(str, num_bytes)) {
2876 if (_bio.is_null()) {
2878 if (_response_type == RT_hangup) {
2880 _status_entry._status_code = SC_lost_connection;
2881 _state = S_try_next_proxy;
2885 _response_type = RT_hangup;
2892 if (elapsed > get_http_timeout()) {
2894 downloader_cat.info()
2895 << _NOTIFY_HTTP_CHANNEL_ID
2896 <<
"Timeout waiting for " 2897 << _request.get_url().get_server_and_port()
2898 <<
" in server_get_failsafe (" << elapsed
2899 <<
" seconds elapsed).\n";
2900 _status_entry._status_code = SC_timeout;
2901 _state = S_try_next_proxy;
2921 server_send(
const string &str,
bool secret) {
2922 nassertr(str.length() > _sent_so_far,
true);
2927 size_t bytes_to_send = str.length() - _sent_so_far;
2929 BIO_write(*_bio, str.data() + _sent_so_far, bytes_to_send);
2931 if (write_count <= 0) {
2932 if (BIO_should_retry(*_bio)) {
2937 if (downloader_cat.is_debug()) {
2938 downloader_cat.debug()
2939 << _NOTIFY_HTTP_CHANNEL_ID
2940 <<
"Lost connection to server unexpectedly during write.\n";
2946 if (downloader_cat.is_spam()) {
2947 downloader_cat.spam()
2948 << _NOTIFY_HTTP_CHANNEL_ID
2949 <<
"wrote " << write_count <<
" bytes to " << _bio <<
"\n";
2953 if (!secret && downloader_cat.is_spam()) {
2954 show_send(str.substr(0, write_count));
2958 if (write_count < (
int)bytes_to_send) {
2959 _sent_so_far += write_count;
2974 parse_http_response(
const string &line) {
2976 if (line.length() < 5 || line.substr(0, 5) != string(
"HTTP/")) {
2978 _status_entry._status_code = SC_non_http_response;
2979 if (_response_type == RT_non_http) {
2981 _state = S_try_next_proxy;
2986 if (downloader_cat.is_debug()) {
2987 downloader_cat.debug()
2988 << _NOTIFY_HTTP_CHANNEL_ID
2989 <<
"got non-HTTP response, resetting.\n";
2992 _response_type = RT_non_http;
2999 while (p < line.length() && !isspace(line[p])) {
3002 _http_version_string = line.substr(0, p);
3003 _http_version = HTTPClient::parse_http_version_string(_http_version_string);
3005 while (p < line.length() && isspace(line[p])) {
3009 while (q < line.length() && !isspace(line[q])) {
3012 string status_code = line.substr(p, q - p);
3013 _status_entry._status_code = atoi(status_code.c_str());
3015 while (q < line.length() && isspace(line[q])) {
3018 _status_entry._status_string = line.substr(q, line.length() - q);
3028 parse_http_header() {
3030 if (!server_getline(line)) {
3034 while (!line.empty()) {
3035 if (isspace(line[0])) {
3038 while (p < line.length() && isspace(line[p])) {
3041 _current_field_value += line.substr(p - 1);
3045 if (!_current_field_name.empty()) {
3046 store_header_field(_current_field_name, _current_field_value);
3047 _current_field_value = string();
3050 size_t colon = line.find(
':');
3051 if (colon != string::npos) {
3052 _current_field_name =
downcase(line.substr(0, colon));
3053 size_t p = colon + 1;
3054 while (p < line.length() && isspace(line[p])) {
3057 _current_field_value = line.substr(p);
3061 if (!server_getline(line)) {
3067 if (!_current_field_name.empty()) {
3068 store_header_field(_current_field_name, _current_field_value);
3069 _current_field_value = string();
3081 parse_content_range(
const string &content_range) {
3084 while (p < content_range.length() && !isspace(content_range[p])) {
3088 string units = content_range.substr(0, p);
3089 while (p < content_range.length() && isspace(content_range[p])) {
3093 if (units ==
"bytes") {
3094 const char *c_str = content_range.c_str();
3096 if (p < content_range.length() && isdigit(content_range[p])) {
3097 long first_byte = strtol(c_str + p, &endptr, 10);
3099 if (p < content_range.length() && content_range[p] ==
'-') {
3101 if (p < content_range.length() && isdigit(content_range[p])) {
3102 long last_byte = strtol(c_str + p, &endptr, 10);
3105 if (last_byte >= first_byte) {
3106 _first_byte_delivered = first_byte;
3107 _last_byte_delivered = last_byte;
3126 nassertv(!_source.is_null());
3127 if ((*_source)->is_closed()) {
3128 if (downloader_cat.is_debug()) {
3129 downloader_cat.debug()
3130 << _NOTIFY_HTTP_CHANNEL_ID
3131 <<
"Lost connection to server unexpectedly during read.\n";
3328 check_preapproved_server_certificate(X509 *cert,
bool &cert_preapproved,
3329 bool &cert_name_preapproved)
const {
3330 return _client->check_preapproved_server_certificate(_request.get_url(),
3331 cert, cert_preapproved,
3332 cert_name_preapproved);
3340 validate_server_name(X509 *cert) {
3341 string hostname = _request.get_url().get_server();
3343 vector_string cert_names;
3347 STACK_OF(GENERAL_NAME) *subject_alt_names =
3348 (STACK_OF(GENERAL_NAME) *)X509_get_ext_d2i(cert, NID_subject_alt_name,
nullptr,
nullptr);
3349 if (subject_alt_names !=
nullptr) {
3350 int num_alts = sk_GENERAL_NAME_num(subject_alt_names);
3351 for (
int i = 0; i < num_alts; ++i) {
3353 const GENERAL_NAME *alt_name =
3354 sk_GENERAL_NAME_value(subject_alt_names, i);
3356 if (alt_name->type == GEN_DNS) {
3357 char *buffer =
nullptr;
3358 int len = ASN1_STRING_to_UTF8((
unsigned char**)&buffer,
3361 cert_names.push_back(
string(buffer, len));
3363 if (buffer !=
nullptr) {
3364 OPENSSL_free(buffer);
3370 if (cert_names.empty()) {
3373 X509_NAME *xname = X509_get_subject_name(cert);
3374 if (xname !=
nullptr) {
3375 string common_name = get_x509_name_component(xname, NID_commonName);
3376 cert_names.push_back(common_name);
3380 if (cert_names.empty()) {
3381 downloader_cat.info()
3382 << _NOTIFY_HTTP_CHANNEL_ID
3383 <<
"Server certificate from " << hostname
3384 <<
" provides no name.\n";
3388 if (downloader_cat.is_debug()) {
3389 downloader_cat.debug()
3390 << _NOTIFY_HTTP_CHANNEL_ID
3391 <<
"Server certificate from " << hostname
3392 <<
" provides name(s):";
3393 vector_string::const_iterator si;
3394 for (si = cert_names.begin(); si != cert_names.end(); ++si) {
3395 const string &cert_name = (*si);
3396 downloader_cat.debug(
false)
3397 <<
" " << cert_name;
3399 downloader_cat.debug(
false)
3405 vector_string::const_iterator si;
3406 for (si = cert_names.begin(); si != cert_names.end(); ++si) {
3407 const string &cert_name = (*si);
3409 if (match_cert_name(cert_name, hostname)) {
3414 downloader_cat.info()
3415 << _NOTIFY_HTTP_CHANNEL_ID
3416 <<
"Server certificate from " << hostname
3417 <<
" provides wrong name(s):";
3418 for (si = cert_names.begin(); si != cert_names.end(); ++si) {
3419 const string &cert_name = (*si);
3420 downloader_cat.info(
false)
3421 <<
" " << cert_name;
3423 downloader_cat.info(
false)
3434 match_cert_name(
const string &cert_name,
const string &hostname) {
3440 pattern.set_case_sensitive(
false);
3441 pattern.set_nomatch_chars(
".");
3442 return pattern.matches(hostname);
3449 string HTTPChannel::
3450 get_x509_name_component(X509_NAME *name,
int nid) {
3451 ASN1_OBJECT *obj = OBJ_nid2obj(nid);
3453 if (obj ==
nullptr) {
3458 int i = X509_NAME_get_index_by_OBJ(name, obj, -1);
3463 ASN1_STRING *data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i));
3464 return string((
char *)data->data, data->length);
3474 _proxy_auth = _client->select_auth(_proxy,
true, _proxy_realm);
3475 _proxy_username = string();
3476 if (_proxy_auth !=
nullptr) {
3477 _proxy_realm = _proxy_auth->get_realm();
3478 _proxy_username = _client->select_username(_proxy,
true, _proxy_realm);
3481 if (_method == HTTPEnum::M_connect) {
3489 _www_auth = _client->select_auth(_request.get_url(),
false, _www_realm);
3490 _www_username = string();
3491 if (_www_auth !=
nullptr) {
3492 _www_realm = _www_auth->get_realm();
3493 _www_username = _client->select_username(_request.get_url(),
false, _www_realm);
3496 string request_path;
3497 if (_proxy_serves_document) {
3502 request_path = url_no_username.
get_url();
3507 request_path = _request.get_url().get_path_and_query();
3512 if (request_path.empty()) {
3516 ostringstream stream;
3519 << _method <<
" " << request_path <<
" " 3520 << _client->get_http_version_string() <<
"\r\n";
3522 if (_client->get_http_version() >= HTTPEnum::HV_11) {
3524 if (_request.get_url().has_port() && _request.get_url().is_default_port()) {
3529 string server = _request.get_url().get_server();
3530 if (server.find(
':') != string::npos) {
3531 stream <<
"Host: [" << server <<
"]";
3533 stream <<
"Host: " << server;
3536 stream <<
"Host: " << _request.get_url().get_server_and_port();
3539 if (!get_persistent_connection()) {
3541 <<
"Connection: close\r\n";
3545 if (_last_byte_requested != 0) {
3547 <<
"Range: bytes=" << _first_byte_requested <<
"-" 3548 << _last_byte_requested <<
"\r\n";
3550 }
else if (_first_byte_requested != 0) {
3552 <<
"Range: bytes=" << _first_byte_requested <<
"-\r\n";
3555 switch (_request.get_request_mode()) {
3556 case DocumentSpec::RM_any:
3558 if (_first_byte_requested != 0) {
3562 if (_request.has_tag()) {
3564 <<
"If-Range: " << _request.get_tag().get_string() <<
"\r\n";
3565 }
else if (_request.has_date()) {
3567 <<
"If-Range: " << _request.get_date().get_string() <<
"\r\n";
3572 case DocumentSpec::RM_equal:
3574 if (_request.has_tag()) {
3576 <<
"If-Match: " << _request.get_tag().get_string() <<
"\r\n";
3578 if (_request.has_date()) {
3580 <<
"If-Unmodified-Since: " << _request.get_date().get_string()
3585 case DocumentSpec::RM_newer:
3587 if (_request.has_tag()) {
3589 <<
"If-None-Match: " << _request.get_tag().get_string() <<
"\r\n";
3591 if (_request.has_date()) {
3593 <<
"If-Modified-Since: " << _request.get_date().get_string()
3598 case DocumentSpec::RM_equal_or_newer:
3600 if (_request.has_date()) {
3605 <<
"If-Modified-Since: " << (_request.get_date() - 1).get_string()
3611 switch (_request.get_cache_control()) {
3612 case DocumentSpec::CC_allow_cache:
3616 case DocumentSpec::CC_revalidate:
3619 <<
"Cache-Control: max-age=0\r\n";
3622 case DocumentSpec::CC_no_cache:
3625 <<
"Cache-Control: no-cache\r\n" 3626 <<
"Pragma: no-cache\r\n";
3630 _client->send_cookies(stream, _request.get_url());
3632 if (!_body.empty()) {
3634 <<
"Content-Type: " << _content_type <<
"\r\n" 3635 <<
"Content-Length: " << _body.length() <<
"\r\n";
3638 _header = stream.str();
3648 make_proxy_request_text() {
3649 _proxy_request_text = _proxy_header;
3651 if (_proxy_auth !=
nullptr && !_proxy_username.empty()) {
3652 _proxy_request_text +=
"Proxy-Authorization: ";
3653 _proxy_request_text +=
3654 _proxy_auth->generate(HTTPEnum::M_connect, _request.get_url().get_server_and_port(),
3655 _proxy_username, _body);
3656 _proxy_request_text +=
"\r\n";
3659 _proxy_request_text +=
"\r\n";
3667 make_request_text() {
3668 _request_text = _header;
3670 if (_proxy_serves_document &&
3671 _proxy_auth !=
nullptr && !_proxy_username.empty()) {
3672 _request_text +=
"Proxy-Authorization: ";
3674 _proxy_auth->generate(_method, _request.get_url().get_url(), _proxy_username, _body);
3675 _request_text +=
"\r\n";
3678 if (_www_auth !=
nullptr && !_www_username.empty()) {
3679 string authorization =
3680 _request_text +=
"Authorization: ";
3682 _www_auth->generate(_method, _request.get_url().get_path_and_query(), _www_username, _body);
3683 _request_text +=
"\r\n";
3686 _request_text += _send_extra_headers;
3687 _request_text +=
"\r\n";
3688 _request_text += _body;
3704 if (downloader_cat.is_debug()) {
3705 downloader_cat.debug()
3706 << _NOTIFY_HTTP_CHANNEL_ID
3707 <<
"resetting for new server " 3719 store_header_field(
const string &field_name,
const string &field_value) {
3720 std::pair<Headers::iterator, bool> insert_result =
3721 _headers.insert(Headers::value_type(field_name, field_value));
3723 if (!insert_result.second) {
3726 Headers::iterator hi = insert_result.first;
3727 (*hi).second +=
", ";
3728 (*hi).second += field_value;
3731 if (field_name ==
"set-cookie") {
3732 _client->set_cookie(HTTPCookie(field_value, _request.get_url()));
3741 show_send(
const string &message) {
3743 size_t newline = message.find(
'\n', start);
3744 while (newline != string::npos) {
3746 downloader_cat.spam()
3747 <<
"send: " << message.substr(start, newline - start - 1) <<
"\n";
3748 start = newline + 1;
3749 newline = message.find(
'\n', start);
3752 if (start < message.length()) {
3753 downloader_cat.spam()
3754 <<
"send: " << message.substr(start) <<
" (no newline)\n";
3764 reset_download_to() {
3765 _started_download =
false;
3766 close_download_stream();
3767 _download_dest = DD_none;
3775 close_download_stream() {
3776 if (_download_to_stream !=
nullptr) {
3777 _download_to_stream->flush();
3778 if (_download_dest == DD_file) {
3782 _download_to_ramfile =
nullptr;
3783 _download_to_stream =
nullptr;
3792 if (downloader_cat.is_spam()) {
3793 downloader_cat.spam()
3794 << _NOTIFY_HTTP_CHANNEL_ID
3795 <<
"reset_to_new.\n";
3806 reset_body_stream() {
3807 if (_owns_body_stream) {
3808 if (_body_stream !=
nullptr) {
3809 close_read_body(_body_stream);
3810 nassertv(_body_stream ==
nullptr && !_owns_body_stream);
3813 _body_stream =
nullptr;
3822 close_connection() {
3823 reset_body_stream();
3826 _working_get = string();
3837 more_useful_status_code(
int a,
int b) {
3838 if (a >= 100 && b >= 100) {
3851 int series_a = (a / 100);
3852 int series_b = (b / 100);
3855 return (series_a < series_b);
3858 if (a < 100 && b < 100) {
3866 return (a > SC_http_error_watermark);
3870 return (b < SC_http_error_watermark);
3878 operator << (ostream &out, HTTPChannel::State state) {
3880 return out << (int)state;
3883 case HTTPChannel::S_new:
3884 return out <<
"new";
3886 case HTTPChannel::S_try_next_proxy:
3887 return out <<
"try_next_proxy";
3889 case HTTPChannel::S_connecting:
3890 return out <<
"connecting";
3892 case HTTPChannel::S_connecting_wait:
3893 return out <<
"connecting_wait";
3895 case HTTPChannel::S_http_proxy_ready:
3896 return out <<
"http_proxy_ready";
3898 case HTTPChannel::S_http_proxy_request_sent:
3899 return out <<
"http_proxy_request_sent";
3901 case HTTPChannel::S_http_proxy_reading_header:
3902 return out <<
"http_proxy_reading_header";
3904 case HTTPChannel::S_socks_proxy_greet:
3905 return out <<
"socks_proxy_greet";
3907 case HTTPChannel::S_socks_proxy_greet_reply:
3908 return out <<
"socks_proxy_greet_reply";
3910 case HTTPChannel::S_socks_proxy_connect:
3911 return out <<
"socks_proxy_connect";
3913 case HTTPChannel::S_socks_proxy_connect_reply:
3914 return out <<
"socks_proxy_connect_reply";
3916 case HTTPChannel::S_setup_ssl:
3917 return out <<
"setup_ssl";
3919 case HTTPChannel::S_ssl_handshake:
3920 return out <<
"ssl_handshake";
3922 case HTTPChannel::S_ready:
3923 return out <<
"ready";
3925 case HTTPChannel::S_request_sent:
3926 return out <<
"request_sent";
3928 case HTTPChannel::S_reading_header:
3929 return out <<
"reading_header";
3931 case HTTPChannel::S_start_direct_file_read:
3932 return out <<
"start_direct_file_read";
3934 case HTTPChannel::S_read_header:
3935 return out <<
"read_header";
3937 case HTTPChannel::S_begin_body:
3938 return out <<
"begin_body";
3940 case HTTPChannel::S_reading_body:
3941 return out <<
"reading_body";
3943 case HTTPChannel::S_read_body:
3944 return out <<
"read_body";
3946 case HTTPChannel::S_read_trailer:
3947 return out <<
"read_trailer";
3949 case HTTPChannel::S_failure:
3950 return out <<
"failure";
3953 return out <<
"invalid state(" << (int)state <<
")";
3957 #endif // HAVE_OPENSSL PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static TrueClock * get_global_ptr()
Returns a pointer to the one TrueClock object in the world.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A container for a URL, e.g.
A hierarchy of directories and files that appears to be one continuous file system, even though the files may originate from several different sources that may not be related to the actual OS's file system.
A container for an "entity tag" from an HTTP server.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_binary()
Indicates that the filename represents a binary file.
string downcase(const string &s)
Returns the input string with all uppercase letters converted to lowercase.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is a convenience class to specialize ConfigVariable as a floating- point type.
get_scheme
Returns the scheme specified by the URL, or empty string if no scheme is specified.
The name of a file, such as a texture file or an Egg file.
An in-memory buffer specifically designed for downloading files to memory.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
get_path
Returns the path specified by the URL, or "/" if no path is specified.
A container for an HTTP-legal time/date indication.
get_authority
Returns the authority specified by the URL (this includes username, server, and/or port)...
bool is_local() const
Returns true if the filename is local, e.g.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static void close_write_file(std::ostream *stream)
Closes a file opened by a previous call to open_write_file().
set_username
Replaces the username part of the URL specification.
get_port
Returns the port number specified by the URL, or the default port if not specified.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::ostream * open_write_file(const Filename &filename, bool auto_wrap, bool truncate)
Convenience function; returns a newly allocated ostream if the file exists and can be written...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
const std::string & get_url() const
Returns the complete URL specification.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_server
Returns the server name specified by the URL, if any.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A descriptor that refers to a particular version of a document.
TypeHandle is the identifier used to differentiate C++ class types.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_server_and_port
Returns a string consisting of the server name, followed by a colon, followed by the port number...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This class can be used to test for string matches against standard Unix- shell filename globbing conv...