Updated license
[anna.git] / source / http / Transport.cpp
1 // ANNA - Anna is Not Nothingness Anymore
2 //
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
4 //
5 // https://bitbucket.org/testillano/anna
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 //     * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //     * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 //     * Neither the name of Google Inc. nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // Authors: eduardo.ramos.testillano@gmail.com
34 //          cisco.tierra@gmail.com
35
36
37 #include <anna/core/tracing/Logger.hpp>
38 #include <anna/config/defines.hpp>
39 #include <anna/core/functions.hpp>
40
41 #include <anna/http/Transport.hpp>
42 #include <anna/http/internal/Tokenizer.hpp>
43 #include <anna/http/internal/Token.hpp>
44 #include <anna/http/internal/sccs.hpp>
45 #include <anna/http/internal/defines.hpp>
46 #include <anna/http/MessageFactory.hpp>
47 #include <anna/http/Method.hpp>
48 #include <anna/http/internal/EncodedBlock.hpp>
49
50 #include <anna/http/parser/Abstract.hpp>
51
52 using namespace std;
53 using namespace anna;
54
55 comm::TransportFactoryImpl <http::Transport> http::Transport::st_factory;
56
57 http::Transport::Transport() :
58   comm::Transport(),
59   a_parser(NULL),
60   a_inputMessage(NULL),
61   a_result(false),
62   a_haveToClear(false),
63   a_encodedBlock(NULL),
64   a_lastChunkedByte(0),
65   a_fullMessage(NULL) {
66   http::sccs::activate();
67   clear();
68   setOverQuotaSize(8192);
69   activateTimeout();
70 }
71
72 //----------------------------------------------------------------------------------------------
73 // Inicializa el estado de esta instancia. Libera el ultimo mensaje HTTP recibido,
74 // inicializa el estado del Parser y las variables numericas que indican atributos
75 // importantes del mensaje HTTP recibido.
76 //----------------------------------------------------------------------------------------------
77 void http::Transport::clear()
78 throw() {
79   comm::Transport::clear();
80
81   if(a_inputMessage != NULL) {
82     MessageFactory::instantiate().release(a_inputMessage);
83     a_inputMessage = NULL;
84   }
85
86   parser::Abstract::setState(*this, parser::Abstract::ClassType::WaitMessage);
87   a_contentLength = a_bodyOffset = -1;
88   a_lastChunkedByte = 0;
89   a_haveToClear = false;
90
91   if(a_encodedBlock != NULL)
92     a_encodedBlock->clear();
93
94   a_fullMessage = NULL;
95 }
96
97 http::Message* http::Transport::getInputMessage()
98 throw(RuntimeException) {
99   if(a_inputMessage == NULL)
100     throw RuntimeException("getInputMessage | HTTP message was not extracted", ANNA_FILE_LOCATION);
101
102   return a_inputMessage;
103 }
104
105 void http::Transport::setParserState(const parser::Abstract* parser)
106 throw(RuntimeException) {
107   if(a_parser == parser)
108     return;
109
110   a_parser = parser;
111   LOGDEBUG(
112     string msg("http::Transport::setParserState | Transport: ");
113     msg += functions::asHexString(anna_ptrnumber_cast(this));
114     msg += " | ";
115     msg += a_parser->asString();
116     Logger::debug(msg, ANNA_FILE_LOCATION);
117   );
118 }
119
120 http::Message* http::Transport::allocateInputMessage(const http::Message::Type::_v type)
121 throw(RuntimeException) {
122   if(a_inputMessage != NULL)
123     throw RuntimeException("http::Transport::allocateInputMessage | Former HTTP message was not released", ANNA_FILE_LOCATION);
124
125   return a_inputMessage = MessageFactory::instantiate().create(type);
126 }
127
128 const http::Tokenizer& http::Transport::split(const http::Token& token)
129 throw(RuntimeException) {
130   a_lineScope.apply(token);
131   return a_lineScope;
132 }
133
134 const http::Tokenizer& http::Transport::split(const http::Token& token, const char* separator)
135 throw(RuntimeException) {
136   a_lineScope.apply(token, separator);
137   return a_lineScope;
138 }
139
140 const http::Tokenizer& http::Transport::split(const http::Token& token, const char separator)
141 throw(RuntimeException) {
142   a_lineScope.apply(token, separator);
143   return a_lineScope;
144 }
145
146 /* El primer bloque se crea con el tamaño aportado por el exterior
147  * El segundo bloque se crea con el tamaño calculado al analizar el mensaje
148  */
149 const http::Message* http::Transport::externalDecode(const char* buffer, const int size)
150 throw(RuntimeException) {
151   DataBlock dataBlock(buffer, size, false);
152   clear();
153   int httpSize;
154
155   if((httpSize = calculeSize(dataBlock)) == -1) {
156     DataBlock shortDataBlock(buffer, min(128, size), false);
157     string msg("http::Transport::externalDecode | Buffer: ");
158     msg += anna::functions::asString(shortDataBlock);
159     msg += " | Can not calculate length of message";
160     throw RuntimeException(msg, ANNA_FILE_LOCATION);
161   }
162
163   if(httpSize > size) {
164     DataBlock shortDataBlock(buffer, min(128, size), false);
165     string msg("http::Transport::externalDecode | ");
166     msg += functions::asString("Message is incomplete | Size: %d | HTTP-Size: %d | Buffer:", size, httpSize);
167     msg += anna::functions::asString(shortDataBlock);
168     throw RuntimeException(msg, ANNA_FILE_LOCATION);
169   }
170
171   DataBlock fine(buffer, httpSize, false);
172   return static_cast <const http::Message*>(decode(fine));
173 }
174
175 //----------------------------------------------------------------------------------------------
176 // Según vayan pasando por los estados se iran estableciendo la longitud del mensaje, el punto
177 // de inicio, etc ...
178 //
179 // Este metodo se puede invocar N-veces para cada mensaje.
180 //
181 // Analiza independientemente cada linea.
182 //
183 // (1) Si comenzamos un nuevo mensaje tenemos que iniciar el estado de la instancia.
184 // (2) Si estamos analizando un Transfer-Encoding: chunked.
185 //----------------------------------------------------------------------------------------------
186 int http::Transport::calculeSize(const DataBlock& dataBlock)
187 throw(RuntimeException) {
188   Tokenizer::iterator ii;
189   Tokenizer::iterator maxii;
190   int result = -1;
191
192   if(a_haveToClear == true)                                                    // (1)
193     clear();
194
195   // Hay estados del parser que requieren acceso al mensaje completo.
196   a_fullMessage = &dataBlock;
197
198   /* Recordar que el 'dataBlock' contiene TODO el mensaje que llevamos acumulando y eso
199    * es malo para analizar los distintos chunk's que nos van llegando.
200    * Así que lo que hace en caso de Transfer-Encoding: chunked es "saltar" todos los bytes
201    * que hayan sido procesados en alguna de las llamadas anteriores.
202    */
203   if(a_encodedBlock != NULL && a_encodedBlock->isValid() && a_lastChunkedByte > 0) {                  // (2)
204     const int chunkSize = dataBlock.getSize() - a_lastChunkedByte;
205     DataBlock chunk(dataBlock.getData() + a_lastChunkedByte, chunkSize, false);
206     a_fullScope.apply(chunk, endOfLine);
207     LOGDEBUG(
208       string msg("http::Transport::calculeSize | DataBlockSize: ");
209       msg += anna::functions::asString("%d | LastChunkSize: %d | Chunk: ", dataBlock.getSize(), a_lastChunkedByte);
210       msg += functions::asString(chunk, 24);
211       Logger::debug(msg, ANNA_FILE_LOCATION);
212     );
213     a_fullScope.apply(chunk, endOfLine);
214
215     for(ii = a_fullScope.begin(), maxii = a_fullScope.end(); ii != maxii && result == -1; ii ++) {
216       Token& token = *Tokenizer::token(ii);
217       result = a_parser->processLine(*this, chunk, token);
218     }
219   } else {
220     a_fullScope.apply(dataBlock, endOfLine);
221
222     for(ii = a_fullScope.begin(), maxii = a_fullScope.end(); ii != maxii && result == -1; ii ++) {
223       Token& token = *Tokenizer::token(ii);
224       LOGDEBUG(
225         string msg("http::Transport::calculeSize | Line: ");
226         msg += functions::asString(token, 24);
227         Logger::debug(msg, ANNA_FILE_LOCATION);
228       )
229       result = a_parser->processLine(*this, dataBlock, token);
230     }
231   }
232
233   return result;
234 }
235
236 //----------------------------------------------------------------------------------------------
237 // Una vez que tenemos todo el mensaje HTTP completo, solo queda establecer el cuerpo del
238 // mensaje, si es que existe.
239 //
240 // (0) El estado parser::WaitMessage deberia haber instancia algun tipo de mensaje.
241 // (1) Hemos terminado de procesar un mensaje => la proxima vez que llamemos a calculeSize sera
242 // para comenzar con el analisis de un un nuevo mensaje.
243 //----------------------------------------------------------------------------------------------
244 const comm::Message* http::Transport::decode(const DataBlock& dataBlock)
245 throw(RuntimeException) {
246   const bool isEncoded = (a_encodedBlock == NULL) ? false : a_encodedBlock->isValid();
247
248   if(isEncoded == true) {
249     switch(a_encodedBlock->getType()) {
250     case EncodedBlock::Type::Chunked:
251       a_inputMessage->comm::Message::setBody(*a_encodedBlock);
252       break;
253     default:
254       a_inputMessage->clearBody();
255       break;
256     }
257   } else {
258     int maxSize = dataBlock.getSize();
259
260     if(a_bodyOffset >= maxSize) {
261       string msg("anna::http::Transport::decode | Error detected | BodyOffset: ");
262       msg += functions::asString(a_bodyOffset);
263       msg += " | Data: ";
264       msg += functions::asString(dataBlock);
265       throw RuntimeException(msg, ANNA_FILE_LOCATION);
266     }
267
268     if(a_inputMessage == NULL) {                                                  // (0)
269       string msg("anna::http::Transport::decode | HTTP message was not extracted | Data: ");
270       msg += functions::asString(dataBlock);
271       throw RuntimeException(msg, ANNA_FILE_LOCATION);
272     }
273
274     if(a_bodyOffset == -1)
275       a_inputMessage->clearBody();
276     else {
277       int bodySize = maxSize - a_bodyOffset;
278
279       if(bodySize > 0)
280         a_inputMessage->comm::Message::setBody(dataBlock.getData() + a_bodyOffset, bodySize);
281       else
282         a_inputMessage->clearBody();
283     }
284   }
285
286   a_haveToClear = true;                                                         // (1)
287   return a_inputMessage;
288 }
289
290 //-----------------------------------------------------------------------------------------------------
291 // Toda la complejidad de la codificacion la soporta el http::Message.
292 //-----------------------------------------------------------------------------------------------------
293 const DataBlock& http::Transport::code(comm::Message& message)
294 throw(RuntimeException) {
295   return message.code();
296 }
297
298 http::EncodedBlock* http::Transport::getEncodedBlock()
299 throw() {
300   if(a_encodedBlock == NULL)
301     a_encodedBlock = new EncodedBlock;
302
303   return a_encodedBlock;
304 }