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