Panda3D
 All Classes Functions Variables Enumerations
chunkedStreamBuf.cxx
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
 All Classes Functions Variables Enumerations