Panda3D
|
00001 // Filename: chunkedStreamBuf.cxx 00002 // Created by: drose (25Sep02) 00003 // 00004 //////////////////////////////////////////////////////////////////// 00005 // 00006 // PANDA 3D SOFTWARE 00007 // Copyright (c) Carnegie Mellon University. All rights reserved. 00008 // 00009 // All use of this software is subject to the terms of the revised BSD 00010 // license. You should have received a copy of this license along 00011 // with this source code in a file named "LICENSE." 00012 // 00013 //////////////////////////////////////////////////////////////////// 00014 00015 #include "chunkedStreamBuf.h" 00016 #include "config_downloader.h" 00017 #include <ctype.h> 00018 00019 // This module is not compiled if OpenSSL is not available. 00020 #ifdef HAVE_OPENSSL 00021 00022 #ifndef HAVE_STREAMSIZE 00023 // Some compilers (notably SGI) don't define this for us 00024 typedef int streamsize; 00025 #endif /* HAVE_STREAMSIZE */ 00026 00027 //////////////////////////////////////////////////////////////////// 00028 // Function: ChunkedStreamBuf::Constructor 00029 // Access: Public 00030 // Description: 00031 //////////////////////////////////////////////////////////////////// 00032 ChunkedStreamBuf:: 00033 ChunkedStreamBuf() { 00034 _chunk_remaining = 0; 00035 _done = true; 00036 _wanted_nonblocking = false; 00037 _read_state = ISocketStream::RS_initial; 00038 00039 #ifdef PHAVE_IOSTREAM 00040 _buffer = (char *)PANDA_MALLOC_ARRAY(4096); 00041 char *ebuf = _buffer + 4096; 00042 setg(_buffer, ebuf, ebuf); 00043 setp(_buffer, ebuf); 00044 00045 #else 00046 allocate(); 00047 setg(base(), ebuf(), ebuf()); 00048 setp(base(), ebuf()); 00049 #endif 00050 } 00051 00052 //////////////////////////////////////////////////////////////////// 00053 // Function: ChunkedStreamBuf::Destructor 00054 // Access: Public, Virtual 00055 // Description: 00056 //////////////////////////////////////////////////////////////////// 00057 ChunkedStreamBuf:: 00058 ~ChunkedStreamBuf() { 00059 close_read(); 00060 #ifdef PHAVE_IOSTREAM 00061 PANDA_FREE_ARRAY(_buffer); 00062 #endif 00063 } 00064 00065 //////////////////////////////////////////////////////////////////// 00066 // Function: ChunkedStreamBuf::open_read 00067 // Access: Public 00068 // Description: If the document pointer is non-NULL, it will be 00069 // updated with the length of the file as it is derived 00070 // from the chunked encoding. 00071 //////////////////////////////////////////////////////////////////// 00072 void ChunkedStreamBuf:: 00073 open_read(BioStreamPtr *source, HTTPChannel *doc) { 00074 _source = source; 00075 nassertv(!_source.is_null()); 00076 _chunk_remaining = 0; 00077 _done = false; 00078 _wanted_nonblocking = doc->_wanted_nonblocking; 00079 _read_state = ISocketStream::RS_reading; 00080 _doc = doc; 00081 00082 if (_doc != (HTTPChannel *)NULL) { 00083 _read_index = doc->_read_index; 00084 _doc->_transfer_file_size = 0; 00085 _doc->_got_transfer_file_size = true; 00086 00087 // Read a little bit from the file to get the first chunk (and 00088 // therefore the file size, or at least the size of the first 00089 // chunk). 00090 underflow(); 00091 } 00092 } 00093 00094 //////////////////////////////////////////////////////////////////// 00095 // Function: ChunkedStreamBuf::close_read 00096 // Access: Public 00097 // Description: 00098 //////////////////////////////////////////////////////////////////// 00099 void ChunkedStreamBuf:: 00100 close_read() { 00101 _source.clear(); 00102 } 00103 00104 //////////////////////////////////////////////////////////////////// 00105 // Function: ChunkedStreamBuf::underflow 00106 // Access: Protected, Virtual 00107 // Description: Called by the system istream implementation when its 00108 // internal buffer needs more characters. 00109 //////////////////////////////////////////////////////////////////// 00110 int ChunkedStreamBuf:: 00111 underflow() { 00112 // Sometimes underflow() is called even if the buffer is not empty. 00113 if (gptr() >= egptr()) { 00114 size_t buffer_size = egptr() - eback(); 00115 gbump(-(int)buffer_size); 00116 00117 size_t num_bytes = buffer_size; 00118 size_t read_count = read_chars(gptr(), buffer_size); 00119 00120 if (read_count != num_bytes) { 00121 // Oops, we didn't read what we thought we would. 00122 if (read_count == 0) { 00123 gbump(num_bytes); 00124 return EOF; 00125 } 00126 00127 // Slide what we did read to the top of the buffer. 00128 nassertr(read_count < num_bytes, EOF); 00129 size_t delta = num_bytes - read_count; 00130 memmove(gptr() + delta, gptr(), read_count); 00131 gbump(delta); 00132 } 00133 } 00134 00135 return (unsigned char)*gptr(); 00136 } 00137 00138 00139 //////////////////////////////////////////////////////////////////// 00140 // Function: ChunkedStreamBuf::read_chars 00141 // Access: Private 00142 // Description: Gets some characters from the source stream. 00143 //////////////////////////////////////////////////////////////////// 00144 size_t ChunkedStreamBuf:: 00145 read_chars(char *start, size_t length) { 00146 while (true) { 00147 nassertr(!_source.is_null(), 0); 00148 if (_done) { 00149 return 0; 00150 } 00151 00152 if (_chunk_remaining != 0) { 00153 // Extract some of the bytes remaining in the chunk. 00154 length = min(length, _chunk_remaining); 00155 (*_source)->read(start, length); 00156 size_t read_count = (*_source)->gcount(); 00157 if (!_wanted_nonblocking) { 00158 while (read_count == 0 && !(*_source)->is_closed()) { 00159 // Simulate blocking. 00160 thread_yield(); 00161 (*_source)->read(start, length); 00162 read_count = (*_source)->gcount(); 00163 } 00164 } 00165 _chunk_remaining -= read_count; 00166 00167 if (read_count == 0 && (*_source)->is_closed()) { 00168 // Whoops, the socket closed while we were downloading. 00169 _read_state = ISocketStream::RS_error; 00170 } 00171 00172 return read_count; 00173 } 00174 00175 // Read the next chunk. 00176 string line; 00177 bool got_line = http_getline(line); 00178 while (got_line && line.empty()) { 00179 // Skip blank lines. There really should be exactly one blank 00180 // line, but who's counting? It's tricky to count and maintain 00181 // reentry for nonblocking I/O. 00182 got_line = http_getline(line); 00183 } 00184 if (!got_line) { 00185 // EOF (or data unavailable) while trying to read the chunk size. 00186 if ((*_source)->is_closed()) { 00187 // Whoops, the socket closed while we were downloading. 00188 _read_state = ISocketStream::RS_error; 00189 } 00190 00191 if (!_wanted_nonblocking) { 00192 // Simulate blocking. 00193 thread_yield(); 00194 continue; // back to the top. 00195 } 00196 00197 return 0; 00198 } 00199 size_t chunk_size = (size_t)strtol(line.c_str(), NULL, 16); 00200 if (downloader_cat.is_spam()) { 00201 downloader_cat.spam() 00202 << "Got chunk of size " << chunk_size << " bytes.\n"; 00203 } 00204 00205 if (chunk_size == 0) { 00206 // Last chunk; we're done. 00207 _done = true; 00208 if (_doc != (HTTPChannel *)NULL && _read_index == _doc->_read_index) { 00209 _doc->_file_size = _doc->_transfer_file_size; 00210 _doc->_got_file_size = true; 00211 } 00212 _read_state = ISocketStream::RS_complete; 00213 return 0; 00214 } 00215 00216 if (_doc != (HTTPChannel *)NULL && _read_index == _doc->_read_index) { 00217 _doc->_transfer_file_size += chunk_size; 00218 } 00219 00220 _chunk_remaining = chunk_size; 00221 00222 // Back to the top. 00223 } 00224 00225 // Never gets here. 00226 return 0; 00227 } 00228 00229 //////////////////////////////////////////////////////////////////// 00230 // Function: ChunkedStreamBuf::http_getline 00231 // Access: Private 00232 // Description: Reads a single line from the stream. Returns 00233 // true if the line is successfully retrieved, or false 00234 // if a complete line has not yet been received or if 00235 // the connection has been closed. 00236 //////////////////////////////////////////////////////////////////// 00237 bool ChunkedStreamBuf:: 00238 http_getline(string &str) { 00239 nassertr(!_source.is_null(), false); 00240 int ch = (*_source)->get(); 00241 while (!(*_source)->eof() && !(*_source)->fail()) { 00242 switch (ch) { 00243 case '\n': 00244 // end-of-line character, we're done. 00245 str = _working_getline; 00246 _working_getline = string(); 00247 { 00248 // Trim trailing whitespace. We're not required to do this per the 00249 // HTTP spec, but let's be generous. 00250 size_t p = str.length(); 00251 while (p > 0 && isspace(str[p - 1])) { 00252 --p; 00253 } 00254 str = str.substr(0, p); 00255 } 00256 00257 return true; 00258 00259 case '\r': 00260 // Ignore CR characters. 00261 break; 00262 00263 default: 00264 _working_getline += (char)ch; 00265 } 00266 ch = (*_source)->get(); 00267 } 00268 00269 return false; 00270 } 00271 00272 #endif // HAVE_OPENSSL