comparison deps/Platinum/Source/Core/PltHttp.cpp @ 0:3425707ddbf6

Initial import (hopefully this mercurial stuff works...)
author fraserofthenight
date Mon, 06 Jul 2009 08:06:28 -0700
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:3425707ddbf6
1 /*****************************************************************
2 |
3 | Platinum - HTTP Protocol Helper
4 |
5 | Copyright (c) 2004-2008, Plutinosoft, LLC.
6 | All rights reserved.
7 | http://www.plutinosoft.com
8 |
9 | This program is free software; you can redistribute it and/or
10 | modify it under the terms of the GNU General Public License
11 | as published by the Free Software Foundation; either version 2
12 | of the License, or (at your option) any later version.
13 |
14 | OEMs, ISVs, VARs and other distributors that combine and
15 | distribute commercially licensed software with Platinum software
16 | and do not wish to distribute the source code for the commercially
17 | licensed software under version 2, or (at your option) any later
18 | version, of the GNU General Public License (the "GPL") must enter
19 | into a commercial license agreement with Plutinosoft, LLC.
20 |
21 | This program is distributed in the hope that it will be useful,
22 | but WITHOUT ANY WARRANTY; without even the implied warranty of
23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 | GNU General Public License for more details.
25 |
26 | You should have received a copy of the GNU General Public License
27 | along with this program; see the file LICENSE.txt. If not, write to
28 | the Free Software Foundation, Inc.,
29 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
30 | http://www.gnu.org/licenses/gpl-2.0.html
31 |
32 ****************************************************************/
33
34 /*----------------------------------------------------------------------
35 | includes
36 +---------------------------------------------------------------------*/
37 #include "PltHttp.h"
38 #include "PltDatagramStream.h"
39 #include "PltVersion.h"
40
41 NPT_SET_LOCAL_LOGGER("platinum.core.http")
42
43 /*----------------------------------------------------------------------
44 | external references
45 +---------------------------------------------------------------------*/
46 extern NPT_String HttpServerHeader;
47
48 /*----------------------------------------------------------------------
49 | NPT_HttpHeaderFinder
50 +---------------------------------------------------------------------*/
51 class NPT_HttpHeaderFinder
52 {
53 public:
54 // methods
55 NPT_HttpHeaderFinder(const char* name) : m_Name(name) {}
56 bool operator()(const NPT_HttpHeader* const & header) const {
57 if (header->GetName().Compare(m_Name, true)) {
58 return true;
59 } else {
60 return false;
61 }
62 }
63
64 private:
65 // members
66 NPT_String m_Name;
67 };
68
69 /*----------------------------------------------------------------------
70 | NPT_HttpHeaderPrinter
71 +---------------------------------------------------------------------*/
72 class NPT_HttpHeaderPrinter
73 {
74 public:
75 // methods
76 NPT_HttpHeaderPrinter(NPT_OutputStreamReference& stream) : m_Stream(stream) {}
77 NPT_Result operator()(NPT_HttpHeader*& header) const {
78 m_Stream->WriteString(header->GetName());
79 m_Stream->Write(": ", 2);
80 m_Stream->WriteString(header->GetValue());
81 m_Stream->Write("\r\n", 2, NULL);
82 return NPT_SUCCESS;
83 }
84
85 private:
86 // members
87 NPT_OutputStreamReference& m_Stream;
88 };
89
90 /*----------------------------------------------------------------------
91 | NPT_HttpHeaderLogger
92 +---------------------------------------------------------------------*/
93 class NPT_HttpHeaderLogger
94 {
95 public:
96 // methods
97 NPT_HttpHeaderLogger(NPT_LoggerReference& logger, int level) :
98 m_Logger(logger), m_Level(level) {}
99 NPT_Result operator()(NPT_HttpHeader*& header) const {
100 NPT_COMPILER_UNUSED(header);
101
102 NPT_LOG_L2(m_Logger, m_Level, "%s: %s",
103 (const char*)header->GetName(),
104 (const char*)header->GetValue());
105 return NPT_SUCCESS;
106 }
107
108 NPT_LoggerReference& m_Logger;
109 int m_Level;
110 };
111
112
113 /*----------------------------------------------------------------------
114 | PLT_HttpHelper::GetContentType
115 +---------------------------------------------------------------------*/
116 NPT_Result
117 PLT_HttpHelper::GetContentType(NPT_HttpMessage& message, NPT_String& type)
118 {
119 type = "";
120
121 const NPT_String* val =
122 message.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_CONTENT_TYPE);
123 NPT_CHECK_POINTER(val);
124
125 type = *val;
126 return NPT_SUCCESS;
127 }
128
129 /*----------------------------------------------------------------------
130 | PLT_HttpHelper::SetContentType
131 +---------------------------------------------------------------------*/
132 void
133 PLT_HttpHelper::SetContentType(NPT_HttpMessage& message, const char* type)
134 {
135 message.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE, type);
136 }
137
138 /*----------------------------------------------------------------------
139 | PLT_HttpHelper::GetContentLength
140 +---------------------------------------------------------------------*/
141 NPT_Result
142 PLT_HttpHelper::GetContentLength(NPT_HttpMessage& message, NPT_LargeSize& len)
143 {
144 len = 0;
145
146 const NPT_String* val =
147 message.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_CONTENT_LENGTH);
148 NPT_CHECK_POINTER(val);
149
150 return val->ToInteger(len);
151 }
152
153 /*----------------------------------------------------------------------
154 | PLT_HttpHelper::SetContentLength
155 +---------------------------------------------------------------------*/
156 void
157 PLT_HttpHelper::SetContentLength(NPT_HttpMessage& message, NPT_LargeSize len)
158 {
159 message.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, NPT_String::FromIntegerU(len));
160 }
161
162 /*----------------------------------------------------------------------
163 | PLT_HttpHelper::SetBody
164 +---------------------------------------------------------------------*/
165 NPT_Result
166 PLT_HttpHelper::SetBody(NPT_HttpMessage& message, NPT_String& body)
167 {
168 return SetBody(message, (const char*)body, body.GetLength());
169 }
170
171 /*----------------------------------------------------------------------
172 | NPT_HttpMessage::SetBody
173 +---------------------------------------------------------------------*/
174 NPT_Result
175 PLT_HttpHelper::SetBody(NPT_HttpMessage& message, const char* body, NPT_Size len)
176 {
177 if (len == 0) {
178 return NPT_SUCCESS;
179 }
180
181 // dump the body in a memory stream
182 NPT_MemoryStreamReference stream(new NPT_MemoryStream);
183 stream->Write(body, len);
184
185 // set content length
186 PLT_HttpHelper::SetContentLength(message, len);
187
188 NPT_InputStreamReference input = stream;
189 return SetBody(message, input, len);
190 }
191
192 /*----------------------------------------------------------------------
193 | NPT_HttpMessage::SetBody
194 +---------------------------------------------------------------------*/
195 NPT_Result
196 PLT_HttpHelper::SetBody(NPT_HttpMessage& message, NPT_InputStreamReference& stream, NPT_LargeSize len)
197 {
198 if (len == 0) {
199 NPT_CHECK_SEVERE(stream->GetAvailable(len));
200 }
201
202 // get the entity
203 NPT_HttpEntity* entity = message.GetEntity();
204 if (entity == NULL) {
205 // no entity yet, create one
206 message.SetEntity(entity = new NPT_HttpEntity());
207 }
208
209 // set the entity body
210 entity->SetInputStream(stream);
211 entity->SetContentLength(len);
212 return NPT_SUCCESS;
213 }
214
215 /*----------------------------------------------------------------------
216 | PLT_HttpHelper::GetBody
217 +---------------------------------------------------------------------*/
218 NPT_Result
219 PLT_HttpHelper::GetBody(NPT_HttpMessage& message, NPT_String& body)
220 {
221 NPT_Result res;
222 NPT_InputStreamReference stream;
223
224 // get stream
225 NPT_HttpEntity* entity = message.GetEntity();
226 if (!entity || NPT_FAILED(entity->GetInputStream(stream))) {
227 return NPT_FAILURE;
228 }
229
230 // extract body
231 NPT_StringOutputStream* output_stream = new NPT_StringOutputStream(&body);
232 res = NPT_StreamToStreamCopy(*stream, *output_stream, 0, entity->GetContentLength());
233 delete output_stream;
234 return res;
235 }
236
237 /*----------------------------------------------------------------------
238 | PLT_HttpHelper::ParseBody
239 +---------------------------------------------------------------------*/
240 NPT_Result
241 PLT_HttpHelper::ParseBody(NPT_HttpMessage& message, NPT_XmlElementNode*& tree)
242 {
243 // reset tree
244 tree = NULL;
245
246 // read body
247 NPT_String body;
248 NPT_CHECK_WARNING(GetBody(message, body));
249
250 // parse body
251 NPT_XmlParser parser;
252 NPT_XmlNode* node;
253 NPT_CHECK_WARNING(parser.Parse(body, node));
254
255 tree = node->AsElementNode();
256 if (!tree) {
257 delete node;
258 return NPT_FAILURE;
259 }
260
261 return NPT_SUCCESS;
262 }
263
264 /*----------------------------------------------------------------------
265 | PLT_HttpHelper::IsConnectionKeepAlive
266 +---------------------------------------------------------------------*/
267 bool
268 PLT_HttpHelper::IsConnectionKeepAlive(NPT_HttpMessage& message)
269 {
270 const NPT_String* connection =
271 message.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_CONNECTION);
272
273 // if we have the keep-alive header then no matter what protocol version, we want keep-alive
274 // if we are in HTTP 1.1 and we don't have the keep-alive header, make sure we also don't have the Connection: close header.
275 NPT_String protocol = message.GetProtocol();
276 if ((!protocol.Compare(NPT_HTTP_PROTOCOL_1_1, true) && (!connection || connection->Compare("close", true))) ||
277 (connection && !connection->Compare("keep-alive", true))) {
278 return true;
279 }
280
281 return false;
282 }
283
284 /*----------------------------------------------------------------------
285 | PLT_HttpHelper::IsBodyStreamSeekable
286 +---------------------------------------------------------------------*/
287 bool
288 PLT_HttpHelper::IsBodyStreamSeekable(NPT_HttpMessage& message)
289 {
290 NPT_HttpEntity* entity = message.GetEntity();
291 NPT_InputStreamReference stream;
292 if (!entity || NPT_FAILED(entity->GetInputStream(stream))) return true;
293
294 // try to get current position and seek there
295 NPT_Position position;
296 if (NPT_FAILED(stream->Tell(position)) ||
297 NPT_FAILED(stream->Seek(position))) {
298 return false;
299 }
300
301 return true;
302 }
303
304 /*----------------------------------------------------------------------
305 | PLT_HttpHelper::GetHost
306 +---------------------------------------------------------------------*/
307 NPT_Result
308 PLT_HttpHelper::GetHost(NPT_HttpRequest& request, NPT_String& value)
309 {
310 value = "";
311
312 const NPT_String* val =
313 request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_HOST);
314 NPT_CHECK_POINTER(val);
315
316 value = *val;
317 return NPT_SUCCESS;
318 }
319
320 /*----------------------------------------------------------------------
321 | PLT_HttpHelper::SetHost
322 +---------------------------------------------------------------------*/
323 void
324 PLT_HttpHelper::SetHost(NPT_HttpRequest& request, const char* host)
325 {
326 request.GetHeaders().SetHeader(NPT_HTTP_HEADER_HOST, host);
327 }
328
329 /*----------------------------------------------------------------------
330 | PLT_HttpHelper::GetRange
331 +---------------------------------------------------------------------*/
332 NPT_Result
333 PLT_HttpHelper::GetRange(NPT_HttpRequest& request,
334 NPT_Position& start,
335 NPT_Position& end)
336 {
337 start = (NPT_Position)-1;
338 end = (NPT_Position)-1;
339
340 const NPT_String* range =
341 request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE);
342 NPT_CHECK_POINTER(range);
343
344 char s[32], e[32];
345 s[0] = '\0';
346 e[0] = '\0';
347 int ret = sscanf(*range, "bytes=%[^-]-%s", s, e);
348 if (ret < 1) {
349 return NPT_FAILURE;
350 }
351 if (s[0] != '\0') {
352 NPT_ParseInteger64U(s, start);
353 }
354 if (e[0] != '\0') {
355 NPT_ParseInteger64U(e, end);
356 }
357
358 return NPT_SUCCESS;
359 }
360
361 /*----------------------------------------------------------------------
362 | PLT_HttpHelper::SetRange
363 +---------------------------------------------------------------------*/
364 void
365 PLT_HttpHelper::SetRange(NPT_HttpRequest& request, NPT_Position start, NPT_Position end)
366 {
367 NPT_String range = "bytes=";
368 if (start != (NPT_Position)-1) {
369 range += NPT_String::FromIntegerU(start);
370 }
371 range += '-';
372 if (end != (NPT_Position)-1) {
373 range += NPT_String::FromIntegerU(end);
374 }
375 request.GetHeaders().SetHeader(NPT_HTTP_HEADER_RANGE, range);
376 }
377
378 /*----------------------------------------------------------------------
379 | PLT_HttpHelper::ToLog
380 +---------------------------------------------------------------------*/
381 NPT_Result
382 PLT_HttpHelper::ToLog(NPT_LoggerReference logger, int level, NPT_HttpRequest* request)
383 {
384 NPT_COMPILER_UNUSED(logger);
385 NPT_COMPILER_UNUSED(level);
386
387 if (!request) {
388 NPT_LOG_L(logger, level, "NULL HTTP Request!");
389 return NPT_FAILURE;
390 }
391
392 NPT_StringOutputStreamReference stream(new NPT_StringOutputStream);
393 NPT_OutputStreamReference output = stream;
394 request->GetHeaders().GetHeaders().Apply(NPT_HttpHeaderPrinter(output));
395
396 NPT_LOG_L4(logger, level, "\n%s %s %s\n%s",
397 (const char*)request->GetMethod(),
398 (const char*)request->GetUrl().ToRequestString(true),
399 (const char*)request->GetProtocol(),
400 (const char*)stream->GetString());
401 return NPT_SUCCESS;
402 }
403
404 /*----------------------------------------------------------------------
405 | PLT_HttpHelper::GetContentRange
406 +---------------------------------------------------------------------*/
407 NPT_Result
408 PLT_HttpHelper::GetContentRange(NPT_HttpResponse& response,
409 NPT_Position& start,
410 NPT_Position& end,
411 NPT_LargeSize& length)
412 {
413 const NPT_String* range =
414 response.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_CONTENT_RANGE);
415 NPT_CHECK_POINTER(range);
416
417 start = (NPT_Position)-1;
418 end = (NPT_Position)-1;
419 length = (NPT_LargeSize)-1;
420
421 char s[32], e[32], l[32];
422 s[0] = '\0';
423 e[0] = '\0';
424 l[0] = '\0';
425 int ret = sscanf(*range, "bytes %[^-]-%s[^/]/%s", s, e, l);
426 if (ret < 3) {
427 return NPT_FAILURE;
428 }
429 if (s[0] != '\0') {
430 NPT_ParseInteger64U(s, start);
431 }
432 if (e[0] != '\0') {
433 NPT_ParseInteger64U(e, end);
434 }
435 if (l[0] != '\0') {
436 NPT_ParseInteger64U(l, length);
437 }
438 return NPT_SUCCESS;
439 }
440
441 /*----------------------------------------------------------------------
442 | PLT_HttpHelper::SetContentRange
443 +---------------------------------------------------------------------*/
444 NPT_Result
445 PLT_HttpHelper::SetContentRange(NPT_HttpResponse& response,
446 NPT_Position start,
447 NPT_Position end,
448 NPT_LargeSize length)
449 {
450 if (start == (NPT_Position)-1 || end == (NPT_Position)-1 || length == (NPT_Size)-1) {
451 NPT_LOG_WARNING_3("Content Range is exactly -1? (start=%d, end=%d, length=%d)", start, end, length);
452 }
453
454 NPT_String range = "bytes ";
455 range += NPT_String::FromIntegerU(start);
456 range += '-';
457 range += NPT_String::FromIntegerU(end);
458 range += '/';
459 range += NPT_String::FromIntegerU(length);
460 response.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, range);
461 return NPT_SUCCESS;
462 }
463
464 /*----------------------------------------------------------------------
465 | NPT_HttpResponse::ToLog
466 +---------------------------------------------------------------------*/
467 NPT_Result
468 PLT_HttpHelper::ToLog(NPT_LoggerReference logger, int level, NPT_HttpResponse* response)
469 {
470 NPT_COMPILER_UNUSED(logger);
471 NPT_COMPILER_UNUSED(level);
472
473 if (!response) {
474 NPT_LOG_L(logger, level, "NULL HTTP Response!");
475 return NPT_FAILURE;
476 }
477
478 NPT_StringOutputStreamReference stream(new NPT_StringOutputStream);
479 NPT_OutputStreamReference output = stream;
480 response->GetHeaders().GetHeaders().Apply(NPT_HttpHeaderPrinter(output));
481
482 NPT_LOG_L4(logger, level, "\n%s %d %s\n%s",
483 (const char*)response->GetProtocol(),
484 response->GetStatusCode(),
485 (const char*)response->GetReasonPhrase(),
486 (const char*)stream->GetString());
487 return NPT_SUCCESS;
488 }
489
490 /*----------------------------------------------------------------------
491 | PLT_HttpClient::Connect
492 +---------------------------------------------------------------------*/
493 NPT_Result
494 PLT_HttpClient::Connect(NPT_Socket* connection, NPT_HttpRequest& request, NPT_Timeout timeout)
495 {
496 // get the address of the server
497 NPT_IpAddress server_address;
498 NPT_CHECK_SEVERE(server_address.ResolveName(request.GetUrl().GetHost(), timeout));
499 NPT_SocketAddress address(server_address, request.GetUrl().GetPort());
500
501 // connect to the server
502 NPT_LOG_FINER_2("Connecting to %s:%d\n", (const char*)request.GetUrl().GetHost(), request.GetUrl().GetPort());
503 NPT_CHECK_SEVERE(connection->Connect(address, timeout));
504
505 return NPT_SUCCESS;
506 }
507
508 /*----------------------------------------------------------------------
509 | PLT_HttpClient::SendRequest
510 +---------------------------------------------------------------------*/
511 NPT_Result
512 PLT_HttpClient::SendRequest(NPT_OutputStreamReference& output_stream,
513 NPT_HttpRequest& request,
514 NPT_Timeout timeout)
515 {
516 NPT_COMPILER_UNUSED(timeout);
517
518 // connect to the server
519 NPT_LOG_FINE("Sending:");
520 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, &request);
521
522 // add any headers that may be missing
523 NPT_HttpHeaders& headers = request.GetHeaders();
524 headers.SetHeader(NPT_HTTP_HEADER_CONNECTION, "close", false); // set but don't replace
525 headers.SetHeader(NPT_HTTP_HEADER_USER_AGENT,
526 NPT_HttpClient::m_UserAgentHeader, false); // set but don't replace
527
528 // set host only if not already set
529 if (!headers.GetHeader(NPT_HTTP_HEADER_HOST)) {
530 NPT_String host = request.GetUrl().GetHost();
531 if (request.GetUrl().GetPort() != NPT_HTTP_DEFAULT_PORT) {
532 host += ":";
533 host += NPT_String::FromInteger(request.GetUrl().GetPort());
534 }
535 headers.SetHeader(NPT_HTTP_HEADER_HOST, host);
536 }
537
538 // get the request entity to set additional headers
539 NPT_InputStreamReference body_stream;
540 NPT_HttpEntity* entity = request.GetEntity();
541 if (entity && NPT_SUCCEEDED(entity->GetInputStream(body_stream))) {
542 // content length
543 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH,
544 NPT_String::FromIntegerU(entity->GetContentLength()));
545
546 // content type
547 NPT_String content_type = entity->GetContentType();
548 if (!content_type.IsEmpty()) {
549 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE, content_type);
550 }
551
552 // content encoding
553 NPT_String content_encoding = entity->GetContentEncoding();
554 if (!content_encoding.IsEmpty()) {
555 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING, content_encoding);
556 }
557 } else {
558 // force content length to 0 is there is no message body
559 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, "0");
560 }
561
562 // create a memory stream to buffer the headers
563 NPT_MemoryStream header_stream;
564
565 // emit the request headers into the header buffer
566 request.Emit(header_stream);
567
568 // send the headers
569 NPT_CHECK_SEVERE(output_stream->WriteFully(header_stream.GetData(), header_stream.GetDataSize()));
570
571 // send request body
572 if (!body_stream.IsNull() && entity->GetContentLength()) {
573 NPT_CHECK_SEVERE(NPT_StreamToStreamCopy(*body_stream.AsPointer(), *output_stream.AsPointer()));
574 }
575
576 // flush the output stream so that everything is sent to the server
577 output_stream->Flush();
578
579 return NPT_SUCCESS;
580 }
581
582 /*----------------------------------------------------------------------
583 | PLT_HttpClient::WaitForResponse
584 +---------------------------------------------------------------------*/
585 NPT_Result
586 PLT_HttpClient::WaitForResponse(NPT_InputStreamReference& input_stream,
587 NPT_HttpRequest& request,
588 const NPT_HttpRequestContext& context,
589 NPT_HttpResponse*& response)
590 {
591 NPT_COMPILER_UNUSED(context);
592
593 // create a buffered stream for this connection stream
594 NPT_BufferedInputStreamReference buffered_input_stream(new NPT_BufferedInputStream(input_stream));
595
596 // parse the response
597 NPT_CHECK(NPT_HttpResponse::Parse(*buffered_input_stream, response));
598
599 // unbuffer the stream
600 buffered_input_stream->SetBufferSize(0);
601
602 // create an entity if one is expected in the response
603 if (request.GetMethod() == NPT_HTTP_METHOD_GET || request.GetMethod() == NPT_HTTP_METHOD_POST) {
604 NPT_HttpEntity* response_entity = new NPT_HttpEntity(response->GetHeaders());
605 // Transfer-Encoding: chunked ?
606 if (response_entity->GetTransferEncoding() == "chunked") {
607 NPT_InputStreamReference body_stream(new NPT_HttpChunkedDecoderInputStream(buffered_input_stream));
608 response_entity->SetInputStream((NPT_InputStreamReference)body_stream);
609 } else {
610 response_entity->SetInputStream((NPT_InputStreamReference)buffered_input_stream);
611 }
612 response->SetEntity(response_entity);
613 }
614
615 return NPT_SUCCESS;
616 }