1 /*****************************************************************
2 |
3 | Neptune - HTTP Protocol
4 |
5 | Copyright (c) 2002-2008, Axiomatic Systems, LLC.
6 | All rights reserved.
7 |
8 | Redistribution and use in source and binary forms, with or without
9 | modification, are permitted provided that the following conditions are met:
10 | * Redistributions of source code must retain the above copyright
11 | notice, this list of conditions and the following disclaimer.
12 | * Redistributions in binary form must reproduce the above copyright
13 | notice, this list of conditions and the following disclaimer in the
14 | documentation and/or other materials provided with the distribution.
15 | * Neither the name of Axiomatic Systems nor the
16 | names of its contributors may be used to endorse or promote products
17 | derived from this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
30 ****************************************************************/
31
32 /*----------------------------------------------------------------------
33 | includes
34 +---------------------------------------------------------------------*/
35 #include "NptHttp.h"
36 #include "NptSockets.h"
37 #include "NptBufferedStreams.h"
38 #include "NptDebug.h"
39 #include "NptVersion.h"
40 #include "NptUtils.h"
41 #include "NptFile.h"
42 #include "NptSystem.h"
43 #include "NptLogging.h"
44 #include "NptTls.h"
45 #include "NptStreams.h"
46
47 /*----------------------------------------------------------------------
48 | logging
49 +---------------------------------------------------------------------*/
50 NPT_SET_LOCAL_LOGGER("neptune.http")
51
52 /*----------------------------------------------------------------------
53 | constants
54 +---------------------------------------------------------------------*/
55 const char* const NPT_HTTP_DEFAULT_403_HTML = "<html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>Access to this URL is forbidden.</p></html>";
56 const char* const NPT_HTTP_DEFAULT_404_HTML = "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></html>";
57 const char* const NPT_HTTP_DEFAULT_500_HTML = "<html><head><title>500 Internal Error</title></head><body><h1>Internal Error</h1><p>The server encountered an unexpected condition which prevented it from fulfilling the request.</p></html>";
58
59 /*----------------------------------------------------------------------
60 | NPT_HttpUrl::NPT_HttpUrl
61 +---------------------------------------------------------------------*/
NPT_HttpUrl(const char * url,bool ignore_scheme)62 NPT_HttpUrl::NPT_HttpUrl(const char* url, bool ignore_scheme) :
63 NPT_Url(url)
64 {
65 if (!ignore_scheme) {
66 if (GetSchemeId() != NPT_Uri::SCHEME_ID_HTTP &&
67 GetSchemeId() != NPT_Uri::SCHEME_ID_HTTPS) {
68 Reset();
69 }
70 }
71 }
72
73 /*----------------------------------------------------------------------
74 | NPT_HttpUrl::NPT_HttpUrl
75 +---------------------------------------------------------------------*/
NPT_HttpUrl(const char * host,NPT_UInt16 port,const char * path,const char * query,const char * fragment)76 NPT_HttpUrl::NPT_HttpUrl(const char* host,
77 NPT_UInt16 port,
78 const char* path,
79 const char* query,
80 const char* fragment) :
81 NPT_Url("http", host, port, path, query, fragment)
82 {
83 }
84
85 /*----------------------------------------------------------------------
86 | NPT_HttpUrl::ToString
87 +---------------------------------------------------------------------*/
88 NPT_String
ToString(bool with_fragment) const89 NPT_HttpUrl::ToString(bool with_fragment) const
90 {
91 NPT_UInt16 default_port;
92 switch (m_SchemeId) {
93 case SCHEME_ID_HTTP: default_port = NPT_HTTP_DEFAULT_PORT; break;
94 case SCHEME_ID_HTTPS: default_port = NPT_HTTPS_DEFAULT_PORT; break;
95 default: default_port = 0;
96 }
97 return NPT_Url::ToStringWithDefaultPort(default_port, with_fragment);
98 }
99
100 /*----------------------------------------------------------------------
101 | NPT_HttpHeader::NPT_HttpHeader
102 +---------------------------------------------------------------------*/
NPT_HttpHeader(const char * name,const char * value)103 NPT_HttpHeader::NPT_HttpHeader(const char* name, const char* value):
104 m_Name(name),
105 m_Value(value)
106 {
107 }
108
109 /*----------------------------------------------------------------------
110 | NPT_HttpHeader::~NPT_HttpHeader
111 +---------------------------------------------------------------------*/
~NPT_HttpHeader()112 NPT_HttpHeader::~NPT_HttpHeader()
113 {
114 }
115
116 /*----------------------------------------------------------------------
117 | NPT_HttpHeader::Emit
118 +---------------------------------------------------------------------*/
119 NPT_Result
Emit(NPT_OutputStream & stream) const120 NPT_HttpHeader::Emit(NPT_OutputStream& stream) const
121 {
122 stream.WriteString(m_Name);
123 stream.WriteFully(": ", 2);
124 stream.WriteString(m_Value);
125 stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
126 NPT_LOG_FINEST_2("header %s: %s", m_Name.GetChars(), m_Value.GetChars());
127
128 return NPT_SUCCESS;
129 }
130
131 /*----------------------------------------------------------------------
132 | NPT_HttpHeader::SetName
133 +---------------------------------------------------------------------*/
134 NPT_Result
SetName(const char * name)135 NPT_HttpHeader::SetName(const char* name)
136 {
137 m_Name = name;
138 return NPT_SUCCESS;
139 }
140
141 /*----------------------------------------------------------------------
142 | NPT_HttpHeader::~NPT_HttpHeader
143 +---------------------------------------------------------------------*/
144 NPT_Result
SetValue(const char * value)145 NPT_HttpHeader::SetValue(const char* value)
146 {
147 m_Value = value;
148 return NPT_SUCCESS;
149 }
150
151 /*----------------------------------------------------------------------
152 | NPT_HttpHeaders::NPT_HttpHeaders
153 +---------------------------------------------------------------------*/
NPT_HttpHeaders()154 NPT_HttpHeaders::NPT_HttpHeaders()
155 {
156 }
157
158 /*----------------------------------------------------------------------
159 | NPT_HttpHeaders::~NPT_HttpHeaders
160 +---------------------------------------------------------------------*/
~NPT_HttpHeaders()161 NPT_HttpHeaders::~NPT_HttpHeaders()
162 {
163 m_Headers.Apply(NPT_ObjectDeleter<NPT_HttpHeader>());
164 }
165
166 /*----------------------------------------------------------------------
167 | NPT_HttpHeaders::Parse
168 +---------------------------------------------------------------------*/
169 NPT_Result
Parse(NPT_BufferedInputStream & stream)170 NPT_HttpHeaders::Parse(NPT_BufferedInputStream& stream)
171 {
172 NPT_String header_name;
173 NPT_String header_value;
174 bool header_pending = false;
175 NPT_String line;
176
177 while (NPT_SUCCEEDED(stream.ReadLine(line, NPT_HTTP_PROTOCOL_MAX_LINE_LENGTH))) {
178 if (line.GetLength() == 0) {
179 // empty line, end of headers
180 break;
181 }
182 if (header_pending && (line[0] == ' ' || line[0] == '\t')) {
183 // continuation (folded header)
184 header_value.Append(line.GetChars()+1, line.GetLength()-1);
185 } else {
186 // add the pending header to the list
187 if (header_pending) {
188 header_value.Trim();
189 AddHeader(header_name, header_value);
190 header_pending = false;
191 NPT_LOG_FINEST_2("header - %s: %s",
192 header_name.GetChars(),
193 header_value.GetChars());
194 }
195
196 // find the colon separating the name and the value
197 int colon_index = line.Find(':');
198 if (colon_index < 1) {
199 // invalid syntax, ignore
200 continue;
201 }
202 header_name = line.Left(colon_index);
203
204 // the field value starts at the first non-whitespace
205 const char* value = line.GetChars()+colon_index+1;
206 while (*value == ' ' || *value == '\t') {
207 value++;
208 }
209 header_value = value;
210
211 // the header is pending
212 header_pending = true;
213 }
214 }
215
216 // if we have a header pending, add it now
217 if (header_pending) {
218 header_value.Trim();
219 AddHeader(header_name, header_value);
220 NPT_LOG_FINEST_2("header %s: %s",
221 header_name.GetChars(),
222 header_value.GetChars());
223 }
224
225 return NPT_SUCCESS;
226 }
227
228 /*----------------------------------------------------------------------
229 | NPT_HttpHeaders::Emit
230 +---------------------------------------------------------------------*/
231 NPT_Result
Emit(NPT_OutputStream & stream) const232 NPT_HttpHeaders::Emit(NPT_OutputStream& stream) const
233 {
234 // for each header in the list
235 NPT_List<NPT_HttpHeader*>::Iterator header = m_Headers.GetFirstItem();
236 while (header) {
237 // emit the header
238 NPT_CHECK_WARNING((*header)->Emit(stream));
239 ++header;
240 }
241 return NPT_SUCCESS;
242 }
243
244 /*----------------------------------------------------------------------
245 | NPT_HttpHeaders::GetHeader
246 +---------------------------------------------------------------------*/
247 NPT_HttpHeader*
GetHeader(const char * name) const248 NPT_HttpHeaders::GetHeader(const char* name) const
249 {
250 // check args
251 if (name == NULL) return NULL;
252
253 // find a matching header
254 NPT_List<NPT_HttpHeader*>::Iterator header = m_Headers.GetFirstItem();
255 while (header) {
256 if ((*header)->GetName().Compare(name, true) == 0) {
257 return *header;
258 }
259 ++header;
260 }
261
262 // not found
263 return NULL;
264 }
265
266 /*----------------------------------------------------------------------
267 | NPT_HttpHeaders::AddHeader
268 +---------------------------------------------------------------------*/
269 NPT_Result
AddHeader(const char * name,const char * value)270 NPT_HttpHeaders::AddHeader(const char* name, const char* value)
271 {
272 return m_Headers.Add(new NPT_HttpHeader(name, value));
273 }
274
275 /*----------------------------------------------------------------------
276 | NPT_HttpHeaders::RemoveHeader
277 +---------------------------------------------------------------------*/
278 NPT_Result
RemoveHeader(const char * name)279 NPT_HttpHeaders::RemoveHeader(const char* name)
280 {
281 bool found = false;
282
283 NPT_HttpHeader* header = NULL;
284 while ((header = GetHeader(name))) {
285 m_Headers.Remove(header);
286 delete header;
287 found = true;
288 }
289 return found?NPT_SUCCESS:NPT_ERROR_NO_SUCH_ITEM;
290 }
291
292 /*----------------------------------------------------------------------
293 | NPT_HttpHeaders::SetHeader
294 +---------------------------------------------------------------------*/
295 NPT_Result
SetHeader(const char * name,const char * value,bool replace)296 NPT_HttpHeaders::SetHeader(const char* name, const char* value, bool replace)
297 {
298 NPT_HttpHeader* header = GetHeader(name);
299 if (header == NULL) {
300 return AddHeader(name, value);
301 } else if (replace) {
302 return header->SetValue(value);
303 } else {
304 return NPT_SUCCESS;
305 }
306 }
307
308 /*----------------------------------------------------------------------
309 | NPT_HttpHeaders::GetHeaderValue
310 +---------------------------------------------------------------------*/
311 const NPT_String*
GetHeaderValue(const char * name) const312 NPT_HttpHeaders::GetHeaderValue(const char* name) const
313 {
314 NPT_HttpHeader* header = GetHeader(name);
315 if (header == NULL) {
316 return NULL;
317 } else {
318 return &header->GetValue();
319 }
320 }
321
322 /*----------------------------------------------------------------------
323 | NPT_HttpEntityBodyInputStream
324 +---------------------------------------------------------------------*/
325 class NPT_HttpEntityBodyInputStream : public NPT_InputStream
326 {
327 public:
328 // constructor and desctructor
329 NPT_HttpEntityBodyInputStream(NPT_BufferedInputStreamReference& source,
330 NPT_LargeSize size,
331 bool size_is_known,
332 bool chunked,
333 NPT_HttpClient::Connection* connection,
334 bool should_persist);
335 virtual ~NPT_HttpEntityBodyInputStream();
336
337 // methods
SizeIsKnown()338 bool SizeIsKnown() { return m_SizeIsKnown; }
339
340 // NPT_InputStream methods
341 NPT_Result Read(void* buffer,
342 NPT_Size bytes_to_read,
343 NPT_Size* bytes_read = NULL);
Seek(NPT_Position)344 NPT_Result Seek(NPT_Position /*offset*/) {
345 return NPT_ERROR_NOT_SUPPORTED;
346 }
Tell(NPT_Position & offset)347 NPT_Result Tell(NPT_Position& offset) {
348 offset = m_Position;
349 return NPT_SUCCESS;
350 }
GetSize(NPT_LargeSize & size)351 NPT_Result GetSize(NPT_LargeSize& size) {
352 size = m_Size;
353 return NPT_SUCCESS;
354 }
355 NPT_Result GetAvailable(NPT_LargeSize& available);
356
357 private:
358 // methods
359 virtual void OnFullyRead();
360
361 // members
362 NPT_LargeSize m_Size;
363 bool m_SizeIsKnown;
364 bool m_Chunked;
365 NPT_HttpClient::Connection* m_Connection;
366 bool m_ShouldPersist;
367 NPT_Position m_Position;
368 NPT_InputStreamReference m_Source;
369 };
370
371 /*----------------------------------------------------------------------
372 | NPT_HttpEntityBodyInputStream::NPT_HttpEntityBodyInputStream
373 +---------------------------------------------------------------------*/
NPT_HttpEntityBodyInputStream(NPT_BufferedInputStreamReference & source,NPT_LargeSize size,bool size_is_known,bool chunked,NPT_HttpClient::Connection * connection,bool should_persist)374 NPT_HttpEntityBodyInputStream::NPT_HttpEntityBodyInputStream(
375 NPT_BufferedInputStreamReference& source,
376 NPT_LargeSize size,
377 bool size_is_known,
378 bool chunked,
379 NPT_HttpClient::Connection* connection,
380 bool should_persist) :
381 m_Size(size),
382 m_SizeIsKnown(size_is_known),
383 m_Chunked(chunked),
384 m_Connection(connection),
385 m_ShouldPersist(should_persist),
386 m_Position(0)
387 {
388 if (size_is_known && size == 0) {
389 OnFullyRead();
390 } else {
391 if (chunked) {
392 m_Source = NPT_InputStreamReference(new NPT_HttpChunkedInputStream(source));
393 } else {
394 m_Source = source;
395 }
396 }
397 }
398
399 /*----------------------------------------------------------------------
400 | NPT_HttpEntityBodyInputStream::~NPT_HttpEntityBodyInputStream
401 +---------------------------------------------------------------------*/
~NPT_HttpEntityBodyInputStream()402 NPT_HttpEntityBodyInputStream::~NPT_HttpEntityBodyInputStream()
403 {
404 delete m_Connection;
405 }
406
407 /*----------------------------------------------------------------------
408 | NPT_HttpEntityBodyInputStream::OnFullyRead
409 +---------------------------------------------------------------------*/
410 void
OnFullyRead()411 NPT_HttpEntityBodyInputStream::OnFullyRead()
412 {
413 m_Source = NULL;
414 if (m_Connection && m_ShouldPersist) {
415 m_Connection->Recycle();
416 m_Connection = NULL;
417 }
418 }
419
420 /*----------------------------------------------------------------------
421 | NPT_HttpEntityBodyInputStream::Read
422 +---------------------------------------------------------------------*/
423 NPT_Result
Read(void * buffer,NPT_Size bytes_to_read,NPT_Size * bytes_read)424 NPT_HttpEntityBodyInputStream::Read(void* buffer,
425 NPT_Size bytes_to_read,
426 NPT_Size* bytes_read)
427 {
428 if (bytes_read) *bytes_read = 0;
429
430 // return now if we've already reached the end
431 if (m_Source.IsNull()) return NPT_ERROR_EOS;
432
433 // clamp to the max possible read size
434 if (!m_Chunked && m_SizeIsKnown) {
435 NPT_LargeSize max_can_read = m_Size-m_Position;
436 if (max_can_read == 0) return NPT_ERROR_EOS;
437 if (bytes_to_read > max_can_read) bytes_to_read = (NPT_Size)max_can_read;
438 }
439
440 // read from the source
441 NPT_Size source_bytes_read = 0;
442 NPT_Result result = m_Source->Read(buffer, bytes_to_read, &source_bytes_read);
443 if (NPT_SUCCEEDED(result)) {
444 m_Position += source_bytes_read;
445 if (bytes_read) *bytes_read = source_bytes_read;
446 }
447
448 // check if we've reached the end
449 if (result == NPT_ERROR_EOS || (m_SizeIsKnown && (m_Position == m_Size))) {
450 OnFullyRead();
451 }
452
453 return result;
454 }
455
456 /*----------------------------------------------------------------------
457 | NPT_HttpEntityBodyInputStream::GetAvaialble
458 +---------------------------------------------------------------------*/
459 NPT_Result
GetAvailable(NPT_LargeSize & available)460 NPT_HttpEntityBodyInputStream::GetAvailable(NPT_LargeSize& available)
461 {
462 if (m_Source.IsNull()) {
463 available = 0;
464 return NPT_SUCCESS;
465 }
466 NPT_Result result = m_Source->GetAvailable(available);
467 if (NPT_FAILED(result)) {
468 available = 0;
469 return result;
470 }
471 if (available > m_Size-m_Position) {
472 available = m_Size-m_Position;
473 }
474 return NPT_SUCCESS;
475 }
476
477 /*----------------------------------------------------------------------
478 | NPT_HttpEntity::NPT_HttpEntity
479 +---------------------------------------------------------------------*/
NPT_HttpEntity()480 NPT_HttpEntity::NPT_HttpEntity() :
481 m_ContentLength(0),
482 m_ContentLengthIsKnown(false)
483 {
484 }
485
486 /*----------------------------------------------------------------------
487 | NPT_HttpEntity::NPT_HttpEntity
488 +---------------------------------------------------------------------*/
NPT_HttpEntity(const NPT_HttpHeaders & headers)489 NPT_HttpEntity::NPT_HttpEntity(const NPT_HttpHeaders& headers) :
490 m_ContentLength(0),
491 m_ContentLengthIsKnown(false)
492 {
493 SetHeaders(headers);
494 }
495
496 /*----------------------------------------------------------------------
497 | NPT_HttpEntity::SetHeaders
498 +---------------------------------------------------------------------*/
499 NPT_Result
SetHeaders(const NPT_HttpHeaders & headers)500 NPT_HttpEntity::SetHeaders(const NPT_HttpHeaders& headers)
501 {
502 NPT_HttpHeader* header;
503
504 // Content-Length
505 header = headers.GetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH);
506 if (header != NULL) {
507 m_ContentLengthIsKnown = true;
508 NPT_LargeSize length;
509 if (NPT_SUCCEEDED(header->GetValue().ToInteger64(length))) {
510 m_ContentLength = length;
511 } else {
512 m_ContentLength = 0;
513 }
514 }
515
516 // Content-Type
517 header = headers.GetHeader(NPT_HTTP_HEADER_CONTENT_TYPE);
518 if (header != NULL) {
519 m_ContentType = header->GetValue();
520 }
521
522 // Content-Encoding
523 header = headers.GetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING);
524 if (header != NULL) {
525 m_ContentEncoding = header->GetValue();
526 }
527
528 // Transfer-Encoding
529 header = headers.GetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING);
530 if (header != NULL) {
531 m_TransferEncoding = header->GetValue();
532 }
533
534 return NPT_SUCCESS;
535 }
536
537 /*----------------------------------------------------------------------
538 | NPT_HttpEntity::~NPT_HttpEntity
539 +---------------------------------------------------------------------*/
~NPT_HttpEntity()540 NPT_HttpEntity::~NPT_HttpEntity()
541 {
542 }
543
544 /*----------------------------------------------------------------------
545 | NPT_HttpEntity::GetInputStream
546 +---------------------------------------------------------------------*/
547 NPT_Result
GetInputStream(NPT_InputStreamReference & stream)548 NPT_HttpEntity::GetInputStream(NPT_InputStreamReference& stream)
549 {
550 // reset output params first
551 stream = NULL;
552
553 if (m_InputStream.IsNull()) return NPT_FAILURE;
554
555 stream = m_InputStream;
556 return NPT_SUCCESS;
557 }
558
559 /*----------------------------------------------------------------------
560 | NPT_HttpEntity::SetInputStream
561 +---------------------------------------------------------------------*/
562 NPT_Result
SetInputStream(const NPT_InputStreamReference & stream,bool update_content_length)563 NPT_HttpEntity::SetInputStream(const NPT_InputStreamReference& stream,
564 bool update_content_length /* = false */)
565 {
566 m_InputStream = stream;
567
568 // get the content length from the stream
569 if (update_content_length && !stream.IsNull()) {
570 NPT_LargeSize length;
571 if (NPT_SUCCEEDED(stream->GetSize(length))) {
572 return SetContentLength(length);
573 }
574 }
575
576 return NPT_SUCCESS;
577 }
578
579 /*----------------------------------------------------------------------
580 | NPT_HttpEntity::SetInputStream
581 +---------------------------------------------------------------------*/
582 NPT_Result
SetInputStream(const void * data,NPT_Size data_size)583 NPT_HttpEntity::SetInputStream(const void* data, NPT_Size data_size)
584 {
585 NPT_MemoryStream* memory_stream = new NPT_MemoryStream(data, data_size);
586 NPT_InputStreamReference body(memory_stream);
587 return SetInputStream(body, true);
588 }
589
590 /*----------------------------------------------------------------------
591 | NPT_HttpEntity::SetInputStream
592 +---------------------------------------------------------------------*/
593 NPT_Result
SetInputStream(const char * string)594 NPT_HttpEntity::SetInputStream(const char* string)
595 {
596 if (string == NULL) return NPT_ERROR_INVALID_PARAMETERS;
597 NPT_MemoryStream* memory_stream = new NPT_MemoryStream((const void*)string,
598 NPT_StringLength(string));
599 NPT_InputStreamReference body(memory_stream);
600 return SetInputStream(body, true);
601 }
602
603 /*----------------------------------------------------------------------
604 | NPT_HttpEntity::SetInputStream
605 +---------------------------------------------------------------------*/
606 NPT_Result
SetInputStream(const NPT_String & string)607 NPT_HttpEntity::SetInputStream(const NPT_String& string)
608 {
609 NPT_MemoryStream* memory_stream = new NPT_MemoryStream((const void*)string.GetChars(),
610 string.GetLength());
611 NPT_InputStreamReference body(memory_stream);
612 return SetInputStream(body, true);
613 }
614
615 /*----------------------------------------------------------------------
616 | NPT_HttpEntity::Load
617 +---------------------------------------------------------------------*/
618 NPT_Result
Load(NPT_DataBuffer & buffer)619 NPT_HttpEntity::Load(NPT_DataBuffer& buffer)
620 {
621 // check that we have an input stream
622 if (m_InputStream.IsNull()) return NPT_ERROR_INVALID_STATE;
623
624 // load the stream into the buffer
625 if (m_ContentLength != (NPT_Size)m_ContentLength) return NPT_ERROR_OUT_OF_RANGE;
626 return m_InputStream->Load(buffer, (NPT_Size)m_ContentLength);
627 }
628
629 /*----------------------------------------------------------------------
630 | NPT_HttpEntity::SetContentLength
631 +---------------------------------------------------------------------*/
632 NPT_Result
SetContentLength(NPT_LargeSize length)633 NPT_HttpEntity::SetContentLength(NPT_LargeSize length)
634 {
635 m_ContentLength = length;
636 m_ContentLengthIsKnown = true;
637 return NPT_SUCCESS;
638 }
639
640 /*----------------------------------------------------------------------
641 | NPT_HttpEntity::SetContentType
642 +---------------------------------------------------------------------*/
643 NPT_Result
SetContentType(const char * type)644 NPT_HttpEntity::SetContentType(const char* type)
645 {
646 m_ContentType = type;
647 return NPT_SUCCESS;
648 }
649
650 /*----------------------------------------------------------------------
651 | NPT_HttpEntity::SetContentEncoding
652 +---------------------------------------------------------------------*/
653 NPT_Result
SetContentEncoding(const char * encoding)654 NPT_HttpEntity::SetContentEncoding(const char* encoding)
655 {
656 m_ContentEncoding = encoding;
657 return NPT_SUCCESS;
658 }
659
660 /*----------------------------------------------------------------------
661 | NPT_HttpEntity::SetTransferEncoding
662 +---------------------------------------------------------------------*/
663 NPT_Result
SetTransferEncoding(const char * encoding)664 NPT_HttpEntity::SetTransferEncoding(const char* encoding)
665 {
666 m_TransferEncoding = encoding;
667 return NPT_SUCCESS;
668 }
669
670 /*----------------------------------------------------------------------
671 | NPT_HttpMessage::NPT_HttpMessage
672 +---------------------------------------------------------------------*/
NPT_HttpMessage(const char * protocol)673 NPT_HttpMessage::NPT_HttpMessage(const char* protocol) :
674 m_Protocol(protocol),
675 m_Entity(NULL)
676 {
677 }
678
679 /*----------------------------------------------------------------------
680 | NPT_HttpMessage::NPT_HttpMessage
681 +---------------------------------------------------------------------*/
~NPT_HttpMessage()682 NPT_HttpMessage::~NPT_HttpMessage()
683 {
684 delete m_Entity;
685 }
686
687 /*----------------------------------------------------------------------
688 | NPT_HttpMessage::SetEntity
689 +---------------------------------------------------------------------*/
690 NPT_Result
SetEntity(NPT_HttpEntity * entity)691 NPT_HttpMessage::SetEntity(NPT_HttpEntity* entity)
692 {
693 if (entity != m_Entity) {
694 delete m_Entity;
695 m_Entity = entity;
696 }
697
698 return NPT_SUCCESS;
699 }
700
701 /*----------------------------------------------------------------------
702 | NPT_HttpMessage::ParseHeaders
703 +---------------------------------------------------------------------*/
704 NPT_Result
ParseHeaders(NPT_BufferedInputStream & stream)705 NPT_HttpMessage::ParseHeaders(NPT_BufferedInputStream& stream)
706 {
707 return m_Headers.Parse(stream);
708 }
709
710 /*----------------------------------------------------------------------
711 | NPT_HttpRequest::NPT_HttpRequest
712 +---------------------------------------------------------------------*/
NPT_HttpRequest(const NPT_HttpUrl & url,const char * method,const char * protocol)713 NPT_HttpRequest::NPT_HttpRequest(const NPT_HttpUrl& url,
714 const char* method,
715 const char* protocol) :
716 NPT_HttpMessage(protocol),
717 m_Url(url),
718 m_Method(method)
719 {
720 }
721
722 /*----------------------------------------------------------------------
723 | NPT_HttpRequest::NPT_HttpRequest
724 +---------------------------------------------------------------------*/
NPT_HttpRequest(const char * url,const char * method,const char * protocol)725 NPT_HttpRequest::NPT_HttpRequest(const char* url,
726 const char* method,
727 const char* protocol) :
728 NPT_HttpMessage(protocol),
729 m_Url(url),
730 m_Method(method)
731 {
732 }
733
734 /*----------------------------------------------------------------------
735 | NPT_HttpRequest::SetUrl
736 +---------------------------------------------------------------------*/
737 NPT_Result
SetUrl(const char * url)738 NPT_HttpRequest::SetUrl(const char* url)
739 {
740 m_Url = url;
741 return NPT_SUCCESS;
742 }
743
744 /*----------------------------------------------------------------------
745 | NPT_HttpRequest::SetUrl
746 +---------------------------------------------------------------------*/
747 NPT_Result
SetUrl(const NPT_HttpUrl & url)748 NPT_HttpRequest::SetUrl(const NPT_HttpUrl& url)
749 {
750 m_Url = url;
751 return NPT_SUCCESS;
752 }
753
754 /*----------------------------------------------------------------------
755 | NPT_HttpRequest::Parse
756 +---------------------------------------------------------------------*/
757 NPT_Result
Parse(NPT_BufferedInputStream & stream,const NPT_SocketAddress * endpoint,NPT_HttpRequest * & request)758 NPT_HttpRequest::Parse(NPT_BufferedInputStream& stream,
759 const NPT_SocketAddress* endpoint,
760 NPT_HttpRequest*& request)
761 {
762 // default return value
763 request = NULL;
764
765 skip_first_empty_line:
766 // read the request line
767 NPT_String line;
768 NPT_CHECK_FINER(stream.ReadLine(line, NPT_HTTP_PROTOCOL_MAX_LINE_LENGTH));
769 NPT_LOG_FINEST_1("http request: %s", line.GetChars());
770
771 // cleanup lines that may contain '\0' as first character, clients such
772 // Spotify desktop app send SSDP M-SEARCH requests followed by an extra
773 // '\0' character which stays in the buffered stream and messes up parsing
774 // the next request.
775 while (line.GetLength() > 0 && line[0] == '\0') {
776 line = line.Erase(0, 1);
777 }
778
779 // when using keep-alive connections, clients such as XBox 360
780 // incorrectly send a few empty lines as body for GET requests
781 // so we try to skip them until we find something to parse
782 if (line.GetLength() == 0) goto skip_first_empty_line;
783
784 // check the request line
785 int first_space = line.Find(' ');
786 if (first_space < 0) {
787 NPT_LOG_FINE_1("http request: %s", line.GetChars());
788 return NPT_ERROR_HTTP_INVALID_REQUEST_LINE;
789 }
790 int second_space = line.Find(' ', first_space+1);
791 if (second_space < 0) {
792 NPT_LOG_FINE_1("http request: %s", line.GetChars());
793 return NPT_ERROR_HTTP_INVALID_REQUEST_LINE;
794 }
795
796 // parse the request line
797 NPT_String method = line.SubString(0, first_space);
798 NPT_String uri = line.SubString(first_space+1, second_space-first_space-1);
799 NPT_String protocol = line.SubString(second_space+1);
800
801 // create a request
802 bool proxy_style_request = false;
803 if (uri.StartsWith("http://", true)) {
804 // proxy-style request with absolute URI
805 request = new NPT_HttpRequest(uri, method, protocol);
806 proxy_style_request = true;
807 } else {
808 // normal absolute path request
809 request = new NPT_HttpRequest("http:", method, protocol);
810 }
811
812 // parse headers
813 NPT_Result result = request->ParseHeaders(stream);
814 if (NPT_FAILED(result)) {
815 delete request;
816 request = NULL;
817 return result;
818 }
819
820 // update the URL
821 if (!proxy_style_request) {
822 request->m_Url.SetScheme("http");
823 request->m_Url.ParsePathPlus(uri);
824 request->m_Url.SetPort(NPT_HTTP_DEFAULT_PORT);
825
826 // check for a Host: header
827 NPT_HttpHeader* host_header = request->GetHeaders().GetHeader(NPT_HTTP_HEADER_HOST);
828 if (host_header) {
829 request->m_Url.SetHost(host_header->GetValue());
830
831 // host sometimes doesn't contain port
832 if (endpoint) {
833 request->m_Url.SetPort(endpoint->GetPort());
834 }
835 } else {
836 // use the endpoint as the host
837 if (endpoint) {
838 request->m_Url.SetHost(endpoint->ToString());
839 } else {
840 // use defaults
841 request->m_Url.SetHost("localhost");
842 }
843 }
844 }
845
846 return NPT_SUCCESS;
847 }
848
849 /*----------------------------------------------------------------------
850 | NPT_HttpRequest::~NPT_HttpRequest
851 +---------------------------------------------------------------------*/
~NPT_HttpRequest()852 NPT_HttpRequest::~NPT_HttpRequest()
853 {
854 }
855
856 /*----------------------------------------------------------------------
857 | NPT_HttpRequest::Emit
858 +---------------------------------------------------------------------*/
859 NPT_Result
Emit(NPT_OutputStream & stream,bool use_proxy) const860 NPT_HttpRequest::Emit(NPT_OutputStream& stream, bool use_proxy) const
861 {
862 // write the request line
863 stream.WriteString(m_Method);
864 stream.WriteFully(" ", 1);
865 if (use_proxy) {
866 stream.WriteString(m_Url.ToString(false));
867 } else {
868 stream.WriteString(m_Url.ToRequestString());
869 }
870 stream.WriteFully(" ", 1);
871 stream.WriteString(m_Protocol);
872 stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
873
874 // emit headers
875 m_Headers.Emit(stream);
876
877 // finish with an empty line
878 stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
879
880 return NPT_SUCCESS;
881 }
882
883 /*----------------------------------------------------------------------
884 | NPT_HttpResponse::NPT_HttpResponse
885 +---------------------------------------------------------------------*/
NPT_HttpResponse(NPT_HttpStatusCode status_code,const char * reason_phrase,const char * protocol)886 NPT_HttpResponse::NPT_HttpResponse(NPT_HttpStatusCode status_code,
887 const char* reason_phrase,
888 const char* protocol) :
889 NPT_HttpMessage(protocol),
890 m_StatusCode(status_code),
891 m_ReasonPhrase(reason_phrase)
892 {
893 }
894
895 /*----------------------------------------------------------------------
896 | NPT_HttpResponse::~NPT_HttpResponse
897 +---------------------------------------------------------------------*/
~NPT_HttpResponse()898 NPT_HttpResponse::~NPT_HttpResponse()
899 {
900 }
901
902 /*----------------------------------------------------------------------
903 | NPT_HttpResponse::SetStatus
904 +---------------------------------------------------------------------*/
905 NPT_Result
SetStatus(NPT_HttpStatusCode status_code,const char * reason_phrase,const char * protocol)906 NPT_HttpResponse::SetStatus(NPT_HttpStatusCode status_code,
907 const char* reason_phrase,
908 const char* protocol)
909 {
910 m_StatusCode = status_code;
911 m_ReasonPhrase = reason_phrase;
912 if (protocol) m_Protocol = protocol;
913 return NPT_SUCCESS;
914 }
915
916 /*----------------------------------------------------------------------
917 | NPT_HttpResponse::SetProtocol
918 +---------------------------------------------------------------------*/
919 NPT_Result
SetProtocol(const char * protocol)920 NPT_HttpResponse::SetProtocol(const char* protocol)
921 {
922 m_Protocol = protocol;
923 return NPT_SUCCESS;
924 }
925
926 /*----------------------------------------------------------------------
927 | NPT_HttpResponse::Emit
928 +---------------------------------------------------------------------*/
929 NPT_Result
Emit(NPT_OutputStream & stream) const930 NPT_HttpResponse::Emit(NPT_OutputStream& stream) const
931 {
932 // write the request line
933 stream.WriteString(m_Protocol);
934 stream.WriteFully(" ", 1);
935 stream.WriteString(NPT_String::FromInteger(m_StatusCode));
936 stream.WriteFully(" ", 1);
937 stream.WriteString(m_ReasonPhrase);
938 stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
939
940 // emit headers
941 m_Headers.Emit(stream);
942
943 // finish with an empty line
944 stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
945
946 return NPT_SUCCESS;
947 }
948
949 /*----------------------------------------------------------------------
950 | NPT_HttpResponse::Parse
951 +---------------------------------------------------------------------*/
952 NPT_Result
Parse(NPT_BufferedInputStream & stream,NPT_HttpResponse * & response)953 NPT_HttpResponse::Parse(NPT_BufferedInputStream& stream,
954 NPT_HttpResponse*& response)
955 {
956 // default return value
957 response = NULL;
958
959 // read the response line
960 NPT_String line;
961 NPT_CHECK_WARNING(stream.ReadLine(line, NPT_HTTP_PROTOCOL_MAX_LINE_LENGTH));
962
963 NPT_LOG_FINER_1("http response: %s", line.GetChars());
964
965 // check the response line
966 // we are lenient here, as we allow the response to deviate slightly from
967 // strict HTTP (for example, ICY servers response with a method equal to
968 // ICY insead of HTTP/1.X)
969 int first_space = line.Find(' ');
970 if (first_space < 1) return NPT_ERROR_HTTP_INVALID_RESPONSE_LINE;
971 int second_space = line.Find(' ', first_space+1);
972 if (second_space < 0) {
973 // some servers omit (incorrectly) the space and Reason-Code
974 // but we don't fail them just for that. Just check that the
975 // status code looks ok
976 if (line.GetLength() != 12) {
977 return NPT_ERROR_HTTP_INVALID_RESPONSE_LINE;
978 }
979 } else if (second_space-first_space != 4) {
980 // the status code is not of length 3
981 return NPT_ERROR_HTTP_INVALID_RESPONSE_LINE;
982 }
983
984 // parse the response line
985 NPT_String protocol = line.SubString(0, first_space);
986 NPT_String status_code = line.SubString(first_space+1, 3);
987 NPT_String reason_phrase = line.SubString(first_space+1+3+1,
988 line.GetLength()-(first_space+1+3+1));
989
990 // create a response object
991 NPT_UInt32 status_code_int = 0;
992 status_code.ToInteger(status_code_int);
993 response = new NPT_HttpResponse(status_code_int, reason_phrase, protocol);
994
995 // parse headers
996 NPT_Result result = response->ParseHeaders(stream);
997 if (NPT_FAILED(result)) {
998 delete response;
999 response = NULL;
1000 }
1001
1002 return result;
1003 }
1004
1005 /*----------------------------------------------------------------------
1006 | NPT_HttpEnvProxySelector
1007 +---------------------------------------------------------------------*/
1008 class NPT_HttpEnvProxySelector : public NPT_HttpProxySelector,
1009 public NPT_AutomaticCleaner::Singleton
1010 {
1011 public:
1012 static NPT_HttpEnvProxySelector* GetInstance();
1013
1014 // NPT_HttpProxySelector methods
1015 NPT_Result GetProxyForUrl(const NPT_HttpUrl& url, NPT_HttpProxyAddress& proxy);
1016
1017 private:
1018 // class variables
1019 static NPT_HttpEnvProxySelector* Instance;
1020
1021 // class methods
1022 static void ParseProxyEnv(const NPT_String& env, NPT_HttpProxyAddress& proxy);
1023
1024 // members
1025 NPT_HttpProxyAddress m_HttpProxy;
1026 NPT_HttpProxyAddress m_HttpsProxy;
1027 NPT_List<NPT_String> m_NoProxy;
1028 NPT_HttpProxyAddress m_AllProxy;
1029 };
1030 NPT_HttpEnvProxySelector* NPT_HttpEnvProxySelector::Instance = NULL;
1031
1032 /*----------------------------------------------------------------------
1033 | NPT_HttpEnvProxySelector::GetInstance
1034 +---------------------------------------------------------------------*/
1035 NPT_HttpEnvProxySelector*
GetInstance()1036 NPT_HttpEnvProxySelector::GetInstance()
1037 {
1038 if (Instance) return Instance;
1039
1040 NPT_SingletonLock::GetInstance().Lock();
1041 if (Instance == NULL) {
1042 // create the shared instance
1043 Instance = new NPT_HttpEnvProxySelector();
1044
1045 // prepare for recycling
1046 NPT_AutomaticCleaner::GetInstance()->Register(Instance);
1047
1048 // parse the http proxy settings
1049 NPT_String http_proxy;
1050 NPT_Environment::Get("http_proxy", http_proxy);
1051 ParseProxyEnv(http_proxy, Instance->m_HttpProxy);
1052 NPT_LOG_FINE_2("http_proxy: %s:%d", Instance->m_HttpProxy.GetHostName().GetChars(), Instance->m_HttpProxy.GetPort());
1053
1054 // parse the https proxy settings
1055 NPT_String https_proxy;
1056 if (NPT_FAILED(NPT_Environment::Get("HTTPS_PROXY", https_proxy))) {
1057 NPT_Environment::Get("https_proxy", https_proxy);
1058 }
1059 ParseProxyEnv(https_proxy, Instance->m_HttpsProxy);
1060 NPT_LOG_FINE_2("https_proxy: %s:%d", Instance->m_HttpsProxy.GetHostName().GetChars(), Instance->m_HttpsProxy.GetPort());
1061
1062 // parse the all-proxy settings
1063 NPT_String all_proxy;
1064 if (NPT_FAILED(NPT_Environment::Get("ALL_PROXY", all_proxy))) {
1065 NPT_Environment::Get("all_proxy", all_proxy);
1066 }
1067 ParseProxyEnv(all_proxy, Instance->m_AllProxy);
1068 NPT_LOG_FINE_2("all_proxy: %s:%d", Instance->m_AllProxy.GetHostName().GetChars(), Instance->m_AllProxy.GetPort());
1069
1070 // parse the no-proxy settings
1071 NPT_String no_proxy;
1072 if (NPT_FAILED(NPT_Environment::Get("NO_PROXY", no_proxy))) {
1073 NPT_Environment::Get("no_proxy", no_proxy);
1074 }
1075 if (no_proxy.GetLength()) {
1076 Instance->m_NoProxy = no_proxy.Split(",");
1077 }
1078 }
1079 NPT_SingletonLock::GetInstance().Unlock();
1080
1081 return Instance;
1082 }
1083
1084 /*----------------------------------------------------------------------
1085 | NPT_HttpEnvProxySelector::ParseProxyEnv
1086 +---------------------------------------------------------------------*/
1087 void
ParseProxyEnv(const NPT_String & env,NPT_HttpProxyAddress & proxy)1088 NPT_HttpEnvProxySelector::ParseProxyEnv(const NPT_String& env,
1089 NPT_HttpProxyAddress& proxy)
1090 {
1091 // ignore empty strings
1092 if (env.GetLength() == 0) return;
1093
1094 NPT_String proxy_spec;
1095 if (env.Find("://") >= 0) {
1096 proxy_spec = env;
1097 } else {
1098 proxy_spec = "http://"+env;
1099 }
1100 NPT_Url url(proxy_spec);
1101 proxy.SetHostName(url.GetHost());
1102 proxy.SetPort(url.GetPort());
1103 }
1104
1105 /*----------------------------------------------------------------------
1106 | NPT_HttpEnvProxySelector::GetProxyForUrl
1107 +---------------------------------------------------------------------*/
1108 NPT_Result
GetProxyForUrl(const NPT_HttpUrl & url,NPT_HttpProxyAddress & proxy)1109 NPT_HttpEnvProxySelector::GetProxyForUrl(const NPT_HttpUrl& url,
1110 NPT_HttpProxyAddress& proxy)
1111 {
1112 NPT_HttpProxyAddress* protocol_proxy = NULL;
1113 switch (url.GetSchemeId()) {
1114 case NPT_Uri::SCHEME_ID_HTTP:
1115 protocol_proxy = &m_HttpProxy;
1116 break;
1117
1118 case NPT_Uri::SCHEME_ID_HTTPS:
1119 protocol_proxy = &m_HttpsProxy;
1120 break;
1121
1122 default:
1123 return NPT_ERROR_HTTP_NO_PROXY;
1124 }
1125
1126 // check for no-proxy first
1127 if (m_NoProxy.GetItemCount()) {
1128 for (NPT_List<NPT_String>::Iterator i = m_NoProxy.GetFirstItem();
1129 i;
1130 ++i) {
1131 if ((*i) == "*") {
1132 return NPT_ERROR_HTTP_NO_PROXY;
1133 }
1134 if (url.GetHost().EndsWith(*i, true)) {
1135 if (url.GetHost().GetLength() == (*i).GetLength()) {
1136 // exact match
1137 return NPT_ERROR_HTTP_NO_PROXY;
1138 }
1139 if (url.GetHost().GetChars()[url.GetHost().GetLength()-(*i).GetLength()-1] == '.') {
1140 // subdomain match
1141 return NPT_ERROR_HTTP_NO_PROXY;
1142 }
1143 }
1144 }
1145 }
1146
1147 // check the protocol proxy
1148 if (protocol_proxy->GetHostName().GetLength()) {
1149 proxy = *protocol_proxy;
1150 return NPT_SUCCESS;
1151 }
1152
1153 // use the default proxy
1154 proxy = m_AllProxy;
1155
1156 return proxy.GetHostName().GetLength()?NPT_SUCCESS:NPT_ERROR_HTTP_NO_PROXY;
1157 }
1158
1159 /*----------------------------------------------------------------------
1160 | NPT_HttpProxySelector::GetDefault
1161 +---------------------------------------------------------------------*/
1162 static bool NPT_HttpProxySelector_ConfigChecked = false;
1163 static unsigned int NPT_HttpProxySelector_Config = 0;
1164 const unsigned int NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE = 0;
1165 const unsigned int NPT_HTTP_PROXY_SELECTOR_CONFIG_ENV = 1;
1166 const unsigned int NPT_HTTP_PROXY_SELECTOR_CONFIG_SYSTEM = 2;
1167 NPT_HttpProxySelector*
GetDefault()1168 NPT_HttpProxySelector::GetDefault()
1169 {
1170 if (!NPT_HttpProxySelector_ConfigChecked) {
1171 NPT_String config;
1172 if (NPT_SUCCEEDED(NPT_Environment::Get("NEPTUNE_NET_CONFIG_PROXY_SELECTOR", config))) {
1173 if (config.Compare("noproxy", true) == 0) {
1174 NPT_HttpProxySelector_Config = NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE;
1175 } else if (config.Compare("env", true) == 0) {
1176 NPT_HttpProxySelector_Config = NPT_HTTP_PROXY_SELECTOR_CONFIG_ENV;
1177 } else if (config.Compare("system", true) == 0) {
1178 NPT_HttpProxySelector_Config = NPT_HTTP_PROXY_SELECTOR_CONFIG_SYSTEM;
1179 } else {
1180 NPT_HttpProxySelector_Config = NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE;
1181 }
1182 }
1183 NPT_HttpProxySelector_ConfigChecked = true;
1184 }
1185
1186 switch (NPT_HttpProxySelector_Config) {
1187 case NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE:
1188 // no proxy
1189 return NULL;
1190
1191 case NPT_HTTP_PROXY_SELECTOR_CONFIG_ENV:
1192 // use the shared instance
1193 return NPT_HttpEnvProxySelector::GetInstance();
1194
1195 case NPT_HTTP_PROXY_SELECTOR_CONFIG_SYSTEM:
1196 // use the sytem proxy selector
1197 return GetSystemSelector();
1198
1199 default:
1200 return NULL;
1201 }
1202 }
1203
1204 /*----------------------------------------------------------------------
1205 | NPT_HttpProxySelector::GetSystemSelector
1206 +---------------------------------------------------------------------*/
1207 #if !defined(NPT_CONFIG_HAVE_SYSTEM_PROXY_SELECTOR)
1208 NPT_HttpProxySelector*
GetSystemSelector()1209 NPT_HttpProxySelector::GetSystemSelector()
1210 {
1211 return NULL;
1212 }
1213 #endif
1214
1215 /*----------------------------------------------------------------------
1216 | NPT_HttpStaticProxySelector
1217 +---------------------------------------------------------------------*/
1218 class NPT_HttpStaticProxySelector : public NPT_HttpProxySelector
1219 {
1220 public:
1221 // constructor
1222 NPT_HttpStaticProxySelector(const char* http_propxy_hostname,
1223 NPT_UInt16 http_proxy_port,
1224 const char* https_proxy_hostname,
1225 NPT_UInt16 htts_proxy_port);
1226
1227 // NPT_HttpProxySelector methods
1228 NPT_Result GetProxyForUrl(const NPT_HttpUrl& url, NPT_HttpProxyAddress& proxy);
1229
1230 private:
1231 // members
1232 NPT_HttpProxyAddress m_HttpProxy;
1233 NPT_HttpProxyAddress m_HttpsProxy;
1234 };
1235
1236 /*----------------------------------------------------------------------
1237 | NPT_HttpStaticProxySelector::NPT_HttpStaticProxySelector
1238 +---------------------------------------------------------------------*/
NPT_HttpStaticProxySelector(const char * http_proxy_hostname,NPT_UInt16 http_proxy_port,const char * https_proxy_hostname,NPT_UInt16 https_proxy_port)1239 NPT_HttpStaticProxySelector::NPT_HttpStaticProxySelector(const char* http_proxy_hostname,
1240 NPT_UInt16 http_proxy_port,
1241 const char* https_proxy_hostname,
1242 NPT_UInt16 https_proxy_port) :
1243 m_HttpProxy( http_proxy_hostname, http_proxy_port),
1244 m_HttpsProxy(https_proxy_hostname, https_proxy_port)
1245 {
1246 }
1247
1248 /*----------------------------------------------------------------------
1249 | NPT_HttpStaticProxySelector::GetProxyForUrl
1250 +---------------------------------------------------------------------*/
1251 NPT_Result
GetProxyForUrl(const NPT_HttpUrl & url,NPT_HttpProxyAddress & proxy)1252 NPT_HttpStaticProxySelector::GetProxyForUrl(const NPT_HttpUrl& url,
1253 NPT_HttpProxyAddress& proxy)
1254 {
1255 switch (url.GetSchemeId()) {
1256 case NPT_Uri::SCHEME_ID_HTTP:
1257 proxy = m_HttpProxy;
1258 break;
1259
1260 case NPT_Uri::SCHEME_ID_HTTPS:
1261 proxy = m_HttpsProxy;
1262 break;
1263
1264 default:
1265 return NPT_ERROR_HTTP_NO_PROXY;
1266 }
1267
1268 return NPT_SUCCESS;
1269 }
1270
1271 /*----------------------------------------------------------------------
1272 | NPT_HttpConnectionManager::NPT_HttpConnectionManager
1273 +---------------------------------------------------------------------*/
NPT_HttpConnectionManager()1274 NPT_HttpConnectionManager::NPT_HttpConnectionManager() :
1275 m_Lock(true),
1276 m_MaxConnections(NPT_HTTP_CONNECTION_MANAGER_MAX_CONNECTION_POOL_SIZE),
1277 m_MaxConnectionAge(NPT_HTTP_CONNECTION_MANAGER_MAX_CONNECTION_AGE)
1278 {
1279 }
1280
1281 /*----------------------------------------------------------------------
1282 | NPT_HttpConnectionManager::~NPT_HttpConnectionManager
1283 +---------------------------------------------------------------------*/
~NPT_HttpConnectionManager()1284 NPT_HttpConnectionManager::~NPT_HttpConnectionManager()
1285 {
1286 // set abort flag and wait for thread to finish
1287 m_Aborted.SetValue(1);
1288 Wait();
1289
1290 m_Connections.Apply(NPT_ObjectDeleter<Connection>());
1291 }
1292
1293 /*----------------------------------------------------------------------
1294 | NPT_HttpConnectionManager::GetInstance
1295 +---------------------------------------------------------------------*/
1296 NPT_HttpConnectionManager*
GetInstance()1297 NPT_HttpConnectionManager::GetInstance()
1298 {
1299 if (Instance) return Instance;
1300
1301 NPT_SingletonLock::GetInstance().Lock();
1302 if (Instance == NULL) {
1303 // create the shared instance
1304 Instance = new NPT_HttpConnectionManager();
1305
1306 // register to for automatic cleanup
1307 NPT_AutomaticCleaner::GetInstance()->RegisterHttpConnectionManager(Instance);
1308
1309 // Start shared instance
1310 Instance->Start();
1311 }
1312 NPT_SingletonLock::GetInstance().Unlock();
1313
1314 return Instance;
1315 }
1316 NPT_HttpConnectionManager* NPT_HttpConnectionManager::Instance = NULL;
1317
1318 /*----------------------------------------------------------------------
1319 | NPT_HttpConnectionManager::Run
1320 +---------------------------------------------------------------------*/
1321 void
Run()1322 NPT_HttpConnectionManager::Run()
1323 {
1324 // try to cleanup every 5 secs
1325 while (m_Aborted.WaitUntilEquals(1, 5000) == NPT_ERROR_TIMEOUT) {
1326 NPT_AutoLock lock(m_Lock);
1327 Cleanup();
1328 }
1329 }
1330
1331 /*----------------------------------------------------------------------
1332 | NPT_HttpConnectionManager::Cleanup
1333 +---------------------------------------------------------------------*/
1334 NPT_Result
Cleanup()1335 NPT_HttpConnectionManager::Cleanup()
1336 {
1337 NPT_TimeStamp now;
1338 NPT_System::GetCurrentTimeStamp(now);
1339 NPT_TimeStamp delta((float)m_MaxConnectionAge);
1340
1341 NPT_List<Connection*>::Iterator tail = m_Connections.GetLastItem();
1342 while (tail) {
1343 if (now < (*tail)->m_TimeStamp + delta) break;
1344 NPT_LOG_FINE_1("cleaning up connection (%d remain)", m_Connections.GetItemCount());
1345 delete *tail;
1346 m_Connections.Erase(tail);
1347 tail = m_Connections.GetLastItem();
1348 }
1349 return NPT_SUCCESS;
1350 }
1351
1352 /*----------------------------------------------------------------------
1353 | NPT_HttpConnectionManager::FindConnection
1354 +---------------------------------------------------------------------*/
1355 NPT_HttpConnectionManager::Connection*
FindConnection(NPT_SocketAddress & address)1356 NPT_HttpConnectionManager::FindConnection(NPT_SocketAddress& address)
1357 {
1358 NPT_AutoLock lock(m_Lock);
1359 Cleanup();
1360
1361 for (NPT_List<Connection*>::Iterator i = m_Connections.GetFirstItem();
1362 i;
1363 ++i) {
1364 Connection* connection = *i;
1365
1366 NPT_SocketInfo info;
1367 if (NPT_FAILED(connection->GetInfo(info))) continue;
1368
1369 if (info.remote_address == address) {
1370 m_Connections.Erase(i);
1371 return connection;
1372 }
1373 }
1374
1375 // not found
1376 return NULL;
1377 }
1378
1379 /*----------------------------------------------------------------------
1380 | NPT_HttpConnectionManager::Track
1381 +---------------------------------------------------------------------*/
1382 NPT_Result
Track(NPT_HttpClient * client,NPT_HttpClient::Connection * connection)1383 NPT_HttpConnectionManager::Track(NPT_HttpClient* client, NPT_HttpClient::Connection* connection)
1384 {
1385 NPT_AutoLock lock(m_Lock);
1386
1387 // look if already tracking client connections
1388 ConnectionList* connections = NULL;
1389 if (NPT_SUCCEEDED(m_ClientConnections.Get(client, connections))) {
1390 // return immediately if connection is already associated with client
1391 if (connections->Find(NPT_ObjectComparator<NPT_HttpClient::Connection*>(connection))) {
1392 NPT_LOG_WARNING("Connection already associated to client.");
1393 return NPT_SUCCESS;
1394 }
1395 connections->Add(connection);
1396 return NPT_SUCCESS;
1397 }
1398
1399 // new client connections
1400 ConnectionList new_connections;
1401
1402 // add connection to new client connection list
1403 new_connections.Add(connection);
1404
1405 // track new client connections
1406 m_ClientConnections.Put(client, new_connections);
1407 return NPT_SUCCESS;
1408 }
1409
1410 /*----------------------------------------------------------------------
1411 | NPT_HttpConnectionManager::UntrackConnection
1412 +---------------------------------------------------------------------*/
1413 NPT_Result
UntrackConnection(NPT_HttpClient::Connection * connection)1414 NPT_HttpConnectionManager::UntrackConnection(NPT_HttpClient::Connection* connection)
1415 {
1416 NPT_AutoLock lock(m_Lock);
1417
1418 if (!connection) {
1419 return NPT_ERROR_INVALID_PARAMETERS;
1420 }
1421
1422 // look for connection by enumerating all client connections
1423 NPT_List<NPT_Map<NPT_HttpClient*, ConnectionList>::Entry*>::Iterator entry =
1424 m_ClientConnections.GetEntries().GetFirstItem();
1425 while (entry) {
1426 NPT_HttpClient*& client = (NPT_HttpClient*&)(*entry)->GetKey();
1427 ConnectionList& connections = (ConnectionList&)(*entry)->GetValue();
1428
1429 // look for connection in client connection list
1430 NPT_List<NPT_HttpClient::Connection*>::Iterator i =
1431 connections.Find(NPT_ObjectComparator<NPT_HttpClient::Connection*>(connection));
1432 if (i) {
1433 // remove it
1434 connections.Erase(i);
1435
1436 // untrack client if no more active connections for it
1437 if (connections.GetItemCount() == 0) {
1438 m_ClientConnections.Erase(client);
1439 }
1440
1441 return NPT_SUCCESS;
1442 }
1443 ++entry;
1444 }
1445
1446 return NPT_ERROR_NO_SUCH_ITEM;
1447 }
1448
1449 /*----------------------------------------------------------------------
1450 | NPT_HttpConnectionManager::Untrack
1451 +---------------------------------------------------------------------*/
1452 NPT_Result
Untrack(NPT_HttpClient::Connection * connection)1453 NPT_HttpConnectionManager::Untrack(NPT_HttpClient::Connection* connection)
1454 {
1455 // check first if ConnectionCanceller Instance has not been released already
1456 // with static finalizers
1457 if (Instance == NULL) return NPT_FAILURE;
1458
1459 return GetInstance()->UntrackConnection(connection);
1460 }
1461
1462 /*----------------------------------------------------------------------
1463 | NPT_HttpConnectionManager::Recycle
1464 +---------------------------------------------------------------------*/
1465 NPT_Result
Recycle(NPT_HttpConnectionManager::Connection * connection)1466 NPT_HttpConnectionManager::Recycle(NPT_HttpConnectionManager::Connection* connection)
1467 {
1468 // Untrack connection
1469 UntrackConnection(connection);
1470
1471 {
1472 NPT_AutoLock lock(m_Lock);
1473 Cleanup();
1474
1475 // remove older connections to make room
1476 while (m_Connections.GetItemCount() >= m_MaxConnections) {
1477 NPT_List<Connection*>::Iterator head = m_Connections.GetFirstItem();
1478 if (!head) break;
1479 delete *head;
1480 m_Connections.Erase(head);
1481 NPT_LOG_FINER("removing connection from pool to make some room");
1482 }
1483
1484 if (connection) {
1485
1486 // label this connection with the current timestamp and flag
1487 NPT_System::GetCurrentTimeStamp(connection->m_TimeStamp);
1488 connection->m_IsRecycled = true;
1489
1490 // add the connection to the pool
1491 m_Connections.Add(connection);
1492 }
1493 }
1494
1495 return NPT_SUCCESS;
1496 }
1497
1498 /*----------------------------------------------------------------------
1499 | NPT_HttpConnectionManager::AbortConnections
1500 +---------------------------------------------------------------------*/
1501 NPT_Result
AbortConnections(NPT_HttpClient * client)1502 NPT_HttpConnectionManager::AbortConnections(NPT_HttpClient* client)
1503 {
1504 NPT_AutoLock lock(m_Lock);
1505
1506 ConnectionList* connections = NULL;
1507 if (NPT_SUCCEEDED(m_ClientConnections.Get(client, connections))) {
1508 for (NPT_List<NPT_HttpClient::Connection*>::Iterator i = connections->GetFirstItem();
1509 i;
1510 ++i) {
1511 (*i)->Abort();
1512 }
1513 }
1514 return NPT_SUCCESS;
1515 }
1516
1517 /*----------------------------------------------------------------------
1518 | NPT_HttpConnectionManager::Connection::Connection
1519 +---------------------------------------------------------------------*/
Connection(NPT_HttpConnectionManager & manager,NPT_SocketReference & socket,NPT_InputStreamReference input_stream,NPT_OutputStreamReference output_stream)1520 NPT_HttpConnectionManager::Connection::Connection(NPT_HttpConnectionManager& manager,
1521 NPT_SocketReference& socket,
1522 NPT_InputStreamReference input_stream,
1523 NPT_OutputStreamReference output_stream) :
1524 m_Manager(manager),
1525 m_IsRecycled(false),
1526 m_Socket(socket),
1527 m_InputStream(input_stream),
1528 m_OutputStream(output_stream)
1529 {
1530 }
1531
1532 /*----------------------------------------------------------------------
1533 | NPT_HttpConnectionManager::Connection::~Connection
1534 +---------------------------------------------------------------------*/
~Connection()1535 NPT_HttpConnectionManager::Connection::~Connection()
1536 {
1537 NPT_HttpConnectionManager::Untrack(this);
1538 }
1539
1540 /*----------------------------------------------------------------------
1541 | NPT_HttpConnectionManager::Connection::Recycle
1542 +---------------------------------------------------------------------*/
1543 NPT_Result
Recycle()1544 NPT_HttpConnectionManager::Connection::Recycle()
1545 {
1546 return m_Manager.Recycle(this);
1547 }
1548
1549 /*----------------------------------------------------------------------
1550 | NPT_HttpClient::NPT_HttpClient
1551 +---------------------------------------------------------------------*/
NPT_HttpClient(Connector * connector,bool transfer_ownership)1552 NPT_HttpClient::NPT_HttpClient(Connector* connector, bool transfer_ownership) :
1553 m_ProxySelector(NPT_HttpProxySelector::GetDefault()),
1554 m_ProxySelectorIsOwned(false),
1555 m_Connector(connector),
1556 m_ConnectorIsOwned(transfer_ownership),
1557 m_Aborted(false)
1558 {
1559 if (connector == NULL) {
1560 m_Connector = new NPT_HttpTlsConnector();
1561 m_ConnectorIsOwned = true;
1562 }
1563 }
1564
1565 /*----------------------------------------------------------------------
1566 | NPT_HttpClient::~NPT_HttpClient
1567 +---------------------------------------------------------------------*/
~NPT_HttpClient()1568 NPT_HttpClient::~NPT_HttpClient()
1569 {
1570 if (m_ProxySelectorIsOwned) {
1571 delete m_ProxySelector;
1572 }
1573 if (m_ConnectorIsOwned) {
1574 delete m_Connector;
1575 }
1576 }
1577
1578 /*----------------------------------------------------------------------
1579 | NPT_HttpClient::SetConfig
1580 +---------------------------------------------------------------------*/
1581 NPT_Result
SetConfig(const Config & config)1582 NPT_HttpClient::SetConfig(const Config& config)
1583 {
1584 m_Config = config;
1585
1586 return NPT_SUCCESS;
1587 }
1588
1589 /*----------------------------------------------------------------------
1590 | NPT_HttpClient::SetProxy
1591 +---------------------------------------------------------------------*/
1592 NPT_Result
SetProxy(const char * http_proxy_hostname,NPT_UInt16 http_proxy_port,const char * https_proxy_hostname,NPT_UInt16 https_proxy_port)1593 NPT_HttpClient::SetProxy(const char* http_proxy_hostname,
1594 NPT_UInt16 http_proxy_port,
1595 const char* https_proxy_hostname,
1596 NPT_UInt16 https_proxy_port)
1597 {
1598 if (m_ProxySelectorIsOwned) {
1599 delete m_ProxySelector;
1600 m_ProxySelector = NULL;
1601 m_ProxySelectorIsOwned = false;
1602 }
1603
1604 // use a static proxy to hold on to the settings
1605 m_ProxySelector = new NPT_HttpStaticProxySelector(http_proxy_hostname,
1606 http_proxy_port,
1607 https_proxy_hostname,
1608 https_proxy_port);
1609 m_ProxySelectorIsOwned = true;
1610
1611 return NPT_SUCCESS;
1612 }
1613
1614 /*----------------------------------------------------------------------
1615 | NPT_HttpClient::SetProxySelector
1616 +---------------------------------------------------------------------*/
1617 NPT_Result
SetProxySelector(NPT_HttpProxySelector * selector)1618 NPT_HttpClient::SetProxySelector(NPT_HttpProxySelector* selector)
1619 {
1620 if (m_ProxySelectorIsOwned && m_ProxySelector != selector) {
1621 delete m_ProxySelector;
1622 }
1623 m_ProxySelector = selector;
1624 m_ProxySelectorIsOwned = false;
1625
1626 return NPT_SUCCESS;
1627 }
1628
1629 /*----------------------------------------------------------------------
1630 | NPT_HttpClient::SetConnector
1631 +---------------------------------------------------------------------*/
1632 NPT_Result
SetConnector(Connector * connector)1633 NPT_HttpClient::SetConnector(Connector* connector)
1634 {
1635 if (m_ConnectorIsOwned && m_Connector != connector) {
1636 delete m_Connector;
1637 }
1638 m_Connector = connector;
1639 m_ConnectorIsOwned = false;
1640
1641 return NPT_SUCCESS;
1642 }
1643
1644 /*----------------------------------------------------------------------
1645 | NPT_HttpClient::SetTimeouts
1646 +---------------------------------------------------------------------*/
1647 NPT_Result
SetTimeouts(NPT_Timeout connection_timeout,NPT_Timeout io_timeout,NPT_Timeout name_resolver_timeout)1648 NPT_HttpClient::SetTimeouts(NPT_Timeout connection_timeout,
1649 NPT_Timeout io_timeout,
1650 NPT_Timeout name_resolver_timeout)
1651 {
1652 m_Config.m_ConnectionTimeout = connection_timeout;
1653 m_Config.m_IoTimeout = io_timeout;
1654 m_Config.m_NameResolverTimeout = name_resolver_timeout;
1655
1656 return NPT_SUCCESS;
1657 }
1658
1659 /*----------------------------------------------------------------------
1660 | NPT_HttpClient::SetUserAgent
1661 +---------------------------------------------------------------------*/
1662 NPT_Result
SetUserAgent(const char * user_agent)1663 NPT_HttpClient::SetUserAgent(const char* user_agent)
1664 {
1665 m_Config.m_UserAgent = user_agent;
1666 return NPT_SUCCESS;
1667 }
1668
1669 /*----------------------------------------------------------------------
1670 | NPT_HttpClient::TrackConnection
1671 +---------------------------------------------------------------------*/
1672 NPT_Result
TrackConnection(Connection * connection)1673 NPT_HttpClient::TrackConnection(Connection* connection)
1674 {
1675 NPT_AutoLock lock(m_AbortLock);
1676 if (m_Aborted) return NPT_ERROR_CANCELLED;
1677 return NPT_HttpConnectionManager::GetInstance()->Track(this, connection);
1678 }
1679
1680 /*----------------------------------------------------------------------
1681 | NPT_HttpClient::SendRequestOnce
1682 +---------------------------------------------------------------------*/
1683 NPT_Result
SendRequestOnce(NPT_HttpRequest & request,NPT_HttpResponse * & response,NPT_HttpRequestContext * context)1684 NPT_HttpClient::SendRequestOnce(NPT_HttpRequest& request,
1685 NPT_HttpResponse*& response,
1686 NPT_HttpRequestContext* context /* = NULL */)
1687 {
1688 // setup default values
1689 NPT_Result result = NPT_SUCCESS;
1690 response = NULL;
1691
1692 NPT_LOG_FINE_1("requesting URL %s", request.GetUrl().ToString().GetChars());
1693
1694 // get the address and port to which we need to connect
1695 NPT_HttpProxyAddress proxy;
1696 bool use_proxy = false;
1697 if (m_ProxySelector) {
1698 // we have a proxy selector, ask it to select a proxy for this URL
1699 result = m_ProxySelector->GetProxyForUrl(request.GetUrl(), proxy);
1700 if (NPT_FAILED(result) && result != NPT_ERROR_HTTP_NO_PROXY) {
1701 NPT_LOG_WARNING_1("proxy selector failure (%d)", result);
1702 return result;
1703 }
1704 use_proxy = !proxy.GetHostName().IsEmpty();
1705 }
1706
1707 // connect to the server or proxy
1708 Connection* connection = NULL;
1709 bool http_1_1 = (request.GetProtocol() == NPT_HTTP_PROTOCOL_1_1);
1710 NPT_Reference<Connection> cref;
1711
1712 // send the request to the server (in a loop, since we may need to reconnect with 1.1)
1713 bool reconnect = false;
1714 unsigned int watchdog = NPT_HTTP_MAX_RECONNECTS;
1715 do {
1716 cref = NULL;
1717 connection = NULL;
1718 NPT_LOG_FINE_3("calling connector (proxy:%s) (http 1.1:%s) (url:%s)",
1719 use_proxy?"yes":"no", http_1_1?"yes":"no", request.GetUrl().ToStringWithDefaultPort(0).GetChars());
1720 NPT_CHECK_WARNING(m_Connector->Connect(request.GetUrl(),
1721 *this,
1722 use_proxy?&proxy:NULL,
1723 http_1_1,
1724 connection));
1725 NPT_LOG_FINE_1("got connection (reused: %s)", connection->IsRecycled()?"true":"false");
1726
1727 NPT_InputStreamReference input_stream = connection->GetInputStream();
1728 NPT_OutputStreamReference output_stream = connection->GetOutputStream();
1729
1730 cref = connection;
1731 reconnect = connection->IsRecycled();
1732
1733 // update context if any
1734 if (context) {
1735 NPT_SocketInfo info;
1736 cref->GetInfo(info);
1737 context->SetLocalAddress(info.local_address);
1738 context->SetRemoteAddress(info.remote_address);
1739 }
1740
1741 NPT_HttpEntity* entity = request.GetEntity();
1742 NPT_InputStreamReference body_stream;
1743
1744 if (reconnect && entity && NPT_SUCCEEDED(entity->GetInputStream(body_stream)) && NPT_FAILED(body_stream->Seek(0))) {
1745 // if body is not seekable, we can't afford to reuse a connection
1746 // that could fail, so we reconnect a new one instead
1747 NPT_LOG_FINE("rewinding body stream would fail ... create new connection");
1748 continue;
1749 }
1750
1751 // decide if this connection should persist
1752 NPT_HttpHeaders& headers = request.GetHeaders();
1753 bool should_persist = http_1_1;
1754 if (!connection->SupportsPersistence()) {
1755 should_persist = false;
1756 }
1757 if (should_persist) {
1758 const NPT_String* connection_header = headers.GetHeaderValue(NPT_HTTP_HEADER_CONNECTION);
1759 if (connection_header && (*connection_header == "close")) {
1760 should_persist = false;
1761 }
1762 }
1763
1764 if (m_Config.m_UserAgent.GetLength()) {
1765 headers.SetHeader(NPT_HTTP_HEADER_USER_AGENT, m_Config.m_UserAgent, false); // set but don't replace
1766 }
1767
1768 result = WriteRequest(*output_stream.AsPointer(), request, should_persist, use_proxy);
1769 if (NPT_FAILED(result)) {
1770 NPT_LOG_FINE_1("failed to write request headers (%d)", result);
1771 if (reconnect && !m_Aborted) {
1772 if (!body_stream.IsNull()) {
1773 // go back to the start of the body so that we can resend
1774 NPT_LOG_FINE("rewinding body stream in order to resend");
1775 result = body_stream->Seek(0);
1776 if (NPT_FAILED(result)) {
1777 NPT_CHECK_FINE(NPT_ERROR_HTTP_CANNOT_RESEND_BODY);
1778 }
1779 }
1780 continue;
1781 } else {
1782 return result;
1783 }
1784 }
1785
1786 result = ReadResponse(input_stream,
1787 should_persist,
1788 request.GetMethod() != NPT_HTTP_METHOD_HEAD,
1789 response,
1790 &cref);
1791 if (NPT_FAILED(result)) {
1792 NPT_LOG_FINE_1("failed to parse the response (%d)", result);
1793 if (reconnect && !m_Aborted /*&&
1794 (result == NPT_ERROR_EOS ||
1795 result == NPT_ERROR_CONNECTION_ABORTED ||
1796 result == NPT_ERROR_CONNECTION_RESET ||
1797 result == NPT_ERROR_READ_FAILED) GBG: don't look for specific error codes */) {
1798 NPT_LOG_FINE("error is not fatal, retrying");
1799 if (!body_stream.IsNull()) {
1800 // go back to the start of the body so that we can resend
1801 NPT_LOG_FINE("rewinding body stream in order to resend");
1802 result = body_stream->Seek(0);
1803 if (NPT_FAILED(result)) {
1804 NPT_CHECK_FINE(NPT_ERROR_HTTP_CANNOT_RESEND_BODY);
1805 }
1806 }
1807 continue;
1808 } else {
1809 // don't retry
1810 return result;
1811 }
1812 }
1813 break;
1814 } while (reconnect && --watchdog && !m_Aborted);
1815
1816 // check that we have a valid connection
1817 if (NPT_FAILED(result) && !m_Aborted) {
1818 NPT_LOG_FINE("failed after max reconnection attempts");
1819 return NPT_ERROR_HTTP_TOO_MANY_RECONNECTS;
1820 }
1821
1822 return result;
1823 }
1824
1825 /*----------------------------------------------------------------------
1826 | NPT_HttpClient::WriteRequest
1827 +---------------------------------------------------------------------*/
1828 NPT_Result
WriteRequest(NPT_OutputStream & output_stream,NPT_HttpRequest & request,bool should_persist,bool use_proxy)1829 NPT_HttpClient::WriteRequest(NPT_OutputStream& output_stream,
1830 NPT_HttpRequest& request,
1831 bool should_persist,
1832 bool use_proxy /* = false */)
1833 {
1834 NPT_Result result = NPT_SUCCESS;
1835
1836 // add any headers that may be missing
1837 NPT_HttpHeaders& headers = request.GetHeaders();
1838
1839 if (!should_persist) {
1840 headers.SetHeader(NPT_HTTP_HEADER_CONNECTION, "close", false); // set but don't replace
1841 }
1842
1843 NPT_String host = request.GetUrl().GetHost();
1844 NPT_UInt16 default_port = 0;
1845 switch (request.GetUrl().GetSchemeId()) {
1846 case NPT_Uri::SCHEME_ID_HTTP: default_port = NPT_HTTP_DEFAULT_PORT; break;
1847 case NPT_Uri::SCHEME_ID_HTTPS: default_port = NPT_HTTPS_DEFAULT_PORT; break;
1848 default: break;
1849 }
1850 if (request.GetUrl().GetPort() != default_port) {
1851 host += ":";
1852 host += NPT_String::FromInteger(request.GetUrl().GetPort());
1853 }
1854 headers.SetHeader(NPT_HTTP_HEADER_HOST, host, false); // set but don't replace
1855
1856 // get the request entity to set additional headers
1857 NPT_InputStreamReference body_stream;
1858 NPT_HttpEntity* entity = request.GetEntity();
1859 if (entity && NPT_SUCCEEDED(entity->GetInputStream(body_stream))) {
1860 // set the content length if known
1861 if (entity->ContentLengthIsKnown()) {
1862 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH,
1863 NPT_String::FromInteger(entity->GetContentLength()));
1864 }
1865
1866 // content type
1867 NPT_String content_type = entity->GetContentType();
1868 if (!content_type.IsEmpty()) {
1869 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE, content_type);
1870 }
1871
1872 // content encoding
1873 NPT_String content_encoding = entity->GetContentEncoding();
1874 if (!content_encoding.IsEmpty()) {
1875 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING, content_encoding);
1876 }
1877
1878 // transfer encoding
1879 const NPT_String& transfer_encoding = entity->GetTransferEncoding();
1880 if (!transfer_encoding.IsEmpty()) {
1881 headers.SetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING, transfer_encoding);
1882 }
1883 }
1884
1885 // create a memory stream to buffer the headers
1886 NPT_MemoryStream header_stream;
1887
1888 // emit the request headers into the header buffer
1889 request.Emit(header_stream, use_proxy && request.GetUrl().GetSchemeId()==NPT_Url::SCHEME_ID_HTTP);
1890
1891 // send the headers
1892 NPT_CHECK_WARNING(output_stream.WriteFully(header_stream.GetData(), header_stream.GetDataSize()));
1893
1894 // send request body
1895 if (entity && !body_stream.IsNull()) {
1896 // check for chunked transfer encoding
1897 NPT_OutputStream* dest = &output_stream;
1898 if (entity->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED) {
1899 dest = new NPT_HttpChunkedOutputStream(output_stream);
1900 }
1901
1902 NPT_LOG_FINE_1("sending body stream, %lld bytes", entity->GetContentLength()); //FIXME: Would be 0 for chunked encoding
1903 NPT_LargeSize bytes_written = 0;
1904
1905 // content length = 0 means copy until input returns EOS
1906 result = NPT_StreamToStreamCopy(*body_stream.AsPointer(), *dest, 0, entity->GetContentLength(), &bytes_written);
1907 if (NPT_FAILED(result)) {
1908 NPT_LOG_FINE_3("body stream only partially sent, %lld bytes (%d:%s)",
1909 bytes_written,
1910 result,
1911 NPT_ResultText(result));
1912 }
1913
1914 // flush to write out any buffered data left in chunked output if used
1915 dest->Flush();
1916
1917 // cleanup (this will send zero size chunk followed by CRLF)
1918 if (dest != &output_stream) delete dest;
1919 }
1920
1921 // flush the output stream so that everything is sent to the server
1922 output_stream.Flush();
1923
1924 return result;
1925 }
1926
1927 /*----------------------------------------------------------------------
1928 | NPT_HttpClient::ReadResponse
1929 +---------------------------------------------------------------------*/
1930 NPT_Result
ReadResponse(NPT_InputStreamReference & input_stream,bool should_persist,bool expect_entity,NPT_HttpResponse * & response,NPT_Reference<Connection> * cref)1931 NPT_HttpClient::ReadResponse(NPT_InputStreamReference& input_stream,
1932 bool should_persist,
1933 bool expect_entity,
1934 NPT_HttpResponse*& response,
1935 NPT_Reference<Connection>* cref /* = NULL */)
1936 {
1937 NPT_Result result;
1938
1939 // setup default values
1940 response = NULL;
1941
1942 // create a buffered stream for this socket stream
1943 NPT_BufferedInputStreamReference buffered_input_stream(new NPT_BufferedInputStream(input_stream));
1944
1945 // parse the response
1946 for (unsigned int watchcat = 0; watchcat < NPT_HTTP_MAX_100_RESPONSES; watchcat++) {
1947 // parse the response
1948 result = NPT_HttpResponse::Parse(*buffered_input_stream, response);
1949 NPT_CHECK_FINE(result);
1950
1951 if (response->GetStatusCode() >= 100 && response->GetStatusCode() < 200) {
1952 NPT_LOG_FINE_1("got %d response, continuing", response->GetStatusCode());
1953 delete response;
1954 response = NULL;
1955 continue;
1956 }
1957 NPT_LOG_FINER_2("got response, code=%d, msg=%s",
1958 response->GetStatusCode(),
1959 response->GetReasonPhrase().GetChars());
1960 break;
1961 }
1962
1963 // check that we have a valid response
1964 if (response == NULL) {
1965 NPT_LOG_FINE("failed after max continuation attempts");
1966 return NPT_ERROR_HTTP_TOO_MANY_RECONNECTS;
1967 }
1968
1969 // unbuffer the stream
1970 buffered_input_stream->SetBufferSize(0);
1971
1972 // decide if we should still try to reuse this connection later on
1973 if (should_persist) {
1974 const NPT_String* connection_header = response->GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_CONNECTION);
1975 if (response->GetProtocol() == NPT_HTTP_PROTOCOL_1_1) {
1976 if (connection_header && (*connection_header == "close")) {
1977 should_persist = false;
1978 }
1979 } else {
1980 if (!connection_header || (*connection_header != "keep-alive")) {
1981 should_persist = false;
1982 }
1983 }
1984 }
1985
1986 // create an entity if one is expected in the response
1987 if (expect_entity) {
1988 NPT_HttpEntity* response_entity = new NPT_HttpEntity(response->GetHeaders());
1989
1990 // check if the content length is known
1991 bool have_content_length = (response->GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_CONTENT_LENGTH) != NULL);
1992
1993 // check for chunked Transfer-Encoding
1994 bool chunked = false;
1995 if (response_entity->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED) {
1996 chunked = true;
1997 response_entity->SetTransferEncoding(NULL);
1998 }
1999
2000 // prepare to transfer ownership of the connection if needed
2001 Connection* connection = NULL;
2002 if (cref) {
2003 connection = cref->AsPointer();
2004 cref->Detach(); // release the internal ref
2005 // don't delete connection now so we can abort while readin response body,
2006 // just pass ownership to NPT_HttpEntityBodyInputStream so it can recycle it
2007 // when done if connection should persist
2008 }
2009
2010 // create the body stream wrapper
2011 NPT_InputStream* response_body_stream =
2012 new NPT_HttpEntityBodyInputStream(buffered_input_stream,
2013 response_entity->GetContentLength(),
2014 have_content_length,
2015 chunked,
2016 connection,
2017 should_persist);
2018 response_entity->SetInputStream(NPT_InputStreamReference(response_body_stream));
2019 response->SetEntity(response_entity);
2020 } else {
2021 if (should_persist && cref) {
2022 Connection* connection = cref->AsPointer();
2023 cref->Detach(); // release the internal ref
2024 connection->Recycle();
2025 }
2026 }
2027
2028 return NPT_SUCCESS;
2029 }
2030
2031 /*----------------------------------------------------------------------
2032 | NPT_HttpClient::SendRequest
2033 +---------------------------------------------------------------------*/
2034 NPT_Result
SendRequest(NPT_HttpRequest & request,NPT_HttpResponse * & response,NPT_HttpRequestContext * context)2035 NPT_HttpClient::SendRequest(NPT_HttpRequest& request,
2036 NPT_HttpResponse*& response,
2037 NPT_HttpRequestContext* context /* = NULL */)
2038 {
2039 NPT_Cardinal watchdog = m_Config.m_MaxRedirects+1;
2040 bool keep_going;
2041 NPT_Result result;
2042
2043 // reset aborted flag
2044 m_Aborted = false;
2045
2046 // default value
2047 response = NULL;
2048
2049 // check that for GET requests there is no entity
2050 if (request.GetEntity() != NULL &&
2051 request.GetMethod() == NPT_HTTP_METHOD_GET) {
2052 return NPT_ERROR_HTTP_INVALID_REQUEST;
2053 }
2054
2055 do {
2056 keep_going = false;
2057 result = SendRequestOnce(request, response, context);
2058 if (NPT_FAILED(result)) break;
2059 if (response && m_Config.m_MaxRedirects &&
2060 (request.GetMethod() == NPT_HTTP_METHOD_GET ||
2061 request.GetMethod() == NPT_HTTP_METHOD_HEAD) &&
2062 (response->GetStatusCode() == 301 ||
2063 response->GetStatusCode() == 302 ||
2064 response->GetStatusCode() == 303 ||
2065 response->GetStatusCode() == 307)) {
2066 // handle redirect
2067 const NPT_String* location = response->GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_LOCATION);
2068 if (location) {
2069 // check for location fields that are not absolute URLs
2070 // (this is not allowed by the standard, but many web servers do it
2071 if (location->StartsWith("/") ||
2072 (!location->StartsWith("http://", true) &&
2073 !location->StartsWith("https://", true))) {
2074 NPT_LOG_FINE_1("Location: header (%s) is not an absolute URL, using it as a relative URL", location->GetChars());
2075 if (location->StartsWith("/")) {
2076 NPT_LOG_FINE_1("redirecting to absolute path %s", location->GetChars());
2077 request.GetUrl().ParsePathPlus(*location);
2078 } else {
2079 NPT_String redirect_path = request.GetUrl().GetPath();
2080 int slash_pos = redirect_path.ReverseFind('/');
2081 if (slash_pos >= 0) {
2082 redirect_path.SetLength(slash_pos+1);
2083 } else {
2084 redirect_path = "/";
2085 }
2086 redirect_path += *location;
2087 NPT_LOG_FINE_1("redirecting to absolute path %s", redirect_path.GetChars());
2088 request.GetUrl().ParsePathPlus(redirect_path);
2089 }
2090 } else {
2091 // replace the request url
2092 NPT_LOG_FINE_1("redirecting to %s", location->GetChars());
2093 request.SetUrl(*location);
2094 // remove host header so it is replaced based on new url
2095 request.GetHeaders().RemoveHeader(NPT_HTTP_HEADER_HOST);
2096 }
2097 keep_going = true;
2098 delete response;
2099 response = NULL;
2100 }
2101 }
2102 } while (keep_going && --watchdog && !m_Aborted);
2103
2104 // check if we were bitten by the watchdog
2105 if (watchdog == 0) {
2106 NPT_LOG_WARNING("too many HTTP redirects");
2107 return NPT_ERROR_HTTP_TOO_MANY_REDIRECTS;
2108 }
2109
2110 return result;
2111 }
2112
2113 /*----------------------------------------------------------------------
2114 | NPT_HttpClient::Abort
2115 +---------------------------------------------------------------------*/
2116 NPT_Result
Abort()2117 NPT_HttpClient::Abort()
2118 {
2119 NPT_AutoLock lock(m_AbortLock);
2120 m_Aborted = true;
2121
2122 NPT_HttpConnectionManager::GetInstance()->AbortConnections(this);
2123 return NPT_SUCCESS;
2124 }
2125
2126 /*----------------------------------------------------------------------
2127 | NPT_HttpRequestContext::NPT_HttpRequestContext
2128 +---------------------------------------------------------------------*/
NPT_HttpRequestContext(const NPT_SocketAddress * local_address,const NPT_SocketAddress * remote_address)2129 NPT_HttpRequestContext::NPT_HttpRequestContext(const NPT_SocketAddress* local_address,
2130 const NPT_SocketAddress* remote_address)
2131 {
2132 if (local_address) m_LocalAddress = *local_address;
2133 if (remote_address) m_RemoteAddress = *remote_address;
2134 }
2135
2136 /*----------------------------------------------------------------------
2137 | NPT_HttpServer::NPT_HttpServer
2138 +---------------------------------------------------------------------*/
NPT_HttpServer(NPT_UInt16 listen_port,bool cancellable)2139 NPT_HttpServer::NPT_HttpServer(NPT_UInt16 listen_port, bool cancellable) :
2140 m_Socket(cancellable?NPT_SOCKET_FLAG_CANCELLABLE:0),
2141 m_BoundPort(0),
2142 m_ServerHeader("Neptune/" NPT_NEPTUNE_VERSION_STRING),
2143 m_Run(true)
2144 {
2145 m_Config.m_ListenAddress = NPT_IpAddress::Any;
2146 m_Config.m_ListenPort = listen_port;
2147 m_Config.m_IoTimeout = NPT_HTTP_SERVER_DEFAULT_IO_TIMEOUT;
2148 m_Config.m_ConnectionTimeout = NPT_HTTP_SERVER_DEFAULT_CONNECTION_TIMEOUT;
2149 m_Config.m_ReuseAddress = true;
2150 }
2151
2152 /*----------------------------------------------------------------------
2153 | NPT_HttpServer::NPT_HttpServer
2154 +---------------------------------------------------------------------*/
NPT_HttpServer(NPT_IpAddress listen_address,NPT_UInt16 listen_port,bool cancellable)2155 NPT_HttpServer::NPT_HttpServer(NPT_IpAddress listen_address,
2156 NPT_UInt16 listen_port,
2157 bool cancellable) :
2158 m_Socket(cancellable?NPT_SOCKET_FLAG_CANCELLABLE:0),
2159 m_BoundPort(0),
2160 m_ServerHeader("Neptune/" NPT_NEPTUNE_VERSION_STRING),
2161 m_Run(true)
2162 {
2163 m_Config.m_ListenAddress = listen_address;
2164 m_Config.m_ListenPort = listen_port;
2165 m_Config.m_IoTimeout = NPT_HTTP_SERVER_DEFAULT_IO_TIMEOUT;
2166 m_Config.m_ConnectionTimeout = NPT_HTTP_SERVER_DEFAULT_CONNECTION_TIMEOUT;
2167 m_Config.m_ReuseAddress = true;
2168 }
2169
2170 /*----------------------------------------------------------------------
2171 | NPT_HttpServer::~NPT_HttpServer
2172 +---------------------------------------------------------------------*/
~NPT_HttpServer()2173 NPT_HttpServer::~NPT_HttpServer()
2174 {
2175 m_RequestHandlers.Apply(NPT_ObjectDeleter<HandlerConfig>());
2176 }
2177
2178 /*----------------------------------------------------------------------
2179 | NPT_HttpServer::Bind
2180 +---------------------------------------------------------------------*/
2181 NPT_Result
Bind()2182 NPT_HttpServer::Bind()
2183 {
2184 // check if we're already bound
2185 if (m_BoundPort != 0) return NPT_SUCCESS;
2186
2187 // bind
2188 NPT_Result result = m_Socket.Bind(
2189 NPT_SocketAddress(m_Config.m_ListenAddress, m_Config.m_ListenPort),
2190 m_Config.m_ReuseAddress);
2191 if (NPT_FAILED(result)) return result;
2192
2193 // update the bound port info
2194 NPT_SocketInfo info;
2195 m_Socket.GetInfo(info);
2196 m_BoundPort = info.local_address.GetPort();
2197
2198 return NPT_SUCCESS;
2199 }
2200
2201 /*----------------------------------------------------------------------
2202 | NPT_HttpServer::SetConfig
2203 +---------------------------------------------------------------------*/
2204 NPT_Result
SetConfig(const Config & config)2205 NPT_HttpServer::SetConfig(const Config& config)
2206 {
2207 m_Config = config;
2208
2209 // check that we can bind to this listen port
2210 return Bind();
2211 }
2212
2213 /*----------------------------------------------------------------------
2214 | NPT_HttpServer::SetListenPort
2215 +---------------------------------------------------------------------*/
2216 NPT_Result
SetListenPort(NPT_UInt16 port,bool reuse_address)2217 NPT_HttpServer::SetListenPort(NPT_UInt16 port, bool reuse_address)
2218 {
2219 m_Config.m_ListenPort = port;
2220 m_Config.m_ReuseAddress = reuse_address;
2221 return Bind();
2222 }
2223
2224 /*----------------------------------------------------------------------
2225 | NPT_HttpServer::SetTimeouts
2226 +---------------------------------------------------------------------*/
2227 NPT_Result
SetTimeouts(NPT_Timeout connection_timeout,NPT_Timeout io_timeout)2228 NPT_HttpServer::SetTimeouts(NPT_Timeout connection_timeout,
2229 NPT_Timeout io_timeout)
2230 {
2231 m_Config.m_ConnectionTimeout = connection_timeout;
2232 m_Config.m_IoTimeout = io_timeout;
2233
2234 return NPT_SUCCESS;
2235 }
2236
2237 /*----------------------------------------------------------------------
2238 | NPT_HttpServer::SetServerHeader
2239 +---------------------------------------------------------------------*/
2240 NPT_Result
SetServerHeader(const char * server_header)2241 NPT_HttpServer::SetServerHeader(const char* server_header)
2242 {
2243 m_ServerHeader = server_header;
2244 return NPT_SUCCESS;
2245 }
2246
2247 /*----------------------------------------------------------------------
2248 | NPT_HttpServer::Abort
2249 +---------------------------------------------------------------------*/
2250 NPT_Result
Abort()2251 NPT_HttpServer::Abort()
2252 {
2253 m_Socket.Cancel();
2254 return NPT_SUCCESS;
2255 }
2256
2257 /*----------------------------------------------------------------------
2258 | NPT_HttpServer::WaitForNewClient
2259 +---------------------------------------------------------------------*/
2260 NPT_Result
WaitForNewClient(NPT_InputStreamReference & input,NPT_OutputStreamReference & output,NPT_HttpRequestContext * context,NPT_Flags socket_flags)2261 NPT_HttpServer::WaitForNewClient(NPT_InputStreamReference& input,
2262 NPT_OutputStreamReference& output,
2263 NPT_HttpRequestContext* context,
2264 NPT_Flags socket_flags)
2265 {
2266 // ensure that we're bound
2267 NPT_CHECK_FINE(Bind());
2268
2269 // wait for a connection
2270 NPT_Socket* client;
2271 NPT_LOG_FINE_2("waiting for new connection on %s:%d...",
2272 (const char*)m_Config.m_ListenAddress.ToString(),
2273 m_BoundPort);
2274 NPT_Result result = m_Socket.WaitForNewClient(client, m_Config.m_ConnectionTimeout, socket_flags);
2275 if (result != NPT_ERROR_TIMEOUT) {
2276 NPT_CHECK_WARNING(result);
2277 } else {
2278 NPT_CHECK_FINE(result);
2279 }
2280 if (client == NULL) return NPT_ERROR_INTERNAL;
2281
2282 // get the client info
2283 if (context) {
2284 NPT_SocketInfo client_info;
2285 client->GetInfo(client_info);
2286
2287 context->SetLocalAddress(client_info.local_address);
2288 context->SetRemoteAddress(client_info.remote_address);
2289
2290 NPT_LOG_FINE_2("client connected (%s <- %s)",
2291 client_info.local_address.ToString().GetChars(),
2292 client_info.remote_address.ToString().GetChars());
2293 }
2294
2295 // configure the socket
2296 client->SetReadTimeout(m_Config.m_IoTimeout);
2297 client->SetWriteTimeout(m_Config.m_IoTimeout);
2298
2299 // get the streams
2300 client->GetInputStream(input);
2301 client->GetOutputStream(output);
2302
2303 // we don't need the socket anymore
2304 delete client;
2305
2306 return NPT_SUCCESS;
2307 }
2308
2309 /*----------------------------------------------------------------------
2310 | NPT_HttpServer::Loop
2311 +---------------------------------------------------------------------*/
2312 NPT_Result
Loop(bool cancellable_sockets)2313 NPT_HttpServer::Loop(bool cancellable_sockets)
2314 {
2315 NPT_InputStreamReference input;
2316 NPT_OutputStreamReference output;
2317 NPT_HttpRequestContext context;
2318 NPT_Result result;
2319
2320 do {
2321 // wait for a client to connect
2322 NPT_Flags flags = cancellable_sockets?NPT_SOCKET_FLAG_CANCELLABLE:0;
2323 result = WaitForNewClient(input, output, &context, flags);
2324 NPT_LOG_FINE_2("WaitForNewClient returned %d (%s)",
2325 result,
2326 NPT_ResultText(result));
2327 if (!m_Run) break;
2328 if (result == NPT_ERROR_TIMEOUT) continue;
2329
2330 // respond to the client
2331 if (NPT_SUCCEEDED(result)) {
2332 // send a response
2333 result = RespondToClient(input, output, context);
2334 NPT_LOG_FINE_2("ResponToClient returned %d (%s)",
2335 result,
2336 NPT_ResultText(result));
2337 } else {
2338 NPT_LOG_FINE_2("WaitForNewClient returned %d (%s)",
2339 result,
2340 NPT_ResultText(result));
2341 // if there was an error, wait a short time to avoid spinning
2342 if (result != NPT_ERROR_TERMINATED) {
2343 NPT_LOG_FINE("sleeping before restarting the loop");
2344 NPT_System::Sleep(1.0);
2345 }
2346 }
2347
2348 // release the stream references so that the socket can be closed
2349 input = NULL;
2350 output = NULL;
2351 } while (m_Run && result != NPT_ERROR_TERMINATED);
2352
2353 return result;
2354 }
2355
2356 /*----------------------------------------------------------------------
2357 | NPT_HttpServer::HandlerConfig::HandlerConfig
2358 +---------------------------------------------------------------------*/
HandlerConfig(NPT_HttpRequestHandler * handler,const char * path,bool include_children,bool transfer_ownership)2359 NPT_HttpServer::HandlerConfig::HandlerConfig(NPT_HttpRequestHandler* handler,
2360 const char* path,
2361 bool include_children,
2362 bool transfer_ownership) :
2363 m_Handler(handler),
2364 m_Path(path),
2365 m_IncludeChildren(include_children),
2366 m_HandlerIsOwned(transfer_ownership)
2367 {
2368 }
2369
2370 /*----------------------------------------------------------------------
2371 | NPT_HttpServer::HandlerConfig::~HandlerConfig
2372 +---------------------------------------------------------------------*/
~HandlerConfig()2373 NPT_HttpServer::HandlerConfig::~HandlerConfig()
2374 {
2375 if (m_HandlerIsOwned) delete m_Handler;
2376 }
2377
2378 /*----------------------------------------------------------------------
2379 | NPT_HttpServer::AddRequestHandler
2380 +---------------------------------------------------------------------*/
2381 NPT_Result
AddRequestHandler(NPT_HttpRequestHandler * handler,const char * path,bool include_children,bool transfer_ownership)2382 NPT_HttpServer::AddRequestHandler(NPT_HttpRequestHandler* handler,
2383 const char* path,
2384 bool include_children,
2385 bool transfer_ownership)
2386 {
2387 return m_RequestHandlers.Add(new HandlerConfig(handler, path, include_children, transfer_ownership));
2388 }
2389
2390 /*----------------------------------------------------------------------
2391 | NPT_HttpServer::FindRequestHandler
2392 +---------------------------------------------------------------------*/
2393 NPT_HttpRequestHandler*
FindRequestHandler(NPT_HttpRequest & request)2394 NPT_HttpServer::FindRequestHandler(NPT_HttpRequest& request)
2395 {
2396 NPT_String path = NPT_Uri::PercentDecode(request.GetUrl().GetPath());
2397 for (NPT_List<HandlerConfig*>::Iterator it = m_RequestHandlers.GetFirstItem();
2398 it;
2399 ++it) {
2400 HandlerConfig* config = *it;
2401 if (config->m_IncludeChildren) {
2402 if (path.StartsWith(config->m_Path)) {
2403 return config->m_Handler;
2404 }
2405 } else {
2406 if (path == config->m_Path) {
2407 return config->m_Handler;
2408 }
2409 }
2410 }
2411
2412 // not found
2413 return NULL;
2414 }
2415
2416 /*----------------------------------------------------------------------
2417 | NPT_HttpServer::FindRequestHandlers
2418 +---------------------------------------------------------------------*/
2419 NPT_List<NPT_HttpRequestHandler*>
FindRequestHandlers(NPT_HttpRequest & request)2420 NPT_HttpServer::FindRequestHandlers(NPT_HttpRequest& request)
2421 {
2422 NPT_List<NPT_HttpRequestHandler*> handlers;
2423
2424 for (NPT_List<HandlerConfig*>::Iterator it = m_RequestHandlers.GetFirstItem();
2425 it;
2426 ++it) {
2427 HandlerConfig* config = *it;
2428 if (config->m_IncludeChildren) {
2429 if (request.GetUrl().GetPath(true).StartsWith(config->m_Path)) {
2430 handlers.Add(config->m_Handler);
2431 }
2432 } else {
2433 if (request.GetUrl().GetPath(true) == config->m_Path) {
2434 handlers.Insert(handlers.GetFirstItem(), config->m_Handler);
2435 }
2436 }
2437 }
2438
2439 return handlers;
2440 }
2441
2442 /*----------------------------------------------------------------------
2443 | NPT_HttpServer::RespondToClient
2444 +---------------------------------------------------------------------*/
2445 NPT_Result
RespondToClient(NPT_InputStreamReference & input,NPT_OutputStreamReference & output,const NPT_HttpRequestContext & context)2446 NPT_HttpServer::RespondToClient(NPT_InputStreamReference& input,
2447 NPT_OutputStreamReference& output,
2448 const NPT_HttpRequestContext& context)
2449 {
2450 NPT_HttpRequest* request;
2451 NPT_HttpResponse* response = NULL;
2452 NPT_Result result = NPT_ERROR_NO_SUCH_ITEM;
2453 bool terminate_server = false;
2454
2455 NPT_HttpResponder responder(input, output);
2456 NPT_CHECK_WARNING(responder.ParseRequest(request, &context.GetLocalAddress()));
2457 NPT_LOG_FINE_1("request, path=%s", request->GetUrl().ToRequestString(true).GetChars());
2458
2459 // prepare the response body
2460 NPT_HttpEntity* body = new NPT_HttpEntity();
2461
2462 NPT_HttpRequestHandler* handler = FindRequestHandler(*request);
2463 if (handler) {
2464 // create a response object
2465 response = new NPT_HttpResponse(200, "OK", NPT_HTTP_PROTOCOL_1_0);
2466 response->SetEntity(body);
2467
2468 // ask the handler to setup the response
2469 result = handler->SetupResponse(*request, context, *response);
2470 }
2471 if (result == NPT_ERROR_NO_SUCH_ITEM || handler == NULL) {
2472 body->SetInputStream(NPT_HTTP_DEFAULT_404_HTML);
2473 body->SetContentType("text/html");
2474 if (response == NULL) {
2475 response = new NPT_HttpResponse(404, "Not Found", NPT_HTTP_PROTOCOL_1_0);
2476 } else {
2477 response->SetStatus(404, "Not Found");
2478 }
2479 response->SetEntity(body);
2480 if (handler) {
2481 handler->Completed(NPT_ERROR_NO_SUCH_ITEM);
2482 handler = NULL;
2483 }
2484 } else if (result == NPT_ERROR_PERMISSION_DENIED) {
2485 body->SetInputStream(NPT_HTTP_DEFAULT_403_HTML);
2486 body->SetContentType("text/html");
2487 response->SetStatus(403, "Forbidden");
2488 handler->Completed(NPT_ERROR_PERMISSION_DENIED);
2489 handler = NULL;
2490 } else if (result == NPT_ERROR_TERMINATED) {
2491 // mark that we want to exit
2492 terminate_server = true;
2493 } else if (NPT_FAILED(result)) {
2494 body->SetInputStream(NPT_HTTP_DEFAULT_500_HTML);
2495 body->SetContentType("text/html");
2496 response->SetStatus(500, "Internal Error");
2497 handler->Completed(result);
2498 handler = NULL;
2499 }
2500
2501 // augment the headers with server information
2502 if (m_ServerHeader.GetLength()) {
2503 response->GetHeaders().SetHeader(NPT_HTTP_HEADER_SERVER, m_ServerHeader, false);
2504 }
2505
2506 // send the response headers
2507 result = responder.SendResponseHeaders(*response);
2508 if (NPT_FAILED(result)) {
2509 NPT_LOG_WARNING_2("SendResponseHeaders failed (%d:%s)", result, NPT_ResultText(result));
2510 goto end;
2511 }
2512
2513 // send the body
2514 if (request->GetMethod() != NPT_HTTP_METHOD_HEAD) {
2515 if (handler) {
2516 result = handler->SendResponseBody(context, *response, *output);
2517 } else {
2518 // send body manually in case there was an error with the handler or no handler was found
2519 NPT_InputStreamReference body_stream;
2520 body->GetInputStream(body_stream);
2521 if (!body_stream.IsNull()) {
2522 result = NPT_StreamToStreamCopy(*body_stream, *output, 0, body->GetContentLength());
2523 if (NPT_FAILED(result)) {
2524 NPT_LOG_INFO_2("NPT_StreamToStreamCopy returned %d (%s)", result, NPT_ResultText(result));
2525 goto end;
2526 }
2527 }
2528 }
2529 }
2530
2531 // flush
2532 output->Flush();
2533
2534 // if we need to die, we return an error code
2535 if (NPT_SUCCEEDED(result) && terminate_server) result = NPT_ERROR_TERMINATED;
2536
2537 end:
2538 // cleanup
2539 delete response;
2540 delete request;
2541
2542 if (handler) {
2543 handler->Completed(result);
2544 }
2545
2546 return result;
2547 }
2548
2549 /*----------------------------------------------------------------------
2550 | NPT_HttpResponder::NPT_HttpResponder
2551 +---------------------------------------------------------------------*/
NPT_HttpResponder(NPT_InputStreamReference & input,NPT_OutputStreamReference & output)2552 NPT_HttpResponder::NPT_HttpResponder(NPT_InputStreamReference& input,
2553 NPT_OutputStreamReference& output) :
2554 m_Input(new NPT_BufferedInputStream(input)),
2555 m_Output(output)
2556 {
2557 m_Config.m_IoTimeout = NPT_HTTP_SERVER_DEFAULT_IO_TIMEOUT;
2558 }
2559
2560 /*----------------------------------------------------------------------
2561 | NPT_HttpResponder::~NPT_HttpResponder
2562 +---------------------------------------------------------------------*/
~NPT_HttpResponder()2563 NPT_HttpResponder::~NPT_HttpResponder()
2564 {
2565 }
2566
2567 /*----------------------------------------------------------------------
2568 | NPT_HttpServer::Terminate
2569 +---------------------------------------------------------------------*/
Terminate()2570 void NPT_HttpServer::Terminate()
2571 {
2572 m_Run = false;
2573 }
2574
2575 /*----------------------------------------------------------------------
2576 | NPT_HttpResponder::SetConfig
2577 +---------------------------------------------------------------------*/
2578 NPT_Result
SetConfig(const Config & config)2579 NPT_HttpResponder::SetConfig(const Config& config)
2580 {
2581 m_Config = config;
2582
2583 return NPT_SUCCESS;
2584 }
2585
2586 /*----------------------------------------------------------------------
2587 | NPT_HttpResponder::SetTimeout
2588 +---------------------------------------------------------------------*/
2589 NPT_Result
SetTimeout(NPT_Timeout io_timeout)2590 NPT_HttpResponder::SetTimeout(NPT_Timeout io_timeout)
2591 {
2592 m_Config.m_IoTimeout = io_timeout;
2593
2594 return NPT_SUCCESS;
2595 }
2596
2597 /*----------------------------------------------------------------------
2598 | NPT_HttpResponder::ParseRequest
2599 +---------------------------------------------------------------------*/
2600 NPT_Result
ParseRequest(NPT_HttpRequest * & request,const NPT_SocketAddress * local_address)2601 NPT_HttpResponder::ParseRequest(NPT_HttpRequest*& request,
2602 const NPT_SocketAddress* local_address)
2603 {
2604 // rebuffer the stream in case we're using a keep-alive connection
2605 m_Input->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE);
2606
2607 // parse the request
2608 NPT_CHECK_FINE(NPT_HttpRequest::Parse(*m_Input, local_address, request));
2609
2610 // unbuffer the stream
2611 m_Input->SetBufferSize(0);
2612
2613 // don't create an entity if no body is expected
2614 if (request->GetMethod() == NPT_HTTP_METHOD_GET ||
2615 request->GetMethod() == NPT_HTTP_METHOD_HEAD ||
2616 request->GetMethod() == NPT_HTTP_METHOD_TRACE) {
2617 return NPT_SUCCESS;
2618 }
2619
2620 // set the entity info
2621 NPT_HttpEntity* entity = new NPT_HttpEntity(request->GetHeaders());
2622 if (entity->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED) {
2623 entity->SetInputStream(NPT_InputStreamReference(new NPT_HttpChunkedInputStream(m_Input)));
2624 } else {
2625 entity->SetInputStream(m_Input);
2626 }
2627 request->SetEntity(entity);
2628
2629 return NPT_SUCCESS;
2630 }
2631
2632 /*----------------------------------------------------------------------
2633 | NPT_HttpResponder::SendResponseHeaders
2634 +---------------------------------------------------------------------*/
2635 NPT_Result
SendResponseHeaders(NPT_HttpResponse & response)2636 NPT_HttpResponder::SendResponseHeaders(NPT_HttpResponse& response)
2637 {
2638 // add default headers
2639 NPT_HttpHeaders& headers = response.GetHeaders();
2640 if (response.GetProtocol() == NPT_HTTP_PROTOCOL_1_0) {
2641 headers.SetHeader(NPT_HTTP_HEADER_CONNECTION,
2642 "close", false); // set but don't replace
2643 }
2644
2645 // add computed headers
2646 NPT_HttpEntity* entity = response.GetEntity();
2647 if (entity) {
2648 // content type
2649 const NPT_String& content_type = entity->GetContentType();
2650 if (!content_type.IsEmpty()) {
2651 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE, content_type);
2652 }
2653
2654 // content encoding
2655 const NPT_String& content_encoding = entity->GetContentEncoding();
2656 if (!content_encoding.IsEmpty()) {
2657 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING, content_encoding);
2658 }
2659
2660 // transfer encoding
2661 const NPT_String& transfer_encoding = entity->GetTransferEncoding();
2662 if (!transfer_encoding.IsEmpty()) {
2663 headers.SetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING, transfer_encoding);
2664 }
2665
2666 // set the content length if known
2667 if (entity->ContentLengthIsKnown()) {
2668 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH,
2669 NPT_String::FromInteger(entity->GetContentLength()));
2670 } else if (transfer_encoding.IsEmpty() || transfer_encoding.Compare(NPT_HTTP_TRANSFER_ENCODING_CHUNKED, true)) {
2671 // no content length, the only way client will know we're done
2672 // is when we'll close the connection unless it's chunked encoding
2673 headers.SetHeader(NPT_HTTP_HEADER_CONNECTION,
2674 "close", true); // set and replace
2675 }
2676 } else {
2677 // force content length to 0 if there is no message body
2678 // (necessary for 1.1 or 1.0 with keep-alive connections)
2679 headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, "0");
2680 }
2681
2682 // create a memory stream to buffer the response line and headers
2683 NPT_MemoryStream buffer;
2684
2685 // emit the response line
2686 NPT_CHECK_WARNING(response.Emit(buffer));
2687
2688 // send the buffer
2689 NPT_CHECK_WARNING(m_Output->WriteFully(buffer.GetData(), buffer.GetDataSize()));
2690
2691 return NPT_SUCCESS;
2692 }
2693
2694 /*----------------------------------------------------------------------
2695 | NPT_HttpRequestHandler Dynamic Cast Anchor
2696 +---------------------------------------------------------------------*/
NPT_DEFINE_DYNAMIC_CAST_ANCHOR(NPT_HttpRequestHandler)2697 NPT_DEFINE_DYNAMIC_CAST_ANCHOR(NPT_HttpRequestHandler)
2698
2699 /*----------------------------------------------------------------------
2700 | NPT_HttpRequestHandler::SendResponseBody
2701 +---------------------------------------------------------------------*/
2702 NPT_Result
2703 NPT_HttpRequestHandler::SendResponseBody(const NPT_HttpRequestContext& /*context*/,
2704 NPT_HttpResponse& response,
2705 NPT_OutputStream& output)
2706 {
2707 NPT_HttpEntity* entity = response.GetEntity();
2708 if (entity == NULL) return NPT_SUCCESS;
2709
2710 NPT_InputStreamReference body_stream;
2711 entity->GetInputStream(body_stream);
2712 if (body_stream.IsNull()) return NPT_SUCCESS;
2713
2714 // check for chunked transfer encoding
2715 NPT_OutputStream* dest = &output;
2716 if (entity->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED) {
2717 dest = new NPT_HttpChunkedOutputStream(output);
2718 }
2719
2720 // send the body
2721 NPT_LOG_FINE_1("sending body stream, %lld bytes", entity->GetContentLength());
2722 NPT_LargeSize bytes_written = 0;
2723 NPT_Result result = NPT_StreamToStreamCopy(*body_stream, *dest, 0, entity->GetContentLength(), &bytes_written);
2724 if (NPT_FAILED(result)) {
2725 NPT_LOG_FINE_3("body stream only partially sent, %lld bytes (%d:%s)",
2726 bytes_written,
2727 result,
2728 NPT_ResultText(result));
2729 }
2730
2731 // flush to write out any buffered data left in chunked output if used
2732 dest->Flush();
2733
2734 // cleanup (this will send zero size chunk followed by CRLF)
2735 if (dest != &output) delete dest;
2736
2737 return result;
2738 }
2739
2740 /*----------------------------------------------------------------------
2741 | NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler
2742 +---------------------------------------------------------------------*/
NPT_HttpStaticRequestHandler(const void * data,NPT_Size size,const char * mime_type,bool copy)2743 NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler(const void* data,
2744 NPT_Size size,
2745 const char* mime_type,
2746 bool copy) :
2747 m_MimeType(mime_type),
2748 m_Buffer(data, size, copy)
2749 {}
2750
2751 /*----------------------------------------------------------------------
2752 | NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler
2753 +---------------------------------------------------------------------*/
NPT_HttpStaticRequestHandler(const char * document,const char * mime_type,bool copy)2754 NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler(const char* document,
2755 const char* mime_type,
2756 bool copy) :
2757 m_MimeType(mime_type),
2758 m_Buffer(document, NPT_StringLength(document), copy)
2759 {}
2760
2761 /*----------------------------------------------------------------------
2762 | NPT_HttpStaticRequestHandler::SetupResponse
2763 +---------------------------------------------------------------------*/
2764 NPT_Result
SetupResponse(NPT_HttpRequest &,const NPT_HttpRequestContext &,NPT_HttpResponse & response)2765 NPT_HttpStaticRequestHandler::SetupResponse(NPT_HttpRequest& /*request*/,
2766 const NPT_HttpRequestContext& /*context*/,
2767 NPT_HttpResponse& response)
2768 {
2769 NPT_HttpEntity* entity = response.GetEntity();
2770 if (entity == NULL) return NPT_ERROR_INVALID_STATE;
2771
2772 entity->SetContentType(m_MimeType);
2773 entity->SetInputStream(m_Buffer.GetData(), m_Buffer.GetDataSize());
2774
2775 return NPT_SUCCESS;
2776 }
2777
2778 const NPT_HttpFileRequestHandler_DefaultFileTypeMapEntry
2779 NPT_HttpFileRequestHandler_DefaultFileTypeMap[] = {
2780 {"xml", "text/xml; charset=\"utf-8\"" },
2781 {"htm", "text/html" },
2782 {"html", "text/html" },
2783 {"c", "text/plain"},
2784 {"h", "text/plain"},
2785 {"txt", "text/plain"},
2786 {"css", "text/css" },
2787 {"manifest", "text/cache-manifest"},
2788 {"gif", "image/gif" },
2789 {"thm", "image/jpeg"},
2790 {"png", "image/png"},
2791 {"tif", "image/tiff"},
2792 {"tiff", "image/tiff"},
2793 {"jpg", "image/jpeg"},
2794 {"jpeg", "image/jpeg"},
2795 {"jpe", "image/jpeg"},
2796 {"jp2", "image/jp2" },
2797 {"png", "image/png" },
2798 {"bmp", "image/bmp" },
2799 {"aif", "audio/x-aiff"},
2800 {"aifc", "audio/x-aiff"},
2801 {"aiff", "audio/x-aiff"},
2802 {"mpa", "audio/mpeg"},
2803 {"mp2", "audio/mpeg"},
2804 {"mp3", "audio/mpeg"},
2805 {"m4a", "audio/mp4"},
2806 {"wma", "audio/x-ms-wma"},
2807 {"wav", "audio/x-wav"},
2808 {"mpeg", "video/mpeg"},
2809 {"mpg", "video/mpeg"},
2810 {"mp4", "video/mp4"},
2811 {"m4v", "video/mp4"},
2812 {"ts", "video/MP2T"}, // RFC 3555
2813 {"mpegts", "video/MP2T"},
2814 {"mov", "video/quicktime"},
2815 {"qt", "video/quicktime"},
2816 {"wmv", "video/x-ms-wmv"},
2817 {"wtv", "video/x-ms-wmv"},
2818 {"asf", "video/x-ms-asf"},
2819 {"mkv", "video/x-matroska"},
2820 {"flv", "video/x-flv"},
2821 {"avi", "video/x-msvideo"},
2822 {"divx", "video/x-msvideo"},
2823 {"xvid", "video/x-msvideo"},
2824 {"doc", "application/msword"},
2825 {"js", "application/javascript"},
2826 {"m3u8", "application/x-mpegURL"},
2827 {"pdf", "application/pdf"},
2828 {"ps", "application/postscript"},
2829 {"eps", "application/postscript"},
2830 {"zip", "application/zip"}
2831 };
2832
2833 /*----------------------------------------------------------------------
2834 | NPT_HttpFileRequestHandler::NPT_HttpFileRequestHandler
2835 +---------------------------------------------------------------------*/
NPT_HttpFileRequestHandler(const char * url_root,const char * file_root,bool auto_dir,const char * auto_index)2836 NPT_HttpFileRequestHandler::NPT_HttpFileRequestHandler(const char* url_root,
2837 const char* file_root,
2838 bool auto_dir,
2839 const char* auto_index) :
2840 m_UrlRoot(url_root),
2841 m_FileRoot(file_root),
2842 m_DefaultMimeType("text/html"),
2843 m_UseDefaultFileTypeMap(true),
2844 m_AutoDir(auto_dir),
2845 m_AutoIndex(auto_index)
2846 {
2847 }
2848
2849 /*----------------------------------------------------------------------
2850 | helper functions FIXME: need to move these to a separate module
2851 +---------------------------------------------------------------------*/
2852 static NPT_UInt32
_utf8_decode(const char ** str)2853 _utf8_decode(const char** str)
2854 {
2855 NPT_UInt32 result;
2856 NPT_UInt32 min_value;
2857 unsigned int bytes_left;
2858
2859 if (**str == 0) {
2860 return ~0;
2861 } else if ((**str & 0x80) == 0x00) {
2862 result = *(*str)++;
2863 bytes_left = 0;
2864 min_value = 0;
2865 } else if ((**str & 0xE0) == 0xC0) {
2866 result = *(*str)++ & 0x1F;
2867 bytes_left = 1;
2868 min_value = 0x80;
2869 } else if ((**str & 0xF0) == 0xE0) {
2870 result = *(*str)++ & 0x0F;
2871 bytes_left = 2;
2872 min_value = 0x800;
2873 } else if ((**str & 0xF8) == 0xF0) {
2874 result = *(*str)++ & 0x07;
2875 bytes_left = 3;
2876 min_value = 0x10000;
2877 } else {
2878 return ~0;
2879 }
2880
2881 while (bytes_left--) {
2882 if (**str == 0 || (**str & 0xC0) != 0x80) return ~0;
2883 result = (result << 6) | (*(*str)++ & 0x3F);
2884 }
2885
2886 if (result < min_value || (result & 0xFFFFF800) == 0xD800 || result > 0x10FFFF) {
2887 return ~0;
2888 }
2889
2890 return result;
2891 }
2892
2893 /*----------------------------------------------------------------------
2894 | NPT_HtmlEncode
2895 +---------------------------------------------------------------------*/
2896 static NPT_String
NPT_HtmlEncode(const char * str,const char * chars)2897 NPT_HtmlEncode(const char* str, const char* chars)
2898 {
2899 NPT_String encoded;
2900
2901 // check args
2902 if (str == NULL) return encoded;
2903
2904 // reserve at least the size of the current uri
2905 encoded.Reserve(NPT_StringLength(str));
2906
2907 // process each character
2908 while (*str) {
2909 NPT_UInt32 c = _utf8_decode(&str);
2910 bool encode = false;
2911 if (c < ' ' || c > '~') {
2912 encode = true;
2913 } else {
2914 const char* match = chars;
2915 while (*match) {
2916 if (c == (NPT_UInt32)*match) {
2917 encode = true;
2918 break;
2919 }
2920 ++match;
2921 }
2922 }
2923 if (encode) {
2924 // encode
2925 char hex[9];
2926 encoded += "&#x";
2927 unsigned int len = 0;
2928 if (c > 0xFFFF) {
2929 NPT_ByteToHex((unsigned char)(c>>24), &hex[0], true);
2930 NPT_ByteToHex((unsigned char)(c>>16), &hex[2], true);
2931 len = 4;
2932 }
2933 NPT_ByteToHex((unsigned char)(c>>8), &hex[len ], true);
2934 NPT_ByteToHex((unsigned char)(c ), &hex[len+2], true);
2935 hex[len+4] = ';';
2936 encoded.Append(hex, len+5);
2937 } else {
2938 // no encoding required
2939 encoded += (char)c;
2940 }
2941 }
2942
2943 return encoded;
2944 }
2945
2946 /*----------------------------------------------------------------------
2947 | NPT_HttpFileRequestHandler::SetupResponse
2948 +---------------------------------------------------------------------*/
2949 NPT_Result
SetupResponse(NPT_HttpRequest & request,const NPT_HttpRequestContext &,NPT_HttpResponse & response)2950 NPT_HttpFileRequestHandler::SetupResponse(NPT_HttpRequest& request,
2951 const NPT_HttpRequestContext& /* context */,
2952 NPT_HttpResponse& response)
2953 {
2954 NPT_HttpEntity* entity = response.GetEntity();
2955 if (entity == NULL) return NPT_ERROR_INVALID_STATE;
2956
2957 // check the method
2958 if (request.GetMethod() != NPT_HTTP_METHOD_GET &&
2959 request.GetMethod() != NPT_HTTP_METHOD_HEAD) {
2960 response.SetStatus(405, "Method Not Allowed");
2961 return NPT_SUCCESS;
2962 }
2963
2964 // set some default headers
2965 response.GetHeaders().SetHeader(NPT_HTTP_HEADER_ACCEPT_RANGES, "bytes");
2966
2967 // declare HTTP/1.1 if the client asked for it
2968 if (request.GetProtocol() == NPT_HTTP_PROTOCOL_1_1) {
2969 response.SetProtocol(NPT_HTTP_PROTOCOL_1_1);
2970 }
2971
2972 // TODO: we need to normalize the request path
2973
2974 // check that the request's path is an entry under the url root
2975 if (!request.GetUrl().GetPath(true).StartsWith(m_UrlRoot)) {
2976 return NPT_ERROR_INVALID_PARAMETERS;
2977 }
2978
2979 // compute the filename
2980 NPT_String filename = m_FileRoot;
2981 NPT_String relative_path = NPT_Url::PercentDecode(request.GetUrl().GetPath().GetChars()+m_UrlRoot.GetLength());
2982 filename += "/";
2983 filename += relative_path;
2984 NPT_LOG_FINE_1("filename = %s", filename.GetChars());
2985
2986 // get info about the file
2987 NPT_FileInfo info;
2988 NPT_File::GetInfo(filename, &info);
2989
2990 // check if this is a directory
2991 if (info.m_Type == NPT_FileInfo::FILE_TYPE_DIRECTORY) {
2992 NPT_LOG_FINE("file is a DIRECTORY");
2993 if (m_AutoDir) {
2994 if (m_AutoIndex.GetLength()) {
2995 NPT_LOG_FINE("redirecting to auto-index");
2996 filename += NPT_FilePath::Separator;
2997 filename += m_AutoIndex;
2998 if (NPT_File::Exists(filename)) {
2999 NPT_String location = m_UrlRoot+"/"+m_AutoIndex;
3000 response.SetStatus(302, "Found");
3001 response.GetHeaders().SetHeader(NPT_HTTP_HEADER_LOCATION, location);
3002 } else {
3003 return NPT_ERROR_PERMISSION_DENIED;
3004 }
3005 } else {
3006 NPT_LOG_FINE("doing auto-dir");
3007
3008 // get the dir entries
3009 NPT_List<NPT_String> entries;
3010 NPT_File::ListDir(filename, entries);
3011
3012 NPT_String html;
3013 html.Reserve(1024+128*entries.GetItemCount());
3014
3015 NPT_String html_dirname = NPT_HtmlEncode(relative_path, "<>&");
3016 html += "<hmtl><head><title>Directory Listing for /";
3017 html += html_dirname;
3018 html += "</title></head><body>";
3019 html += "<h2>Directory Listing for /";
3020 html += html_dirname;
3021 html += "</h2><hr><ul>\r\n";
3022 NPT_String url_base_path = NPT_HtmlEncode(request.GetUrl().GetPath(), "<>&\"");
3023
3024 for (NPT_List<NPT_String>::Iterator i = entries.GetFirstItem();
3025 i;
3026 ++i) {
3027 NPT_String url_filename = NPT_HtmlEncode(*i, "<>&");
3028 html += "<li><a href=\"";
3029 html += url_base_path;
3030 if (!url_base_path.EndsWith("/")) html += "/";
3031 html += url_filename;
3032 html += "\">";
3033 html +=url_filename;
3034
3035 NPT_String full_path = filename;
3036 full_path += "/";
3037 full_path += *i;
3038 NPT_File::GetInfo(full_path, &info);
3039 if (info.m_Type == NPT_FileInfo::FILE_TYPE_DIRECTORY) html += "/";
3040
3041 html += "</a><br>\r\n";
3042 }
3043 html += "</ul></body></html>";
3044
3045 entity->SetContentType("text/html");
3046 entity->SetInputStream(html);
3047 return NPT_SUCCESS;
3048 }
3049 } else {
3050 return NPT_ERROR_PERMISSION_DENIED;
3051 }
3052 }
3053
3054 // open the file
3055 NPT_File file(filename);
3056 NPT_Result result = file.Open(NPT_FILE_OPEN_MODE_READ);
3057 if (NPT_FAILED(result)) {
3058 NPT_LOG_FINE("file not found");
3059 return NPT_ERROR_NO_SUCH_ITEM;
3060 }
3061 NPT_InputStreamReference stream;
3062 file.GetInputStream(stream);
3063
3064 // check for range requests
3065 const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE);
3066
3067 // setup entity body
3068 NPT_CHECK(SetupResponseBody(response, stream, range_spec));
3069
3070 // set the response body
3071 entity->SetContentType(GetContentType(filename));
3072
3073 return NPT_SUCCESS;
3074 }
3075
3076 /*----------------------------------------------------------------------
3077 | NPT_HttpFileRequestHandler::SetupResponseBody
3078 +---------------------------------------------------------------------*/
3079 NPT_Result
SetupResponseBody(NPT_HttpResponse & response,NPT_InputStreamReference & stream,const NPT_String * range_spec)3080 NPT_HttpFileRequestHandler::SetupResponseBody(NPT_HttpResponse& response,
3081 NPT_InputStreamReference& stream,
3082 const NPT_String* range_spec /* = NULL */)
3083 {
3084 NPT_HttpEntity* entity = response.GetEntity();
3085 if (entity == NULL) return NPT_ERROR_INVALID_STATE;
3086
3087 if (range_spec) {
3088 const NPT_String* accept_range = response.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_ACCEPT_RANGES);
3089
3090 if (response.GetEntity()->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED ||
3091 (accept_range && accept_range->Compare("bytes"))) {
3092 NPT_LOG_FINE("range request not supported");
3093 response.SetStatus(416, "Requested Range Not Satisfiable");
3094 return NPT_SUCCESS;
3095 }
3096
3097 // measure the stream size
3098 bool has_stream_size = false;
3099 NPT_LargeSize stream_size = 0;
3100 NPT_Result result = stream->GetSize(stream_size);
3101 if (NPT_SUCCEEDED(result)) {
3102 has_stream_size = true;
3103 NPT_LOG_FINE_1("body size=%lld", stream_size);
3104 if (stream_size == 0) return NPT_SUCCESS;
3105 }
3106
3107 if (!range_spec->StartsWith("bytes=")) {
3108 NPT_LOG_FINE("unknown range spec");
3109 response.SetStatus(400, "Bad Request");
3110 return NPT_SUCCESS;
3111 }
3112 NPT_String valid_range;
3113 NPT_String range(range_spec->GetChars()+6);
3114 if (range.Find(',') >= 0) {
3115 NPT_LOG_FINE("multi-range requests not supported");
3116 if (has_stream_size) {
3117 valid_range = "bytes */";
3118 valid_range += NPT_String::FromInteger(stream_size);
3119 response.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, valid_range.GetChars());
3120 }
3121 response.SetStatus(416, "Requested Range Not Satisfiable");
3122 return NPT_SUCCESS;
3123 }
3124 int sep = range.Find('-');
3125 NPT_UInt64 range_start = 0;
3126 NPT_UInt64 range_end = 0;
3127 bool has_start = false;
3128 bool has_end = false;
3129 bool satisfied = false;
3130 if (sep < 0) {
3131 NPT_LOG_FINE("invalid syntax");
3132 response.SetStatus(400, "Bad Request");
3133 return NPT_SUCCESS;
3134 } else {
3135 if ((unsigned int)sep+1 < range.GetLength()) {
3136 result = NPT_ParseInteger64(range.GetChars()+sep+1, range_end);
3137 if (NPT_FAILED(result)) {
3138 NPT_LOG_FINE("failed to parse range end");
3139 return result;
3140 }
3141 range.SetLength(sep);
3142 has_end = true;
3143 }
3144 if (sep > 0) {
3145 result = range.ToInteger64(range_start);
3146 if (NPT_FAILED(result)) {
3147 NPT_LOG_FINE("failed to parse range start");
3148 return result;
3149 }
3150 has_start = true;
3151 }
3152
3153 if (!has_stream_size) {
3154 if (has_start && range_start == 0 && !has_end) {
3155 bool update_content_length = (entity->GetTransferEncoding() != NPT_HTTP_TRANSFER_ENCODING_CHUNKED);
3156 // use the whole file stream as a body
3157 return entity->SetInputStream(stream, update_content_length);
3158 } else {
3159 NPT_LOG_WARNING_2("file.GetSize() failed (%d:%s)", result, NPT_ResultText(result));
3160 NPT_LOG_FINE("range request not supported");
3161 response.SetStatus(416, "Requested Range Not Satisfiable");
3162 return NPT_SUCCESS;
3163 }
3164 }
3165
3166 if (has_start) {
3167 // some clients sends incorrect range_end equal to size
3168 // we try to handle it
3169 if (!has_end || range_end == stream_size) range_end = stream_size-1;
3170 } else {
3171 if (has_end) {
3172 if (range_end <= stream_size) {
3173 range_start = stream_size-range_end;
3174 range_end = stream_size-1;
3175 }
3176 }
3177 }
3178 NPT_LOG_FINE_2("final range: start=%lld, end=%lld", range_start, range_end);
3179 if (range_start > range_end) {
3180 NPT_LOG_FINE("invalid range");
3181 response.SetStatus(400, "Bad Request");
3182 satisfied = false;
3183 } else if (range_end >= stream_size) {
3184 response.SetStatus(416, "Requested Range Not Satisfiable");
3185 NPT_LOG_FINE("out of range");
3186 satisfied = false;
3187 } else {
3188 satisfied = true;
3189 }
3190 }
3191 if (satisfied && range_start != 0) {
3192 // seek in the stream
3193 result = stream->Seek(range_start);
3194 if (NPT_FAILED(result)) {
3195 NPT_LOG_WARNING_2("stream.Seek() failed (%d:%s)", result, NPT_ResultText(result));
3196 satisfied = false;
3197 }
3198 }
3199 if (!satisfied) {
3200 if (!valid_range.IsEmpty()) response.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, valid_range.GetChars());
3201 response.SetStatus(416, "Requested Range Not Satisfiable");
3202 return NPT_SUCCESS;
3203 }
3204
3205 // use a portion of the file stream as a body
3206 entity->SetInputStream(stream, false);
3207 entity->SetContentLength(range_end-range_start+1);
3208 response.SetStatus(206, "Partial Content");
3209 valid_range = "bytes ";
3210 valid_range += NPT_String::FromInteger(range_start);
3211 valid_range += "-";
3212 valid_range += NPT_String::FromInteger(range_end);
3213 valid_range += "/";
3214 valid_range += NPT_String::FromInteger(stream_size);
3215 response.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, valid_range.GetChars());
3216 } else {
3217 bool update_content_length = (entity->GetTransferEncoding() != NPT_HTTP_TRANSFER_ENCODING_CHUNKED);
3218 // use the whole file stream as a body
3219 entity->SetInputStream(stream, update_content_length);
3220 }
3221
3222 return NPT_SUCCESS;
3223 }
3224
3225 /*----------------------------------------------------------------------
3226 | NPT_HttpFileRequestHandler::GetContentType
3227 +---------------------------------------------------------------------*/
3228 const char*
GetDefaultContentType(const char * extension)3229 NPT_HttpFileRequestHandler::GetDefaultContentType(const char* extension)
3230 {
3231 for (unsigned int i=0; i<NPT_ARRAY_SIZE(NPT_HttpFileRequestHandler_DefaultFileTypeMap); i++) {
3232 if (NPT_String::Compare(extension, NPT_HttpFileRequestHandler_DefaultFileTypeMap[i].extension, true) == 0) {
3233 const char* type = NPT_HttpFileRequestHandler_DefaultFileTypeMap[i].mime_type;
3234 NPT_LOG_FINE_1("using type from default list: %s", type);
3235 return type;
3236 }
3237 }
3238
3239 return NULL;
3240 }
3241
3242 /*----------------------------------------------------------------------
3243 | NPT_HttpFileRequestHandler::GetContentType
3244 +---------------------------------------------------------------------*/
3245 const char*
GetContentType(const NPT_String & filename)3246 NPT_HttpFileRequestHandler::GetContentType(const NPT_String& filename)
3247 {
3248 int last_dot = filename.ReverseFind('.');
3249 if (last_dot > 0) {
3250 NPT_String extension = filename.GetChars()+last_dot+1;
3251 extension.MakeLowercase();
3252
3253 NPT_LOG_FINE_1("extension=%s", extension.GetChars());
3254
3255 NPT_String* mime_type;
3256 if (NPT_SUCCEEDED(m_FileTypeMap.Get(extension, mime_type))) {
3257 NPT_LOG_FINE_1("found mime type in map: %s", mime_type->GetChars());
3258 return mime_type->GetChars();
3259 }
3260
3261 // not found, look in the default map if necessary
3262 if (m_UseDefaultFileTypeMap) {
3263 const char* type = NPT_HttpFileRequestHandler::GetDefaultContentType(extension);
3264 if (type) return type;
3265 }
3266 }
3267
3268 NPT_LOG_FINE("using default mime type");
3269 return m_DefaultMimeType;
3270 }
3271
3272 /*----------------------------------------------------------------------
3273 | NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream
3274 +---------------------------------------------------------------------*/
NPT_HttpChunkedInputStream(NPT_BufferedInputStreamReference & stream)3275 NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream(
3276 NPT_BufferedInputStreamReference& stream) :
3277 m_Source(stream),
3278 m_CurrentChunkSize(0),
3279 m_Eos(false)
3280 {
3281 }
3282
3283 /*----------------------------------------------------------------------
3284 | NPT_HttpChunkedInputStream::~NPT_HttpChunkedInputStream
3285 +---------------------------------------------------------------------*/
~NPT_HttpChunkedInputStream()3286 NPT_HttpChunkedInputStream::~NPT_HttpChunkedInputStream()
3287 {
3288 }
3289
3290 /*----------------------------------------------------------------------
3291 | NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream
3292 +---------------------------------------------------------------------*/
3293 NPT_Result
Read(void * buffer,NPT_Size bytes_to_read,NPT_Size * bytes_read)3294 NPT_HttpChunkedInputStream::Read(void* buffer,
3295 NPT_Size bytes_to_read,
3296 NPT_Size* bytes_read /* = NULL */)
3297 {
3298 // set the initial state of return values
3299 if (bytes_read) *bytes_read = 0;
3300
3301 // check for end of stream
3302 if (m_Eos) return NPT_ERROR_EOS;
3303
3304 // shortcut
3305 if (bytes_to_read == 0) return NPT_SUCCESS;
3306
3307 // read next chunk size if needed
3308 if (m_CurrentChunkSize == 0) {
3309 // buffered mode
3310 m_Source->SetBufferSize(4096);
3311
3312 NPT_String size_line;
3313 NPT_CHECK_FINE(m_Source->ReadLine(size_line));
3314
3315 // decode size (in hex)
3316 m_CurrentChunkSize = 0;
3317 if (size_line.GetLength() < 1) {
3318 NPT_LOG_WARNING("empty chunk size line");
3319 return NPT_ERROR_INVALID_FORMAT;
3320 }
3321 const char* size_hex = size_line.GetChars();
3322 while (*size_hex != '\0' &&
3323 *size_hex != ' ' &&
3324 *size_hex != ';' &&
3325 *size_hex != '\r' &&
3326 *size_hex != '\n') {
3327 int nibble = NPT_HexToNibble(*size_hex);
3328 if (nibble < 0) {
3329 NPT_LOG_WARNING_1("invalid chunk size format (%s)", size_line.GetChars());
3330 return NPT_ERROR_INVALID_FORMAT;
3331 }
3332 m_CurrentChunkSize = (m_CurrentChunkSize<<4)|nibble;
3333 ++size_hex;
3334 }
3335 NPT_LOG_FINEST_1("start of chunk, size=%d", m_CurrentChunkSize);
3336
3337 // 0 = end of body
3338 if (m_CurrentChunkSize == 0) {
3339 NPT_LOG_FINEST("end of chunked stream, reading trailers");
3340
3341 // read footers until empty line
3342 NPT_String footer;
3343 do {
3344 NPT_CHECK_FINE(m_Source->ReadLine(footer));
3345 } while (!footer.IsEmpty());
3346 m_Eos = true;
3347
3348 NPT_LOG_FINEST("end of chunked stream, done");
3349 return NPT_ERROR_EOS;
3350 }
3351
3352 // unbuffer source
3353 m_Source->SetBufferSize(0);
3354 }
3355
3356 // read no more than what's left in chunk
3357 NPT_Size chunk_bytes_read;
3358 if (bytes_to_read > m_CurrentChunkSize) bytes_to_read = m_CurrentChunkSize;
3359 NPT_CHECK_FINE(m_Source->Read(buffer, bytes_to_read, &chunk_bytes_read));
3360
3361 // ready to go to next chunk?
3362 m_CurrentChunkSize -= chunk_bytes_read;
3363 if (m_CurrentChunkSize == 0) {
3364 NPT_LOG_FINEST("reading end of chunk");
3365
3366 // when a chunk is finished, a \r\n follows
3367 char newline[2];
3368 NPT_CHECK_FINE(m_Source->ReadFully(newline, 2));
3369 if (newline[0] != '\r' || newline[1] != '\n') {
3370 NPT_LOG_WARNING("invalid end of chunk (expected \\r\\n)");
3371 return NPT_ERROR_INVALID_FORMAT;
3372 }
3373 }
3374
3375 // update output params
3376 if (bytes_read) *bytes_read = chunk_bytes_read;
3377
3378 return NPT_SUCCESS;
3379 }
3380
3381 /*----------------------------------------------------------------------
3382 | NPT_HttpChunkedInputStream::Seek
3383 +---------------------------------------------------------------------*/
3384 NPT_Result
Seek(NPT_Position)3385 NPT_HttpChunkedInputStream::Seek(NPT_Position /*offset*/)
3386 {
3387 return NPT_ERROR_NOT_SUPPORTED;
3388 }
3389
3390 /*----------------------------------------------------------------------
3391 | NPT_HttpChunkedInputStream::Tell
3392 +---------------------------------------------------------------------*/
3393 NPT_Result
Tell(NPT_Position & offset)3394 NPT_HttpChunkedInputStream::Tell(NPT_Position& offset)
3395 {
3396 offset = 0;
3397 return NPT_ERROR_NOT_SUPPORTED;
3398 }
3399
3400 /*----------------------------------------------------------------------
3401 | NPT_HttpChunkedInputStream::GetSize
3402 +---------------------------------------------------------------------*/
3403 NPT_Result
GetSize(NPT_LargeSize & size)3404 NPT_HttpChunkedInputStream::GetSize(NPT_LargeSize& size)
3405 {
3406 return m_Source->GetSize(size);
3407 }
3408
3409 /*----------------------------------------------------------------------
3410 | NPT_HttpChunkedInputStream::GetAvailable
3411 +---------------------------------------------------------------------*/
3412 NPT_Result
GetAvailable(NPT_LargeSize & available)3413 NPT_HttpChunkedInputStream::GetAvailable(NPT_LargeSize& available)
3414 {
3415 return m_Source->GetAvailable(available);
3416 }
3417
3418 /*----------------------------------------------------------------------
3419 | NPT_HttpChunkedOutputStream::NPT_HttpChunkedOutputStream
3420 +---------------------------------------------------------------------*/
NPT_HttpChunkedOutputStream(NPT_OutputStream & stream)3421 NPT_HttpChunkedOutputStream::NPT_HttpChunkedOutputStream(NPT_OutputStream& stream) :
3422 m_Stream(stream)
3423 {
3424 }
3425
3426 /*----------------------------------------------------------------------
3427 | NPT_HttpChunkedOutputStream::~NPT_HttpChunkedOutputStream
3428 +---------------------------------------------------------------------*/
~NPT_HttpChunkedOutputStream()3429 NPT_HttpChunkedOutputStream::~NPT_HttpChunkedOutputStream()
3430 {
3431 // zero size chunk followed by CRLF (no trailer)
3432 m_Stream.WriteFully("0" NPT_HTTP_LINE_TERMINATOR NPT_HTTP_LINE_TERMINATOR, 5);
3433 }
3434
3435 /*----------------------------------------------------------------------
3436 | NPT_HttpChunkedOutputStream::Write
3437 +---------------------------------------------------------------------*/
3438 NPT_Result
Write(const void * buffer,NPT_Size bytes_to_write,NPT_Size * bytes_written)3439 NPT_HttpChunkedOutputStream::Write(const void* buffer,
3440 NPT_Size bytes_to_write,
3441 NPT_Size* bytes_written)
3442 {
3443 // default values
3444 if (bytes_written) *bytes_written = 0;
3445
3446 // shortcut
3447 if (bytes_to_write == 0) return NPT_SUCCESS;
3448
3449 // write the chunk header
3450 char size[16];
3451 size[15] = '\n';
3452 size[14] = '\r';
3453 char* c = &size[14];
3454 unsigned int char_count = 2;
3455 unsigned int value = bytes_to_write;
3456 do {
3457 unsigned int digit = (unsigned int)(value%16);
3458 if (digit < 10) {
3459 *--c = '0'+digit;
3460 } else {
3461 *--c = 'A'+digit-10;
3462 }
3463 char_count++;
3464 value /= 16;
3465 } while(value);
3466 NPT_Result result = m_Stream.WriteFully(c, char_count);
3467 if (NPT_FAILED(result)) return result;
3468
3469 // write the chunk data
3470 result = m_Stream.WriteFully(buffer, bytes_to_write);
3471 if (NPT_FAILED(result)) return result;
3472
3473 // finish the chunk
3474 result = m_Stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
3475 if (NPT_SUCCEEDED(result) && bytes_written) {
3476 *bytes_written = bytes_to_write;
3477 }
3478 return result;
3479 }
3480