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