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