Panda3D
chunkedStreamBuf.cxx
1 // Filename: chunkedStreamBuf.cxx
2 // Created by: drose (25Sep02)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "chunkedStreamBuf.h"
16 #include "config_downloader.h"
17 #include <ctype.h>
18 
19 // This module is not compiled if OpenSSL is not available.
20 #ifdef HAVE_OPENSSL
21 
22 #ifndef HAVE_STREAMSIZE
23 // Some compilers (notably SGI) don't define this for us
24 typedef int streamsize;
25 #endif /* HAVE_STREAMSIZE */
26 
27 ////////////////////////////////////////////////////////////////////
28 // Function: ChunkedStreamBuf::Constructor
29 // Access: Public
30 // Description:
31 ////////////////////////////////////////////////////////////////////
32 ChunkedStreamBuf::
33 ChunkedStreamBuf() {
34  _chunk_remaining = 0;
35  _done = true;
36  _wanted_nonblocking = false;
37  _read_state = ISocketStream::RS_initial;
38 
39 #ifdef PHAVE_IOSTREAM
40  _buffer = (char *)PANDA_MALLOC_ARRAY(4096);
41  char *ebuf = _buffer + 4096;
42  setg(_buffer, ebuf, ebuf);
43  setp(_buffer, ebuf);
44 
45 #else
46  allocate();
47  setg(base(), ebuf(), ebuf());
48  setp(base(), ebuf());
49 #endif
50 }
51 
52 ////////////////////////////////////////////////////////////////////
53 // Function: ChunkedStreamBuf::Destructor
54 // Access: Public, Virtual
55 // Description:
56 ////////////////////////////////////////////////////////////////////
57 ChunkedStreamBuf::
58 ~ChunkedStreamBuf() {
59  close_read();
60 #ifdef PHAVE_IOSTREAM
61  PANDA_FREE_ARRAY(_buffer);
62 #endif
63 }
64 
65 ////////////////////////////////////////////////////////////////////
66 // Function: ChunkedStreamBuf::open_read
67 // Access: Public
68 // Description: If the document pointer is non-NULL, it will be
69 // updated with the length of the file as it is derived
70 // from the chunked encoding.
71 ////////////////////////////////////////////////////////////////////
72 void ChunkedStreamBuf::
73 open_read(BioStreamPtr *source, HTTPChannel *doc) {
74  _source = source;
75  nassertv(!_source.is_null());
76  _chunk_remaining = 0;
77  _done = false;
78  _wanted_nonblocking = doc->_wanted_nonblocking;
79  _read_state = ISocketStream::RS_reading;
80  _doc = doc;
81 
82  if (_doc != (HTTPChannel *)NULL) {
83  _read_index = doc->_read_index;
84  _doc->_transfer_file_size = 0;
85  _doc->_got_transfer_file_size = true;
86 
87  // Read a little bit from the file to get the first chunk (and
88  // therefore the file size, or at least the size of the first
89  // chunk).
90  underflow();
91  }
92 }
93 
94 ////////////////////////////////////////////////////////////////////
95 // Function: ChunkedStreamBuf::close_read
96 // Access: Public
97 // Description:
98 ////////////////////////////////////////////////////////////////////
99 void ChunkedStreamBuf::
100 close_read() {
101  _source.clear();
102 }
103 
104 ////////////////////////////////////////////////////////////////////
105 // Function: ChunkedStreamBuf::underflow
106 // Access: Protected, Virtual
107 // Description: Called by the system istream implementation when its
108 // internal buffer needs more characters.
109 ////////////////////////////////////////////////////////////////////
110 int ChunkedStreamBuf::
111 underflow() {
112  // Sometimes underflow() is called even if the buffer is not empty.
113  if (gptr() >= egptr()) {
114  size_t buffer_size = egptr() - eback();
115  gbump(-(int)buffer_size);
116 
117  size_t num_bytes = buffer_size;
118  size_t read_count = read_chars(gptr(), buffer_size);
119 
120  if (read_count != num_bytes) {
121  // Oops, we didn't read what we thought we would.
122  if (read_count == 0) {
123  gbump(num_bytes);
124  return EOF;
125  }
126 
127  // Slide what we did read to the top of the buffer.
128  nassertr(read_count < num_bytes, EOF);
129  size_t delta = num_bytes - read_count;
130  memmove(gptr() + delta, gptr(), read_count);
131  gbump(delta);
132  }
133  }
134 
135  return (unsigned char)*gptr();
136 }
137 
138 
139 ////////////////////////////////////////////////////////////////////
140 // Function: ChunkedStreamBuf::read_chars
141 // Access: Private
142 // Description: Gets some characters from the source stream.
143 ////////////////////////////////////////////////////////////////////
144 size_t ChunkedStreamBuf::
145 read_chars(char *start, size_t length) {
146  while (true) {
147  nassertr(!_source.is_null(), 0);
148  if (_done) {
149  return 0;
150  }
151 
152  if (_chunk_remaining != 0) {
153  // Extract some of the bytes remaining in the chunk.
154  length = min(length, _chunk_remaining);
155  (*_source)->read(start, length);
156  size_t read_count = (*_source)->gcount();
157  if (!_wanted_nonblocking) {
158  while (read_count == 0 && !(*_source)->is_closed()) {
159  // Simulate blocking.
160  thread_yield();
161  (*_source)->read(start, length);
162  read_count = (*_source)->gcount();
163  }
164  }
165  _chunk_remaining -= read_count;
166 
167  if (read_count == 0 && (*_source)->is_closed()) {
168  // Whoops, the socket closed while we were downloading.
169  _read_state = ISocketStream::RS_error;
170  }
171 
172  return read_count;
173  }
174 
175  // Read the next chunk.
176  string line;
177  bool got_line = http_getline(line);
178  while (got_line && line.empty()) {
179  // Skip blank lines. There really should be exactly one blank
180  // line, but who's counting? It's tricky to count and maintain
181  // reentry for nonblocking I/O.
182  got_line = http_getline(line);
183  }
184  if (!got_line) {
185  // EOF (or data unavailable) while trying to read the chunk size.
186  if ((*_source)->is_closed()) {
187  // Whoops, the socket closed while we were downloading.
188  _read_state = ISocketStream::RS_error;
189  }
190 
191  if (!_wanted_nonblocking) {
192  // Simulate blocking.
193  thread_yield();
194  continue; // back to the top.
195  }
196 
197  return 0;
198  }
199  size_t chunk_size = (size_t)strtol(line.c_str(), NULL, 16);
200  if (downloader_cat.is_spam()) {
201  downloader_cat.spam()
202  << "Got chunk of size " << chunk_size << " bytes.\n";
203  }
204 
205  if (chunk_size == 0) {
206  // Last chunk; we're done.
207  _done = true;
208  if (_doc != (HTTPChannel *)NULL && _read_index == _doc->_read_index) {
209  _doc->_file_size = _doc->_transfer_file_size;
210  _doc->_got_file_size = true;
211  }
212  _read_state = ISocketStream::RS_complete;
213  return 0;
214  }
215 
216  if (_doc != (HTTPChannel *)NULL && _read_index == _doc->_read_index) {
217  _doc->_transfer_file_size += chunk_size;
218  }
219 
220  _chunk_remaining = chunk_size;
221 
222  // Back to the top.
223  }
224 
225  // Never gets here.
226  return 0;
227 }
228 
229 ////////////////////////////////////////////////////////////////////
230 // Function: ChunkedStreamBuf::http_getline
231 // Access: Private
232 // Description: Reads a single line from the stream. Returns
233 // true if the line is successfully retrieved, or false
234 // if a complete line has not yet been received or if
235 // the connection has been closed.
236 ////////////////////////////////////////////////////////////////////
237 bool ChunkedStreamBuf::
238 http_getline(string &str) {
239  nassertr(!_source.is_null(), false);
240  int ch = (*_source)->get();
241  while (!(*_source)->eof() && !(*_source)->fail()) {
242  switch (ch) {
243  case '\n':
244  // end-of-line character, we're done.
245  str = _working_getline;
246  _working_getline = string();
247  {
248  // Trim trailing whitespace. We're not required to do this per the
249  // HTTP spec, but let's be generous.
250  size_t p = str.length();
251  while (p > 0 && isspace(str[p - 1])) {
252  --p;
253  }
254  str = str.substr(0, p);
255  }
256 
257  return true;
258 
259  case '\r':
260  // Ignore CR characters.
261  break;
262 
263  default:
264  _working_getline += (char)ch;
265  }
266  ch = (*_source)->get();
267  }
268 
269  return false;
270 }
271 
272 #endif // HAVE_OPENSSL