]> git.mxchange.org Git - simgear.git/blob - simgear/io/HTTPContentDecode.cxx
Linux needs <cstring>
[simgear.git] / simgear / io / HTTPContentDecode.cxx
1 // Written by James Turner
2 //
3 // Copyright (C) 2013  James Turner  <zakalawe@mac.com>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Library General Public
7 // License as published by the Free Software Foundation; either
8 // version 2 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // Library General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 //
19
20 #include "HTTPContentDecode.hxx"
21
22 #include <cassert>
23 #include <cstdlib> // rand()
24 #include <iostream>
25 #include <cstring> // for memset, memcpy
26
27 #include <simgear/debug/logstream.hxx>
28 #include <simgear/structure/exception.hxx>
29 #include <simgear/io/lowlevel.hxx> // for sgEndian stuff
30
31 namespace simgear
32 {
33
34 namespace HTTP
35 {
36
37     const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
38     const int ZLIB_INFLATE_WINDOW_BITS = -MAX_WBITS;
39   
40     // see http://www.ietf.org/rfc/rfc1952.txt for these values and
41     // detailed description of the logic 
42     const int GZIP_HEADER_ID1 = 31;
43     const int GZIP_HEADER_ID2 = 139;
44     const int GZIP_HEADER_METHOD_DEFLATE = 8;
45     const unsigned int GZIP_HEADER_SIZE = 10;
46     const int GZIP_HEADER_FEXTRA = 1 << 2;
47     const int GZIP_HEADER_FNAME = 1 << 3;
48     const int GZIP_HEADER_COMMENT = 1 << 4;
49     const int GZIP_HEADER_CRC = 1 << 1;
50     
51 ContentDecoder::ContentDecoder() :
52     _output(NULL),
53     _zlib(NULL),
54     _input(NULL),
55     _inputAllocated(0),
56     _inputSize(0)
57 {
58 }
59
60 ContentDecoder::~ContentDecoder()
61 {
62     free(_output);
63     free(_input);
64     free(_zlib);
65 }
66
67 void ContentDecoder::setEncoding(const std::string& encoding)
68 {
69     std::cout << "setEncoding:" << encoding << std::endl;
70     if (encoding == "gzip") {
71       _contentDeflate = true;
72       _needGZipHeader = true;
73     } else if (encoding == "deflate") {
74       _contentDeflate = true;
75       _needGZipHeader = false;
76     } else if (encoding != "identity") {
77       SG_LOG(SG_IO, SG_WARN, "unsupported content encoding:" << encoding);
78     }
79 }
80
81 void ContentDecoder::reset()
82 {
83     _request = NULL;
84     _contentDeflate = false;
85     _needGZipHeader = false;
86     _inputSize = 0;
87 }
88
89 void ContentDecoder::initWithRequest(Request_ptr req)
90 {
91     _request = req;
92     if (!_contentDeflate) {
93         return;
94     }
95
96     if (!_zlib) {
97         _zlib = (z_stream*) malloc(sizeof(z_stream));
98     }
99     
100     memset(_zlib, 0, sizeof(z_stream));
101     if (!_output) {
102          _output = (unsigned char*) malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
103     }
104     
105     _inputSize = 0;
106     // NULLs means we'll get default alloc+free methods
107     // which is absolutely fine
108     _zlib->avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
109     _zlib->next_out = _output;
110     if (inflateInit2(_zlib, ZLIB_INFLATE_WINDOW_BITS) != Z_OK) {
111       SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
112     }
113 }
114
115 void ContentDecoder::finish()
116 {    
117     if (_contentDeflate) {
118         runDecoder();
119       inflateEnd(_zlib);
120     }
121 }
122
123 void ContentDecoder::receivedBytes(const char* n, size_t s)
124 {
125     if (!_contentDeflate) {
126         _request->processBodyBytes(n, s);
127         return;
128     }    
129     
130 // allocate more space if needed (this will only happen rarely once the
131 // buffer has hit something proportionate to the server's compression
132 // window size)
133     size_t requiredSize = _inputSize + s;
134     if (requiredSize > _inputAllocated) {
135         reallocateInputBuffer(requiredSize);
136     }
137     
138 // copy newly recieved bytes into the buffer
139     memcpy(_input + _inputSize, n, s);
140     _inputSize += s;
141     
142     if (_needGZipHeader && !consumeGZipHeader()) {
143         std::cout << "waiting on GZIP header" << std::endl;
144         // still waiting on the full GZIP header, so done
145         return;
146     }
147     
148     runDecoder();
149 }
150
151 void ContentDecoder::consumeBytes(size_t consumed)
152 {    
153     assert(_inputSize >= consumed);
154 // move existing (consumed) bytes down
155     if (consumed > 0) {
156         size_t newSize = _inputSize - consumed;
157         memmove(_input, _input + consumed, newSize);
158         _inputSize = newSize;
159     }
160 }
161
162 void ContentDecoder::reallocateInputBuffer(size_t newSize)
163 {
164     std::cout << "reallocate:" << newSize << std::endl;
165     
166     
167     _input = (unsigned char*) realloc(_input, newSize);
168     _inputAllocated = newSize;
169 }
170
171 void ContentDecoder::runDecoder()
172 {
173     _zlib->next_in = (unsigned char*) _input;
174     _zlib->avail_in = _inputSize;
175     int writtenSize;
176     
177     // loop, running zlib() inflate and sending output bytes to
178     // our request body handler. Keep calling inflate until no bytes are
179     // written, and ZLIB has consumed all available input
180     do {
181         _zlib->next_out = _output;
182         _zlib->avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
183       int result = inflate(_zlib, Z_NO_FLUSH);
184       if (result == Z_OK || result == Z_STREAM_END) {
185           // nothing to do
186       } else if (result == Z_BUF_ERROR) {
187           // transient error, fall through
188       } else {
189         //  _error = result;          
190         return;
191       }
192           
193       writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - _zlib->avail_out;      
194       if (writtenSize > 0) {
195           _request->processBodyBytes((char*) _output, writtenSize);
196       }
197       
198       if (result == Z_STREAM_END) {
199           break;
200       }
201     } while ((_zlib->avail_in > 0) || (writtenSize > 0));
202     
203     // update input buffers based on what we consumed
204     consumeBytes(_inputSize - _zlib->avail_in);
205 }
206
207 bool ContentDecoder::consumeGZipHeader()
208 {    
209     size_t avail = _inputSize;
210     if (avail < GZIP_HEADER_SIZE) {
211       return false; // need more header bytes
212     }
213     
214     if ((_input[0] != GZIP_HEADER_ID1) ||
215         (_input[1] != GZIP_HEADER_ID2) ||
216         (_input[2] != GZIP_HEADER_METHOD_DEFLATE))
217     {
218       return false; // invalid GZip header
219     }
220     
221     char flags = _input[3];
222     unsigned int gzipHeaderSize =  GZIP_HEADER_SIZE;
223     if (flags & GZIP_HEADER_FEXTRA) {
224       gzipHeaderSize += 2;
225       if (avail < gzipHeaderSize) {
226         return false; // need more header bytes
227       }
228       
229       unsigned short extraHeaderBytes = *(reinterpret_cast<unsigned short*>(_input + GZIP_HEADER_FEXTRA));
230       if ( sgIsBigEndian() ) {
231           sgEndianSwap( &extraHeaderBytes );
232       }
233       
234       gzipHeaderSize += extraHeaderBytes;
235       if (avail < gzipHeaderSize) {
236         return false; // need more header bytes
237       }
238     }
239
240 #if 0
241     if (flags & GZIP_HEADER_FNAME) {
242       gzipHeaderSize++;
243       while (gzipHeaderSize <= avail) {
244         if (_input[gzipHeaderSize-1] == 0) {
245           break; // found terminating NULL character
246         }
247       }
248     }
249     
250     if (flags & GZIP_HEADER_COMMENT) {
251       gzipHeaderSize++;
252       while (gzipHeaderSize <= avail) {
253         if (_input[gzipHeaderSize-1] == 0) {
254           break; // found terminating NULL character
255         }
256       }
257     }
258 #endif
259         
260     if (flags & GZIP_HEADER_CRC) {
261       gzipHeaderSize += 2;
262     }
263     
264     if (avail < gzipHeaderSize) {
265       return false; // need more header bytes
266     }
267     
268     consumeBytes(gzipHeaderSize);
269     _needGZipHeader = false;
270     return true;
271 }
272
273 } // of namespace HTTP
274
275 } // of namespace simgear