Merge pull request #25959 from neo1973/TagLib_deprecation_warnings
[xbmc.git] / lib / libUPnP / Neptune / Source / Core / NptHttp.cpp
blobdb2c50759a3cb70b6cade5d1020a3365861b1b99
1 /*****************************************************************
3 | Neptune - HTTP Protocol
5 | Copyright (c) 2002-2008, Axiomatic Systems, LLC.
6 | All rights reserved.
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.
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.
30 ****************************************************************/
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"
47 /*----------------------------------------------------------------------
48 | logging
49 +---------------------------------------------------------------------*/
50 NPT_SET_LOCAL_LOGGER("neptune.http")
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>";
59 /*----------------------------------------------------------------------
60 | NPT_HttpUrl::NPT_HttpUrl
61 +---------------------------------------------------------------------*/
62 NPT_HttpUrl::NPT_HttpUrl(const char* url, bool ignore_scheme) :
63 NPT_Url(url)
65 if (!ignore_scheme) {
66 if (GetSchemeId() != NPT_Uri::SCHEME_ID_HTTP &&
67 GetSchemeId() != NPT_Uri::SCHEME_ID_HTTPS) {
68 Reset();
73 /*----------------------------------------------------------------------
74 | NPT_HttpUrl::NPT_HttpUrl
75 +---------------------------------------------------------------------*/
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)
85 /*----------------------------------------------------------------------
86 | NPT_HttpUrl::ToString
87 +---------------------------------------------------------------------*/
88 NPT_String
89 NPT_HttpUrl::ToString(bool with_fragment) const
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;
97 return NPT_Url::ToStringWithDefaultPort(default_port, with_fragment);
100 /*----------------------------------------------------------------------
101 | NPT_HttpHeader::NPT_HttpHeader
102 +---------------------------------------------------------------------*/
103 NPT_HttpHeader::NPT_HttpHeader(const char* name, const char* value):
104 m_Name(name),
105 m_Value(value)
109 /*----------------------------------------------------------------------
110 | NPT_HttpHeader::~NPT_HttpHeader
111 +---------------------------------------------------------------------*/
112 NPT_HttpHeader::~NPT_HttpHeader()
116 /*----------------------------------------------------------------------
117 | NPT_HttpHeader::Emit
118 +---------------------------------------------------------------------*/
119 NPT_Result
120 NPT_HttpHeader::Emit(NPT_OutputStream& stream) const
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());
128 return NPT_SUCCESS;
131 /*----------------------------------------------------------------------
132 | NPT_HttpHeader::SetName
133 +---------------------------------------------------------------------*/
134 NPT_Result
135 NPT_HttpHeader::SetName(const char* name)
137 m_Name = name;
138 return NPT_SUCCESS;
141 /*----------------------------------------------------------------------
142 | NPT_HttpHeader::~NPT_HttpHeader
143 +---------------------------------------------------------------------*/
144 NPT_Result
145 NPT_HttpHeader::SetValue(const char* value)
147 m_Value = value;
148 return NPT_SUCCESS;
151 /*----------------------------------------------------------------------
152 | NPT_HttpHeaders::NPT_HttpHeaders
153 +---------------------------------------------------------------------*/
154 NPT_HttpHeaders::NPT_HttpHeaders()
158 /*----------------------------------------------------------------------
159 | NPT_HttpHeaders::~NPT_HttpHeaders
160 +---------------------------------------------------------------------*/
161 NPT_HttpHeaders::~NPT_HttpHeaders()
163 m_Headers.Apply(NPT_ObjectDeleter<NPT_HttpHeader>());
166 /*----------------------------------------------------------------------
167 | NPT_HttpHeaders::Parse
168 +---------------------------------------------------------------------*/
169 NPT_Result
170 NPT_HttpHeaders::Parse(NPT_BufferedInputStream& stream)
172 NPT_String header_name;
173 NPT_String header_value;
174 bool header_pending = false;
175 NPT_String line;
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;
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());
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;
202 header_name = line.Left(colon_index);
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++;
209 header_value = value;
211 // the header is pending
212 header_pending = true;
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());
225 return NPT_SUCCESS;
228 /*----------------------------------------------------------------------
229 | NPT_HttpHeaders::Emit
230 +---------------------------------------------------------------------*/
231 NPT_Result
232 NPT_HttpHeaders::Emit(NPT_OutputStream& stream) const
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;
241 return NPT_SUCCESS;
244 /*----------------------------------------------------------------------
245 | NPT_HttpHeaders::GetHeader
246 +---------------------------------------------------------------------*/
247 NPT_HttpHeader*
248 NPT_HttpHeaders::GetHeader(const char* name) const
250 // check args
251 if (name == NULL) return NULL;
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;
259 ++header;
262 // not found
263 return NULL;
266 /*----------------------------------------------------------------------
267 | NPT_HttpHeaders::AddHeader
268 +---------------------------------------------------------------------*/
269 NPT_Result
270 NPT_HttpHeaders::AddHeader(const char* name, const char* value)
272 return m_Headers.Add(new NPT_HttpHeader(name, value));
275 /*----------------------------------------------------------------------
276 | NPT_HttpHeaders::RemoveHeader
277 +---------------------------------------------------------------------*/
278 NPT_Result
279 NPT_HttpHeaders::RemoveHeader(const char* name)
281 bool found = false;
283 NPT_HttpHeader* header = NULL;
284 while ((header = GetHeader(name))) {
285 m_Headers.Remove(header);
286 delete header;
287 found = true;
289 return found?NPT_SUCCESS:NPT_ERROR_NO_SUCH_ITEM;
292 /*----------------------------------------------------------------------
293 | NPT_HttpHeaders::SetHeader
294 +---------------------------------------------------------------------*/
295 NPT_Result
296 NPT_HttpHeaders::SetHeader(const char* name, const char* value, bool replace)
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;
308 /*----------------------------------------------------------------------
309 | NPT_HttpHeaders::GetHeaderValue
310 +---------------------------------------------------------------------*/
311 const NPT_String*
312 NPT_HttpHeaders::GetHeaderValue(const char* name) const
314 NPT_HttpHeader* header = GetHeader(name);
315 if (header == NULL) {
316 return NULL;
317 } else {
318 return &header->GetValue();
322 /*----------------------------------------------------------------------
323 | NPT_HttpEntityBodyInputStream
324 +---------------------------------------------------------------------*/
325 class NPT_HttpEntityBodyInputStream : public NPT_InputStream
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 ~NPT_HttpEntityBodyInputStream() override;
337 // methods
338 bool SizeIsKnown() { return m_SizeIsKnown; }
340 // NPT_InputStream methods
341 NPT_Result Read(void* buffer,
342 NPT_Size bytes_to_read,
343 NPT_Size* bytes_read = NULL) override;
344 NPT_Result Seek(NPT_Position /*offset*/) override {
345 return NPT_ERROR_NOT_SUPPORTED;
347 NPT_Result Tell(NPT_Position& offset) override {
348 offset = m_Position;
349 return NPT_SUCCESS;
351 NPT_Result GetSize(NPT_LargeSize& size) override {
352 size = m_Size;
353 return NPT_SUCCESS;
355 NPT_Result GetAvailable(NPT_LargeSize& available) override;
357 private:
358 // methods
359 virtual void OnFullyRead();
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;
371 /*----------------------------------------------------------------------
372 | NPT_HttpEntityBodyInputStream::NPT_HttpEntityBodyInputStream
373 +---------------------------------------------------------------------*/
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)
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;
399 /*----------------------------------------------------------------------
400 | NPT_HttpEntityBodyInputStream::~NPT_HttpEntityBodyInputStream
401 +---------------------------------------------------------------------*/
402 NPT_HttpEntityBodyInputStream::~NPT_HttpEntityBodyInputStream()
404 delete m_Connection;
407 /*----------------------------------------------------------------------
408 | NPT_HttpEntityBodyInputStream::OnFullyRead
409 +---------------------------------------------------------------------*/
410 void
411 NPT_HttpEntityBodyInputStream::OnFullyRead()
413 m_Source = NULL;
414 if (m_Connection && m_ShouldPersist) {
415 m_Connection->Recycle();
416 m_Connection = NULL;
420 /*----------------------------------------------------------------------
421 | NPT_HttpEntityBodyInputStream::Read
422 +---------------------------------------------------------------------*/
423 NPT_Result
424 NPT_HttpEntityBodyInputStream::Read(void* buffer,
425 NPT_Size bytes_to_read,
426 NPT_Size* bytes_read)
428 if (bytes_read) *bytes_read = 0;
430 // return now if we've already reached the end
431 if (m_Source.IsNull()) return NPT_ERROR_EOS;
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;
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;
448 // check if we've reached the end
449 if (result == NPT_ERROR_EOS || (m_SizeIsKnown && (m_Position == m_Size))) {
450 OnFullyRead();
453 return result;
456 /*----------------------------------------------------------------------
457 | NPT_HttpEntityBodyInputStream::GetAvaialble
458 +---------------------------------------------------------------------*/
459 NPT_Result
460 NPT_HttpEntityBodyInputStream::GetAvailable(NPT_LargeSize& available)
462 if (m_Source.IsNull()) {
463 available = 0;
464 return NPT_SUCCESS;
466 NPT_Result result = m_Source->GetAvailable(available);
467 if (NPT_FAILED(result)) {
468 available = 0;
469 return result;
471 if (available > m_Size-m_Position) {
472 available = m_Size-m_Position;
474 return NPT_SUCCESS;
477 /*----------------------------------------------------------------------
478 | NPT_HttpEntity::NPT_HttpEntity
479 +---------------------------------------------------------------------*/
480 NPT_HttpEntity::NPT_HttpEntity() :
481 m_ContentLength(0),
482 m_ContentLengthIsKnown(false)
486 /*----------------------------------------------------------------------
487 | NPT_HttpEntity::NPT_HttpEntity
488 +---------------------------------------------------------------------*/
489 NPT_HttpEntity::NPT_HttpEntity(const NPT_HttpHeaders& headers) :
490 m_ContentLength(0),
491 m_ContentLengthIsKnown(false)
493 SetHeaders(headers);
496 /*----------------------------------------------------------------------
497 | NPT_HttpEntity::SetHeaders
498 +---------------------------------------------------------------------*/
499 NPT_Result
500 NPT_HttpEntity::SetHeaders(const NPT_HttpHeaders& headers)
502 NPT_HttpHeader* header;
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;
516 // Content-Type
517 header = headers.GetHeader(NPT_HTTP_HEADER_CONTENT_TYPE);
518 if (header != NULL) {
519 m_ContentType = header->GetValue();
522 // Content-Encoding
523 header = headers.GetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING);
524 if (header != NULL) {
525 m_ContentEncoding = header->GetValue();
528 // Transfer-Encoding
529 header = headers.GetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING);
530 if (header != NULL) {
531 m_TransferEncoding = header->GetValue();
534 return NPT_SUCCESS;
537 /*----------------------------------------------------------------------
538 | NPT_HttpEntity::~NPT_HttpEntity
539 +---------------------------------------------------------------------*/
540 NPT_HttpEntity::~NPT_HttpEntity()
544 /*----------------------------------------------------------------------
545 | NPT_HttpEntity::GetInputStream
546 +---------------------------------------------------------------------*/
547 NPT_Result
548 NPT_HttpEntity::GetInputStream(NPT_InputStreamReference& stream)
550 // reset output params first
551 stream = NULL;
553 if (m_InputStream.IsNull()) return NPT_FAILURE;
555 stream = m_InputStream;
556 return NPT_SUCCESS;
559 /*----------------------------------------------------------------------
560 | NPT_HttpEntity::SetInputStream
561 +---------------------------------------------------------------------*/
562 NPT_Result
563 NPT_HttpEntity::SetInputStream(const NPT_InputStreamReference& stream,
564 bool update_content_length /* = false */)
566 m_InputStream = stream;
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);
576 return NPT_SUCCESS;
579 /*----------------------------------------------------------------------
580 | NPT_HttpEntity::SetInputStream
581 +---------------------------------------------------------------------*/
582 NPT_Result
583 NPT_HttpEntity::SetInputStream(const void* data, NPT_Size data_size)
585 NPT_MemoryStream* memory_stream = new NPT_MemoryStream(data, data_size);
586 NPT_InputStreamReference body(memory_stream);
587 return SetInputStream(body, true);
590 /*----------------------------------------------------------------------
591 | NPT_HttpEntity::SetInputStream
592 +---------------------------------------------------------------------*/
593 NPT_Result
594 NPT_HttpEntity::SetInputStream(const char* string)
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);
603 /*----------------------------------------------------------------------
604 | NPT_HttpEntity::SetInputStream
605 +---------------------------------------------------------------------*/
606 NPT_Result
607 NPT_HttpEntity::SetInputStream(const NPT_String& string)
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);
615 /*----------------------------------------------------------------------
616 | NPT_HttpEntity::Load
617 +---------------------------------------------------------------------*/
618 NPT_Result
619 NPT_HttpEntity::Load(NPT_DataBuffer& buffer)
621 // check that we have an input stream
622 if (m_InputStream.IsNull()) return NPT_ERROR_INVALID_STATE;
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);
629 /*----------------------------------------------------------------------
630 | NPT_HttpEntity::SetContentLength
631 +---------------------------------------------------------------------*/
632 NPT_Result
633 NPT_HttpEntity::SetContentLength(NPT_LargeSize length)
635 m_ContentLength = length;
636 m_ContentLengthIsKnown = true;
637 return NPT_SUCCESS;
640 /*----------------------------------------------------------------------
641 | NPT_HttpEntity::SetContentType
642 +---------------------------------------------------------------------*/
643 NPT_Result
644 NPT_HttpEntity::SetContentType(const char* type)
646 m_ContentType = type;
647 return NPT_SUCCESS;
650 /*----------------------------------------------------------------------
651 | NPT_HttpEntity::SetContentEncoding
652 +---------------------------------------------------------------------*/
653 NPT_Result
654 NPT_HttpEntity::SetContentEncoding(const char* encoding)
656 m_ContentEncoding = encoding;
657 return NPT_SUCCESS;
660 /*----------------------------------------------------------------------
661 | NPT_HttpEntity::SetTransferEncoding
662 +---------------------------------------------------------------------*/
663 NPT_Result
664 NPT_HttpEntity::SetTransferEncoding(const char* encoding)
666 m_TransferEncoding = encoding;
667 return NPT_SUCCESS;
670 /*----------------------------------------------------------------------
671 | NPT_HttpMessage::NPT_HttpMessage
672 +---------------------------------------------------------------------*/
673 NPT_HttpMessage::NPT_HttpMessage(const char* protocol) :
674 m_Protocol(protocol),
675 m_Entity(NULL)
679 /*----------------------------------------------------------------------
680 | NPT_HttpMessage::NPT_HttpMessage
681 +---------------------------------------------------------------------*/
682 NPT_HttpMessage::~NPT_HttpMessage()
684 delete m_Entity;
687 /*----------------------------------------------------------------------
688 | NPT_HttpMessage::SetEntity
689 +---------------------------------------------------------------------*/
690 NPT_Result
691 NPT_HttpMessage::SetEntity(NPT_HttpEntity* entity)
693 if (entity != m_Entity) {
694 delete m_Entity;
695 m_Entity = entity;
698 return NPT_SUCCESS;
701 /*----------------------------------------------------------------------
702 | NPT_HttpMessage::ParseHeaders
703 +---------------------------------------------------------------------*/
704 NPT_Result
705 NPT_HttpMessage::ParseHeaders(NPT_BufferedInputStream& stream)
707 return m_Headers.Parse(stream);
710 /*----------------------------------------------------------------------
711 | NPT_HttpRequest::NPT_HttpRequest
712 +---------------------------------------------------------------------*/
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)
722 /*----------------------------------------------------------------------
723 | NPT_HttpRequest::NPT_HttpRequest
724 +---------------------------------------------------------------------*/
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)
734 /*----------------------------------------------------------------------
735 | NPT_HttpRequest::SetUrl
736 +---------------------------------------------------------------------*/
737 NPT_Result
738 NPT_HttpRequest::SetUrl(const char* url)
740 m_Url = url;
741 return NPT_SUCCESS;
744 /*----------------------------------------------------------------------
745 | NPT_HttpRequest::SetUrl
746 +---------------------------------------------------------------------*/
747 NPT_Result
748 NPT_HttpRequest::SetUrl(const NPT_HttpUrl& url)
750 m_Url = url;
751 return NPT_SUCCESS;
754 /*----------------------------------------------------------------------
755 | NPT_HttpRequest::Parse
756 +---------------------------------------------------------------------*/
757 NPT_Result
758 NPT_HttpRequest::Parse(NPT_BufferedInputStream& stream,
759 const NPT_SocketAddress* endpoint,
760 NPT_HttpRequest*& request)
762 // default return value
763 request = NULL;
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());
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);
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;
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;
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;
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);
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);
812 // parse headers
813 NPT_Result result = request->ParseHeaders(stream);
814 if (NPT_FAILED(result)) {
815 delete request;
816 request = NULL;
817 return result;
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);
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());
831 // host sometimes doesn't contain port
832 if (endpoint) {
833 request->m_Url.SetPort(endpoint->GetPort());
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");
846 return NPT_SUCCESS;
849 /*----------------------------------------------------------------------
850 | NPT_HttpRequest::~NPT_HttpRequest
851 +---------------------------------------------------------------------*/
852 NPT_HttpRequest::~NPT_HttpRequest()
856 /*----------------------------------------------------------------------
857 | NPT_HttpRequest::Emit
858 +---------------------------------------------------------------------*/
859 NPT_Result
860 NPT_HttpRequest::Emit(NPT_OutputStream& stream, bool use_proxy) const
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());
870 stream.WriteFully(" ", 1);
871 stream.WriteString(m_Protocol);
872 stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
874 // emit headers
875 m_Headers.Emit(stream);
877 // finish with an empty line
878 stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
880 return NPT_SUCCESS;
883 /*----------------------------------------------------------------------
884 | NPT_HttpResponse::NPT_HttpResponse
885 +---------------------------------------------------------------------*/
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)
895 /*----------------------------------------------------------------------
896 | NPT_HttpResponse::~NPT_HttpResponse
897 +---------------------------------------------------------------------*/
898 NPT_HttpResponse::~NPT_HttpResponse()
902 /*----------------------------------------------------------------------
903 | NPT_HttpResponse::SetStatus
904 +---------------------------------------------------------------------*/
905 NPT_Result
906 NPT_HttpResponse::SetStatus(NPT_HttpStatusCode status_code,
907 const char* reason_phrase,
908 const char* protocol)
910 m_StatusCode = status_code;
911 m_ReasonPhrase = reason_phrase;
912 if (protocol) m_Protocol = protocol;
913 return NPT_SUCCESS;
916 /*----------------------------------------------------------------------
917 | NPT_HttpResponse::SetProtocol
918 +---------------------------------------------------------------------*/
919 NPT_Result
920 NPT_HttpResponse::SetProtocol(const char* protocol)
922 m_Protocol = protocol;
923 return NPT_SUCCESS;
926 /*----------------------------------------------------------------------
927 | NPT_HttpResponse::Emit
928 +---------------------------------------------------------------------*/
929 NPT_Result
930 NPT_HttpResponse::Emit(NPT_OutputStream& stream) const
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);
940 // emit headers
941 m_Headers.Emit(stream);
943 // finish with an empty line
944 stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
946 return NPT_SUCCESS;
949 /*----------------------------------------------------------------------
950 | NPT_HttpResponse::Parse
951 +---------------------------------------------------------------------*/
952 NPT_Result
953 NPT_HttpResponse::Parse(NPT_BufferedInputStream& stream,
954 NPT_HttpResponse*& response)
956 // default return value
957 response = NULL;
959 // read the response line
960 NPT_String line;
961 NPT_CHECK_WARNING(stream.ReadLine(line, NPT_HTTP_PROTOCOL_MAX_LINE_LENGTH));
963 NPT_LOG_FINER_1("http response: %s", line.GetChars());
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;
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;
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));
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);
995 // parse headers
996 NPT_Result result = response->ParseHeaders(stream);
997 if (NPT_FAILED(result)) {
998 delete response;
999 response = NULL;
1002 return result;
1005 /*----------------------------------------------------------------------
1006 | NPT_HttpEnvProxySelector
1007 +---------------------------------------------------------------------*/
1008 class NPT_HttpEnvProxySelector : public NPT_HttpProxySelector,
1009 public NPT_AutomaticCleaner::Singleton
1011 public:
1012 static NPT_HttpEnvProxySelector* GetInstance();
1014 // NPT_HttpProxySelector methods
1015 NPT_Result GetProxyForUrl(const NPT_HttpUrl& url, NPT_HttpProxyAddress& proxy) override;
1017 private:
1018 // class variables
1019 static NPT_HttpEnvProxySelector* Instance;
1021 // class methods
1022 static void ParseProxyEnv(const NPT_String& env, NPT_HttpProxyAddress& proxy);
1024 // members
1025 NPT_HttpProxyAddress m_HttpProxy;
1026 NPT_HttpProxyAddress m_HttpsProxy;
1027 NPT_List<NPT_String> m_NoProxy;
1028 NPT_HttpProxyAddress m_AllProxy;
1030 NPT_HttpEnvProxySelector* NPT_HttpEnvProxySelector::Instance = NULL;
1032 /*----------------------------------------------------------------------
1033 | NPT_HttpEnvProxySelector::GetInstance
1034 +---------------------------------------------------------------------*/
1035 NPT_HttpEnvProxySelector*
1036 NPT_HttpEnvProxySelector::GetInstance()
1038 if (Instance) return Instance;
1040 NPT_SingletonLock::GetInstance().Lock();
1041 if (Instance == NULL) {
1042 // create the shared instance
1043 Instance = new NPT_HttpEnvProxySelector();
1045 // prepare for recycling
1046 NPT_AutomaticCleaner::GetInstance()->Register(Instance);
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());
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);
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());
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);
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());
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);
1075 if (no_proxy.GetLength()) {
1076 Instance->m_NoProxy = no_proxy.Split(",");
1079 NPT_SingletonLock::GetInstance().Unlock();
1081 return Instance;
1084 /*----------------------------------------------------------------------
1085 | NPT_HttpEnvProxySelector::ParseProxyEnv
1086 +---------------------------------------------------------------------*/
1087 void
1088 NPT_HttpEnvProxySelector::ParseProxyEnv(const NPT_String& env,
1089 NPT_HttpProxyAddress& proxy)
1091 // ignore empty strings
1092 if (env.GetLength() == 0) return;
1094 NPT_String proxy_spec;
1095 if (env.Find("://") >= 0) {
1096 proxy_spec = env;
1097 } else {
1098 proxy_spec = "http://"+env;
1100 NPT_Url url(proxy_spec);
1101 proxy.SetHostName(url.GetHost());
1102 proxy.SetPort(url.GetPort());
1105 /*----------------------------------------------------------------------
1106 | NPT_HttpEnvProxySelector::GetProxyForUrl
1107 +---------------------------------------------------------------------*/
1108 NPT_Result
1109 NPT_HttpEnvProxySelector::GetProxyForUrl(const NPT_HttpUrl& url,
1110 NPT_HttpProxyAddress& proxy)
1112 NPT_HttpProxyAddress* protocol_proxy = NULL;
1113 switch (url.GetSchemeId()) {
1114 case NPT_Uri::SCHEME_ID_HTTP:
1115 protocol_proxy = &m_HttpProxy;
1116 break;
1118 case NPT_Uri::SCHEME_ID_HTTPS:
1119 protocol_proxy = &m_HttpsProxy;
1120 break;
1122 default:
1123 return NPT_ERROR_HTTP_NO_PROXY;
1126 // check for no-proxy first
1127 if (m_NoProxy.GetItemCount()) {
1128 for (NPT_List<NPT_String>::Iterator i = m_NoProxy.GetFirstItem();
1130 ++i) {
1131 if ((*i) == "*") {
1132 return NPT_ERROR_HTTP_NO_PROXY;
1134 if (url.GetHost().EndsWith(*i, true)) {
1135 if (url.GetHost().GetLength() == (*i).GetLength()) {
1136 // exact match
1137 return NPT_ERROR_HTTP_NO_PROXY;
1139 if (url.GetHost().GetChars()[url.GetHost().GetLength()-(*i).GetLength()-1] == '.') {
1140 // subdomain match
1141 return NPT_ERROR_HTTP_NO_PROXY;
1147 // check the protocol proxy
1148 if (protocol_proxy->GetHostName().GetLength()) {
1149 proxy = *protocol_proxy;
1150 return NPT_SUCCESS;
1153 // use the default proxy
1154 proxy = m_AllProxy;
1156 return proxy.GetHostName().GetLength()?NPT_SUCCESS:NPT_ERROR_HTTP_NO_PROXY;
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*
1168 NPT_HttpProxySelector::GetDefault()
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;
1183 NPT_HttpProxySelector_ConfigChecked = true;
1186 switch (NPT_HttpProxySelector_Config) {
1187 case NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE:
1188 // no proxy
1189 return NULL;
1191 case NPT_HTTP_PROXY_SELECTOR_CONFIG_ENV:
1192 // use the shared instance
1193 return NPT_HttpEnvProxySelector::GetInstance();
1195 case NPT_HTTP_PROXY_SELECTOR_CONFIG_SYSTEM:
1196 // use the sytem proxy selector
1197 return GetSystemSelector();
1199 default:
1200 return NULL;
1204 /*----------------------------------------------------------------------
1205 | NPT_HttpProxySelector::GetSystemSelector
1206 +---------------------------------------------------------------------*/
1207 #if !defined(NPT_CONFIG_HAVE_SYSTEM_PROXY_SELECTOR)
1208 NPT_HttpProxySelector*
1209 NPT_HttpProxySelector::GetSystemSelector()
1211 return NULL;
1213 #endif
1215 /*----------------------------------------------------------------------
1216 | NPT_HttpStaticProxySelector
1217 +---------------------------------------------------------------------*/
1218 class NPT_HttpStaticProxySelector : public NPT_HttpProxySelector
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);
1227 // NPT_HttpProxySelector methods
1228 NPT_Result GetProxyForUrl(const NPT_HttpUrl& url, NPT_HttpProxyAddress& proxy) override;
1230 private:
1231 // members
1232 NPT_HttpProxyAddress m_HttpProxy;
1233 NPT_HttpProxyAddress m_HttpsProxy;
1236 /*----------------------------------------------------------------------
1237 | NPT_HttpStaticProxySelector::NPT_HttpStaticProxySelector
1238 +---------------------------------------------------------------------*/
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)
1248 /*----------------------------------------------------------------------
1249 | NPT_HttpStaticProxySelector::GetProxyForUrl
1250 +---------------------------------------------------------------------*/
1251 NPT_Result
1252 NPT_HttpStaticProxySelector::GetProxyForUrl(const NPT_HttpUrl& url,
1253 NPT_HttpProxyAddress& proxy)
1255 switch (url.GetSchemeId()) {
1256 case NPT_Uri::SCHEME_ID_HTTP:
1257 proxy = m_HttpProxy;
1258 break;
1260 case NPT_Uri::SCHEME_ID_HTTPS:
1261 proxy = m_HttpsProxy;
1262 break;
1264 default:
1265 return NPT_ERROR_HTTP_NO_PROXY;
1268 return NPT_SUCCESS;
1271 /*----------------------------------------------------------------------
1272 | NPT_HttpConnectionManager::NPT_HttpConnectionManager
1273 +---------------------------------------------------------------------*/
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)
1281 /*----------------------------------------------------------------------
1282 | NPT_HttpConnectionManager::~NPT_HttpConnectionManager
1283 +---------------------------------------------------------------------*/
1284 NPT_HttpConnectionManager::~NPT_HttpConnectionManager()
1286 // set abort flag and wait for thread to finish
1287 m_Aborted.SetValue(1);
1288 Wait();
1290 m_Connections.Apply(NPT_ObjectDeleter<Connection>());
1293 /*----------------------------------------------------------------------
1294 | NPT_HttpConnectionManager::GetInstance
1295 +---------------------------------------------------------------------*/
1296 NPT_HttpConnectionManager*
1297 NPT_HttpConnectionManager::GetInstance()
1299 if (Instance) return Instance;
1301 NPT_SingletonLock::GetInstance().Lock();
1302 if (Instance == NULL) {
1303 // create the shared instance
1304 Instance = new NPT_HttpConnectionManager();
1306 // register to for automatic cleanup
1307 NPT_AutomaticCleaner::GetInstance()->RegisterHttpConnectionManager(Instance);
1309 // Start shared instance
1310 Instance->Start();
1312 NPT_SingletonLock::GetInstance().Unlock();
1314 return Instance;
1316 NPT_HttpConnectionManager* NPT_HttpConnectionManager::Instance = NULL;
1318 /*----------------------------------------------------------------------
1319 | NPT_HttpConnectionManager::Run
1320 +---------------------------------------------------------------------*/
1321 void
1322 NPT_HttpConnectionManager::Run()
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();
1331 /*----------------------------------------------------------------------
1332 | NPT_HttpConnectionManager::Cleanup
1333 +---------------------------------------------------------------------*/
1334 NPT_Result
1335 NPT_HttpConnectionManager::Cleanup()
1337 NPT_TimeStamp now;
1338 NPT_System::GetCurrentTimeStamp(now);
1339 NPT_TimeStamp delta((float)m_MaxConnectionAge);
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();
1349 return NPT_SUCCESS;
1352 /*----------------------------------------------------------------------
1353 | NPT_HttpConnectionManager::FindConnection
1354 +---------------------------------------------------------------------*/
1355 NPT_HttpConnectionManager::Connection*
1356 NPT_HttpConnectionManager::FindConnection(NPT_SocketAddress& address)
1358 NPT_AutoLock lock(m_Lock);
1359 Cleanup();
1361 for (NPT_List<Connection*>::Iterator i = m_Connections.GetFirstItem();
1363 ++i) {
1364 Connection* connection = *i;
1366 NPT_SocketInfo info;
1367 if (NPT_FAILED(connection->GetInfo(info))) continue;
1369 if (info.remote_address == address) {
1370 m_Connections.Erase(i);
1371 return connection;
1375 // not found
1376 return NULL;
1379 /*----------------------------------------------------------------------
1380 | NPT_HttpConnectionManager::Track
1381 +---------------------------------------------------------------------*/
1382 NPT_Result
1383 NPT_HttpConnectionManager::Track(NPT_HttpClient* client, NPT_HttpClient::Connection* connection)
1385 NPT_AutoLock lock(m_Lock);
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;
1395 connections->Add(connection);
1396 return NPT_SUCCESS;
1399 // new client connections
1400 ConnectionList new_connections;
1402 // add connection to new client connection list
1403 new_connections.Add(connection);
1405 // track new client connections
1406 m_ClientConnections.Put(client, new_connections);
1407 return NPT_SUCCESS;
1410 /*----------------------------------------------------------------------
1411 | NPT_HttpConnectionManager::UntrackConnection
1412 +---------------------------------------------------------------------*/
1413 NPT_Result
1414 NPT_HttpConnectionManager::UntrackConnection(NPT_HttpClient::Connection* connection)
1416 NPT_AutoLock lock(m_Lock);
1418 if (!connection) {
1419 return NPT_ERROR_INVALID_PARAMETERS;
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();
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);
1436 // untrack client if no more active connections for it
1437 if (connections.GetItemCount() == 0) {
1438 m_ClientConnections.Erase(client);
1441 return NPT_SUCCESS;
1443 ++entry;
1446 return NPT_ERROR_NO_SUCH_ITEM;
1449 /*----------------------------------------------------------------------
1450 | NPT_HttpConnectionManager::Untrack
1451 +---------------------------------------------------------------------*/
1452 NPT_Result
1453 NPT_HttpConnectionManager::Untrack(NPT_HttpClient::Connection* connection)
1455 // check first if ConnectionCanceller Instance has not been released already
1456 // with static finalizers
1457 if (Instance == NULL) return NPT_FAILURE;
1459 return GetInstance()->UntrackConnection(connection);
1462 /*----------------------------------------------------------------------
1463 | NPT_HttpConnectionManager::Recycle
1464 +---------------------------------------------------------------------*/
1465 NPT_Result
1466 NPT_HttpConnectionManager::Recycle(NPT_HttpConnectionManager::Connection* connection)
1468 // Untrack connection
1469 UntrackConnection(connection);
1472 NPT_AutoLock lock(m_Lock);
1473 Cleanup();
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");
1484 if (connection) {
1486 // label this connection with the current timestamp and flag
1487 NPT_System::GetCurrentTimeStamp(connection->m_TimeStamp);
1488 connection->m_IsRecycled = true;
1490 // add the connection to the pool
1491 m_Connections.Add(connection);
1495 return NPT_SUCCESS;
1498 /*----------------------------------------------------------------------
1499 | NPT_HttpConnectionManager::AbortConnections
1500 +---------------------------------------------------------------------*/
1501 NPT_Result
1502 NPT_HttpConnectionManager::AbortConnections(NPT_HttpClient* client)
1504 NPT_AutoLock lock(m_Lock);
1506 ConnectionList* connections = NULL;
1507 if (NPT_SUCCEEDED(m_ClientConnections.Get(client, connections))) {
1508 for (NPT_List<NPT_HttpClient::Connection*>::Iterator i = connections->GetFirstItem();
1510 ++i) {
1511 (*i)->Abort();
1514 return NPT_SUCCESS;
1517 /*----------------------------------------------------------------------
1518 | NPT_HttpConnectionManager::Connection::Connection
1519 +---------------------------------------------------------------------*/
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)
1532 /*----------------------------------------------------------------------
1533 | NPT_HttpConnectionManager::Connection::~Connection
1534 +---------------------------------------------------------------------*/
1535 NPT_HttpConnectionManager::Connection::~Connection()
1537 NPT_HttpConnectionManager::Untrack(this);
1540 /*----------------------------------------------------------------------
1541 | NPT_HttpConnectionManager::Connection::Recycle
1542 +---------------------------------------------------------------------*/
1543 NPT_Result
1544 NPT_HttpConnectionManager::Connection::Recycle()
1546 return m_Manager.Recycle(this);
1549 /*----------------------------------------------------------------------
1550 | NPT_HttpClient::NPT_HttpClient
1551 +---------------------------------------------------------------------*/
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)
1559 if (connector == NULL) {
1560 m_Connector = new NPT_HttpTlsConnector();
1561 m_ConnectorIsOwned = true;
1565 /*----------------------------------------------------------------------
1566 | NPT_HttpClient::~NPT_HttpClient
1567 +---------------------------------------------------------------------*/
1568 NPT_HttpClient::~NPT_HttpClient()
1570 if (m_ProxySelectorIsOwned) {
1571 delete m_ProxySelector;
1573 if (m_ConnectorIsOwned) {
1574 delete m_Connector;
1578 /*----------------------------------------------------------------------
1579 | NPT_HttpClient::SetConfig
1580 +---------------------------------------------------------------------*/
1581 NPT_Result
1582 NPT_HttpClient::SetConfig(const Config& config)
1584 m_Config = config;
1586 return NPT_SUCCESS;
1589 /*----------------------------------------------------------------------
1590 | NPT_HttpClient::SetProxy
1591 +---------------------------------------------------------------------*/
1592 NPT_Result
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)
1598 if (m_ProxySelectorIsOwned) {
1599 delete m_ProxySelector;
1600 m_ProxySelector = NULL;
1601 m_ProxySelectorIsOwned = false;
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;
1611 return NPT_SUCCESS;
1614 /*----------------------------------------------------------------------
1615 | NPT_HttpClient::SetProxySelector
1616 +---------------------------------------------------------------------*/
1617 NPT_Result
1618 NPT_HttpClient::SetProxySelector(NPT_HttpProxySelector* selector)
1620 if (m_ProxySelectorIsOwned && m_ProxySelector != selector) {
1621 delete m_ProxySelector;
1623 m_ProxySelector = selector;
1624 m_ProxySelectorIsOwned = false;
1626 return NPT_SUCCESS;
1629 /*----------------------------------------------------------------------
1630 | NPT_HttpClient::SetConnector
1631 +---------------------------------------------------------------------*/
1632 NPT_Result
1633 NPT_HttpClient::SetConnector(Connector* connector)
1635 if (m_ConnectorIsOwned && m_Connector != connector) {
1636 delete m_Connector;
1638 m_Connector = connector;
1639 m_ConnectorIsOwned = false;
1641 return NPT_SUCCESS;
1644 /*----------------------------------------------------------------------
1645 | NPT_HttpClient::SetTimeouts
1646 +---------------------------------------------------------------------*/
1647 NPT_Result
1648 NPT_HttpClient::SetTimeouts(NPT_Timeout connection_timeout,
1649 NPT_Timeout io_timeout,
1650 NPT_Timeout name_resolver_timeout)
1652 m_Config.m_ConnectionTimeout = connection_timeout;
1653 m_Config.m_IoTimeout = io_timeout;
1654 m_Config.m_NameResolverTimeout = name_resolver_timeout;
1656 return NPT_SUCCESS;
1659 /*----------------------------------------------------------------------
1660 | NPT_HttpClient::SetUserAgent
1661 +---------------------------------------------------------------------*/
1662 NPT_Result
1663 NPT_HttpClient::SetUserAgent(const char* user_agent)
1665 m_Config.m_UserAgent = user_agent;
1666 return NPT_SUCCESS;
1669 /*----------------------------------------------------------------------
1670 | NPT_HttpClient::TrackConnection
1671 +---------------------------------------------------------------------*/
1672 NPT_Result
1673 NPT_HttpClient::TrackConnection(Connection* connection)
1675 NPT_AutoLock lock(m_AbortLock);
1676 if (m_Aborted) return NPT_ERROR_CANCELLED;
1677 return NPT_HttpConnectionManager::GetInstance()->Track(this, connection);
1680 /*----------------------------------------------------------------------
1681 | NPT_HttpClient::SendRequestOnce
1682 +---------------------------------------------------------------------*/
1683 NPT_Result
1684 NPT_HttpClient::SendRequestOnce(NPT_HttpRequest& request,
1685 NPT_HttpResponse*& response,
1686 NPT_HttpRequestContext* context /* = NULL */)
1688 // setup default values
1689 NPT_Result result = NPT_SUCCESS;
1690 response = NULL;
1692 NPT_LOG_FINE_1("requesting URL %s", request.GetUrl().ToString().GetChars());
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;
1704 use_proxy = !proxy.GetHostName().IsEmpty();
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;
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");
1727 NPT_InputStreamReference input_stream = connection->GetInputStream();
1728 NPT_OutputStreamReference output_stream = connection->GetOutputStream();
1730 cref = connection;
1731 reconnect = connection->IsRecycled();
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);
1741 NPT_HttpEntity* entity = request.GetEntity();
1742 NPT_InputStreamReference body_stream;
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;
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;
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;
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
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);
1780 continue;
1781 } else {
1782 return result;
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);
1807 continue;
1808 } else {
1809 // don't retry
1810 return result;
1813 break;
1814 } while (reconnect && --watchdog && !m_Aborted);
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;
1822 return result;
1825 /*----------------------------------------------------------------------
1826 | NPT_HttpClient::WriteRequest
1827 +---------------------------------------------------------------------*/
1828 NPT_Result
1829 NPT_HttpClient::WriteRequest(NPT_OutputStream& output_stream,
1830 NPT_HttpRequest& request,
1831 bool should_persist,
1832 bool use_proxy /* = false */)
1834 NPT_Result result = NPT_SUCCESS;
1836 // add any headers that may be missing
1837 NPT_HttpHeaders& headers = request.GetHeaders();
1839 if (!should_persist) {
1840 headers.SetHeader(NPT_HTTP_HEADER_CONNECTION, "close", false); // set but don't replace
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;
1850 if (request.GetUrl().GetPort() != default_port) {
1851 host += ":";
1852 host += NPT_String::FromInteger(request.GetUrl().GetPort());
1854 headers.SetHeader(NPT_HTTP_HEADER_HOST, host, false); // set but don't replace
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()));
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);
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);
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);
1885 // create a memory stream to buffer the headers
1886 NPT_MemoryStream header_stream;
1888 // emit the request headers into the header buffer
1889 request.Emit(header_stream, use_proxy && request.GetUrl().GetSchemeId()==NPT_Url::SCHEME_ID_HTTP);
1891 // send the headers
1892 NPT_CHECK_WARNING(output_stream.WriteFully(header_stream.GetData(), header_stream.GetDataSize()));
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);
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;
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));
1914 // flush to write out any buffered data left in chunked output if used
1915 dest->Flush();
1917 // cleanup (this will send zero size chunk followed by CRLF)
1918 if (dest != &output_stream) delete dest;
1921 // flush the output stream so that everything is sent to the server
1922 output_stream.Flush();
1924 return result;
1927 /*----------------------------------------------------------------------
1928 | NPT_HttpClient::ReadResponse
1929 +---------------------------------------------------------------------*/
1930 NPT_Result
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 */)
1937 NPT_Result result;
1939 // setup default values
1940 response = NULL;
1942 // create a buffered stream for this socket stream
1943 NPT_BufferedInputStreamReference buffered_input_stream(new NPT_BufferedInputStream(input_stream));
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);
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;
1957 NPT_LOG_FINER_2("got response, code=%d, msg=%s",
1958 response->GetStatusCode(),
1959 response->GetReasonPhrase().GetChars());
1960 break;
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;
1969 // unbuffer the stream
1970 buffered_input_stream->SetBufferSize(0);
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;
1979 } else {
1980 if (!connection_header || (*connection_header != "keep-alive")) {
1981 should_persist = false;
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());
1990 // check if the content length is known
1991 bool have_content_length = (response->GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_CONTENT_LENGTH) != NULL);
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);
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
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();
2028 return NPT_SUCCESS;
2031 /*----------------------------------------------------------------------
2032 | NPT_HttpClient::SendRequest
2033 +---------------------------------------------------------------------*/
2034 NPT_Result
2035 NPT_HttpClient::SendRequest(NPT_HttpRequest& request,
2036 NPT_HttpResponse*& response,
2037 NPT_HttpRequestContext* context /* = NULL */)
2039 NPT_Cardinal watchdog = m_Config.m_MaxRedirects+1;
2040 bool keep_going;
2041 NPT_Result result;
2043 // reset aborted flag
2044 m_Aborted = false;
2046 // default value
2047 response = NULL;
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;
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 = "/";
2086 redirect_path += *location;
2087 NPT_LOG_FINE_1("redirecting to absolute path %s", redirect_path.GetChars());
2088 request.GetUrl().ParsePathPlus(redirect_path);
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);
2097 keep_going = true;
2098 delete response;
2099 response = NULL;
2102 } while (keep_going && --watchdog && !m_Aborted);
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;
2110 return result;
2113 /*----------------------------------------------------------------------
2114 | NPT_HttpClient::Abort
2115 +---------------------------------------------------------------------*/
2116 NPT_Result
2117 NPT_HttpClient::Abort()
2119 NPT_AutoLock lock(m_AbortLock);
2120 m_Aborted = true;
2122 NPT_HttpConnectionManager::GetInstance()->AbortConnections(this);
2123 return NPT_SUCCESS;
2126 /*----------------------------------------------------------------------
2127 | NPT_HttpRequestContext::NPT_HttpRequestContext
2128 +---------------------------------------------------------------------*/
2129 NPT_HttpRequestContext::NPT_HttpRequestContext(const NPT_SocketAddress* local_address,
2130 const NPT_SocketAddress* remote_address)
2132 if (local_address) m_LocalAddress = *local_address;
2133 if (remote_address) m_RemoteAddress = *remote_address;
2136 /*----------------------------------------------------------------------
2137 | NPT_HttpServer::NPT_HttpServer
2138 +---------------------------------------------------------------------*/
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)
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;
2152 /*----------------------------------------------------------------------
2153 | NPT_HttpServer::NPT_HttpServer
2154 +---------------------------------------------------------------------*/
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)
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;
2170 /*----------------------------------------------------------------------
2171 | NPT_HttpServer::~NPT_HttpServer
2172 +---------------------------------------------------------------------*/
2173 NPT_HttpServer::~NPT_HttpServer()
2175 m_RequestHandlers.Apply(NPT_ObjectDeleter<HandlerConfig>());
2178 /*----------------------------------------------------------------------
2179 | NPT_HttpServer::Bind
2180 +---------------------------------------------------------------------*/
2181 NPT_Result
2182 NPT_HttpServer::Bind()
2184 // check if we're already bound
2185 if (m_BoundPort != 0) return NPT_SUCCESS;
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;
2193 // update the bound port info
2194 NPT_SocketInfo info;
2195 m_Socket.GetInfo(info);
2196 m_BoundPort = info.local_address.GetPort();
2198 return NPT_SUCCESS;
2201 /*----------------------------------------------------------------------
2202 | NPT_HttpServer::SetConfig
2203 +---------------------------------------------------------------------*/
2204 NPT_Result
2205 NPT_HttpServer::SetConfig(const Config& config)
2207 m_Config = config;
2209 // check that we can bind to this listen port
2210 return Bind();
2213 /*----------------------------------------------------------------------
2214 | NPT_HttpServer::SetListenPort
2215 +---------------------------------------------------------------------*/
2216 NPT_Result
2217 NPT_HttpServer::SetListenPort(NPT_UInt16 port, bool reuse_address)
2219 m_Config.m_ListenPort = port;
2220 m_Config.m_ReuseAddress = reuse_address;
2221 return Bind();
2224 /*----------------------------------------------------------------------
2225 | NPT_HttpServer::SetTimeouts
2226 +---------------------------------------------------------------------*/
2227 NPT_Result
2228 NPT_HttpServer::SetTimeouts(NPT_Timeout connection_timeout,
2229 NPT_Timeout io_timeout)
2231 m_Config.m_ConnectionTimeout = connection_timeout;
2232 m_Config.m_IoTimeout = io_timeout;
2234 return NPT_SUCCESS;
2237 /*----------------------------------------------------------------------
2238 | NPT_HttpServer::SetServerHeader
2239 +---------------------------------------------------------------------*/
2240 NPT_Result
2241 NPT_HttpServer::SetServerHeader(const char* server_header)
2243 m_ServerHeader = server_header;
2244 return NPT_SUCCESS;
2247 /*----------------------------------------------------------------------
2248 | NPT_HttpServer::Abort
2249 +---------------------------------------------------------------------*/
2250 NPT_Result
2251 NPT_HttpServer::Abort()
2253 m_Socket.Cancel();
2254 return NPT_SUCCESS;
2257 /*----------------------------------------------------------------------
2258 | NPT_HttpServer::WaitForNewClient
2259 +---------------------------------------------------------------------*/
2260 NPT_Result
2261 NPT_HttpServer::WaitForNewClient(NPT_InputStreamReference& input,
2262 NPT_OutputStreamReference& output,
2263 NPT_HttpRequestContext* context,
2264 NPT_Flags socket_flags)
2266 // ensure that we're bound
2267 NPT_CHECK_FINE(Bind());
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);
2280 if (client == NULL) return NPT_ERROR_INTERNAL;
2282 // get the client info
2283 if (context) {
2284 NPT_SocketInfo client_info;
2285 client->GetInfo(client_info);
2287 context->SetLocalAddress(client_info.local_address);
2288 context->SetRemoteAddress(client_info.remote_address);
2290 NPT_LOG_FINE_2("client connected (%s <- %s)",
2291 client_info.local_address.ToString().GetChars(),
2292 client_info.remote_address.ToString().GetChars());
2295 // configure the socket
2296 client->SetReadTimeout(m_Config.m_IoTimeout);
2297 client->SetWriteTimeout(m_Config.m_IoTimeout);
2299 // get the streams
2300 client->GetInputStream(input);
2301 client->GetOutputStream(output);
2303 // we don't need the socket anymore
2304 delete client;
2306 return NPT_SUCCESS;
2309 /*----------------------------------------------------------------------
2310 | NPT_HttpServer::Loop
2311 +---------------------------------------------------------------------*/
2312 NPT_Result
2313 NPT_HttpServer::Loop(bool cancellable_sockets)
2315 NPT_InputStreamReference input;
2316 NPT_OutputStreamReference output;
2317 NPT_HttpRequestContext context;
2318 NPT_Result result;
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;
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);
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);
2353 return result;
2356 /*----------------------------------------------------------------------
2357 | NPT_HttpServer::HandlerConfig::HandlerConfig
2358 +---------------------------------------------------------------------*/
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)
2370 /*----------------------------------------------------------------------
2371 | NPT_HttpServer::HandlerConfig::~HandlerConfig
2372 +---------------------------------------------------------------------*/
2373 NPT_HttpServer::HandlerConfig::~HandlerConfig()
2375 if (m_HandlerIsOwned) delete m_Handler;
2378 /*----------------------------------------------------------------------
2379 | NPT_HttpServer::AddRequestHandler
2380 +---------------------------------------------------------------------*/
2381 NPT_Result
2382 NPT_HttpServer::AddRequestHandler(NPT_HttpRequestHandler* handler,
2383 const char* path,
2384 bool include_children,
2385 bool transfer_ownership)
2387 return m_RequestHandlers.Add(new HandlerConfig(handler, path, include_children, transfer_ownership));
2390 /*----------------------------------------------------------------------
2391 | NPT_HttpServer::FindRequestHandler
2392 +---------------------------------------------------------------------*/
2393 NPT_HttpRequestHandler*
2394 NPT_HttpServer::FindRequestHandler(NPT_HttpRequest& request)
2396 NPT_String path = NPT_Uri::PercentDecode(request.GetUrl().GetPath());
2397 for (NPT_List<HandlerConfig*>::Iterator it = m_RequestHandlers.GetFirstItem();
2399 ++it) {
2400 HandlerConfig* config = *it;
2401 if (config->m_IncludeChildren) {
2402 if (path.StartsWith(config->m_Path)) {
2403 return config->m_Handler;
2405 } else {
2406 if (path == config->m_Path) {
2407 return config->m_Handler;
2412 // not found
2413 return NULL;
2416 /*----------------------------------------------------------------------
2417 | NPT_HttpServer::FindRequestHandlers
2418 +---------------------------------------------------------------------*/
2419 NPT_List<NPT_HttpRequestHandler*>
2420 NPT_HttpServer::FindRequestHandlers(NPT_HttpRequest& request)
2422 NPT_List<NPT_HttpRequestHandler*> handlers;
2424 for (NPT_List<HandlerConfig*>::Iterator it = m_RequestHandlers.GetFirstItem();
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);
2432 } else {
2433 if (request.GetUrl().GetPath(true) == config->m_Path) {
2434 handlers.Insert(handlers.GetFirstItem(), config->m_Handler);
2439 return handlers;
2442 /*----------------------------------------------------------------------
2443 | NPT_HttpServer::RespondToClient
2444 +---------------------------------------------------------------------*/
2445 NPT_Result
2446 NPT_HttpServer::RespondToClient(NPT_InputStreamReference& input,
2447 NPT_OutputStreamReference& output,
2448 const NPT_HttpRequestContext& context)
2450 NPT_HttpRequest* request;
2451 NPT_HttpResponse* response = NULL;
2452 NPT_Result result = NPT_ERROR_NO_SUCH_ITEM;
2453 bool terminate_server = false;
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());
2459 // prepare the response body
2460 NPT_HttpEntity* body = new NPT_HttpEntity();
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);
2468 // ask the handler to setup the response
2469 result = handler->SetupResponse(*request, context, *response);
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");
2479 response->SetEntity(body);
2480 if (handler) {
2481 handler->Completed(NPT_ERROR_NO_SUCH_ITEM);
2482 handler = NULL;
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;
2501 // augment the headers with server information
2502 if (m_ServerHeader.GetLength()) {
2503 response->GetHeaders().SetHeader(NPT_HTTP_HEADER_SERVER, m_ServerHeader, false);
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;
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;
2531 // flush
2532 output->Flush();
2534 // if we need to die, we return an error code
2535 if (NPT_SUCCEEDED(result) && terminate_server) result = NPT_ERROR_TERMINATED;
2537 end:
2538 // cleanup
2539 delete response;
2540 delete request;
2542 if (handler) {
2543 handler->Completed(result);
2546 return result;
2549 /*----------------------------------------------------------------------
2550 | NPT_HttpResponder::NPT_HttpResponder
2551 +---------------------------------------------------------------------*/
2552 NPT_HttpResponder::NPT_HttpResponder(NPT_InputStreamReference& input,
2553 NPT_OutputStreamReference& output) :
2554 m_Input(new NPT_BufferedInputStream(input)),
2555 m_Output(output)
2557 m_Config.m_IoTimeout = NPT_HTTP_SERVER_DEFAULT_IO_TIMEOUT;
2560 /*----------------------------------------------------------------------
2561 | NPT_HttpResponder::~NPT_HttpResponder
2562 +---------------------------------------------------------------------*/
2563 NPT_HttpResponder::~NPT_HttpResponder()
2567 /*----------------------------------------------------------------------
2568 | NPT_HttpServer::Terminate
2569 +---------------------------------------------------------------------*/
2570 void NPT_HttpServer::Terminate()
2572 m_Run = false;
2575 /*----------------------------------------------------------------------
2576 | NPT_HttpResponder::SetConfig
2577 +---------------------------------------------------------------------*/
2578 NPT_Result
2579 NPT_HttpResponder::SetConfig(const Config& config)
2581 m_Config = config;
2583 return NPT_SUCCESS;
2586 /*----------------------------------------------------------------------
2587 | NPT_HttpResponder::SetTimeout
2588 +---------------------------------------------------------------------*/
2589 NPT_Result
2590 NPT_HttpResponder::SetTimeout(NPT_Timeout io_timeout)
2592 m_Config.m_IoTimeout = io_timeout;
2594 return NPT_SUCCESS;
2597 /*----------------------------------------------------------------------
2598 | NPT_HttpResponder::ParseRequest
2599 +---------------------------------------------------------------------*/
2600 NPT_Result
2601 NPT_HttpResponder::ParseRequest(NPT_HttpRequest*& request,
2602 const NPT_SocketAddress* local_address)
2604 // rebuffer the stream in case we're using a keep-alive connection
2605 m_Input->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE);
2607 // parse the request
2608 NPT_CHECK_FINE(NPT_HttpRequest::Parse(*m_Input, local_address, request));
2610 // unbuffer the stream
2611 m_Input->SetBufferSize(0);
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;
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);
2627 request->SetEntity(entity);
2629 return NPT_SUCCESS;
2632 /*----------------------------------------------------------------------
2633 | NPT_HttpResponder::SendResponseHeaders
2634 +---------------------------------------------------------------------*/
2635 NPT_Result
2636 NPT_HttpResponder::SendResponseHeaders(NPT_HttpResponse& response)
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
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);
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);
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);
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
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");
2682 // create a memory stream to buffer the response line and headers
2683 NPT_MemoryStream buffer;
2685 // emit the response line
2686 NPT_CHECK_WARNING(response.Emit(buffer));
2688 // send the buffer
2689 NPT_CHECK_WARNING(m_Output->WriteFully(buffer.GetData(), buffer.GetDataSize()));
2691 return NPT_SUCCESS;
2694 /*----------------------------------------------------------------------
2695 | NPT_HttpRequestHandler Dynamic Cast Anchor
2696 +---------------------------------------------------------------------*/
2697 NPT_DEFINE_DYNAMIC_CAST_ANCHOR(NPT_HttpRequestHandler)
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)
2707 NPT_HttpEntity* entity = response.GetEntity();
2708 if (entity == NULL) return NPT_SUCCESS;
2710 NPT_InputStreamReference body_stream;
2711 entity->GetInputStream(body_stream);
2712 if (body_stream.IsNull()) return NPT_SUCCESS;
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);
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));
2731 // flush to write out any buffered data left in chunked output if used
2732 dest->Flush();
2734 // cleanup (this will send zero size chunk followed by CRLF)
2735 if (dest != &output) delete dest;
2737 return result;
2740 /*----------------------------------------------------------------------
2741 | NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler
2742 +---------------------------------------------------------------------*/
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)
2751 /*----------------------------------------------------------------------
2752 | NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler
2753 +---------------------------------------------------------------------*/
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)
2761 /*----------------------------------------------------------------------
2762 | NPT_HttpStaticRequestHandler::SetupResponse
2763 +---------------------------------------------------------------------*/
2764 NPT_Result
2765 NPT_HttpStaticRequestHandler::SetupResponse(NPT_HttpRequest& /*request*/,
2766 const NPT_HttpRequestContext& /*context*/,
2767 NPT_HttpResponse& response)
2769 NPT_HttpEntity* entity = response.GetEntity();
2770 if (entity == NULL) return NPT_ERROR_INVALID_STATE;
2772 entity->SetContentType(m_MimeType);
2773 entity->SetInputStream(m_Buffer.GetData(), m_Buffer.GetDataSize());
2775 return NPT_SUCCESS;
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 {"flac", "audio/x-flac"},
2803 {"mka", "audio/x-matroska"},
2804 {"mpa", "audio/mpeg"},
2805 {"mp2", "audio/mpeg"},
2806 {"mp3", "audio/mpeg"},
2807 {"m4a", "audio/mp4"},
2808 {"wma", "audio/x-ms-wma"},
2809 {"wav", "audio/x-wav"},
2810 {"mkv", "video/x-matroska"},
2811 {"mpeg", "video/mpeg"},
2812 {"mpg", "video/mpeg"},
2813 {"mp4", "video/mp4"},
2814 {"m4v", "video/mp4"},
2815 {"ts", "video/MP2T"}, // RFC 3555
2816 {"mpegts", "video/MP2T"},
2817 {"mov", "video/quicktime"},
2818 {"qt", "video/quicktime"},
2819 {"wmv", "video/x-ms-wmv"},
2820 {"wtv", "video/x-ms-wmv"},
2821 {"asf", "video/x-ms-asf"},
2822 {"mkv", "video/x-matroska"},
2823 {"mk3d", "video/x-matroska-3d"},
2824 {"flv", "video/x-flv"},
2825 {"avi", "video/x-msvideo"},
2826 {"divx", "video/x-msvideo"},
2827 {"xvid", "video/x-msvideo"},
2828 {"doc", "application/msword"},
2829 {"js", "application/javascript"},
2830 {"m3u8", "application/x-mpegURL"},
2831 {"pdf", "application/pdf"},
2832 {"ps", "application/postscript"},
2833 {"eps", "application/postscript"},
2834 {"zip", "application/zip"}
2837 /*----------------------------------------------------------------------
2838 | NPT_HttpFileRequestHandler::NPT_HttpFileRequestHandler
2839 +---------------------------------------------------------------------*/
2840 NPT_HttpFileRequestHandler::NPT_HttpFileRequestHandler(const char* url_root,
2841 const char* file_root,
2842 bool auto_dir,
2843 const char* auto_index) :
2844 m_UrlRoot(url_root),
2845 m_FileRoot(file_root),
2846 m_DefaultMimeType("text/html"),
2847 m_UseDefaultFileTypeMap(true),
2848 m_AutoDir(auto_dir),
2849 m_AutoIndex(auto_index)
2853 /*----------------------------------------------------------------------
2854 | helper functions FIXME: need to move these to a separate module
2855 +---------------------------------------------------------------------*/
2856 static NPT_UInt32
2857 _utf8_decode(const char** str)
2859 NPT_UInt32 result;
2860 NPT_UInt32 min_value;
2861 unsigned int bytes_left;
2863 if (**str == 0) {
2864 return ~0;
2865 } else if ((**str & 0x80) == 0x00) {
2866 result = *(*str)++;
2867 bytes_left = 0;
2868 min_value = 0;
2869 } else if ((**str & 0xE0) == 0xC0) {
2870 result = *(*str)++ & 0x1F;
2871 bytes_left = 1;
2872 min_value = 0x80;
2873 } else if ((**str & 0xF0) == 0xE0) {
2874 result = *(*str)++ & 0x0F;
2875 bytes_left = 2;
2876 min_value = 0x800;
2877 } else if ((**str & 0xF8) == 0xF0) {
2878 result = *(*str)++ & 0x07;
2879 bytes_left = 3;
2880 min_value = 0x10000;
2881 } else {
2882 return ~0;
2885 while (bytes_left--) {
2886 if (**str == 0 || (**str & 0xC0) != 0x80) return ~0;
2887 result = (result << 6) | (*(*str)++ & 0x3F);
2890 if (result < min_value || (result & 0xFFFFF800) == 0xD800 || result > 0x10FFFF) {
2891 return ~0;
2894 return result;
2897 /*----------------------------------------------------------------------
2898 | NPT_HtmlEncode
2899 +---------------------------------------------------------------------*/
2900 static NPT_String
2901 NPT_HtmlEncode(const char* str, const char* chars)
2903 NPT_String encoded;
2905 // check args
2906 if (str == NULL) return encoded;
2908 // reserve at least the size of the current uri
2909 encoded.Reserve(NPT_StringLength(str));
2911 // process each character
2912 while (*str) {
2913 NPT_UInt32 c = _utf8_decode(&str);
2914 bool encode = false;
2915 if (c < ' ' || c > '~') {
2916 encode = true;
2917 } else {
2918 const char* match = chars;
2919 while (*match) {
2920 if (c == (NPT_UInt32)*match) {
2921 encode = true;
2922 break;
2924 ++match;
2927 if (encode) {
2928 // encode
2929 char hex[9];
2930 encoded += "&#x";
2931 unsigned int len = 0;
2932 if (c > 0xFFFF) {
2933 NPT_ByteToHex((unsigned char)(c>>24), &hex[0], true);
2934 NPT_ByteToHex((unsigned char)(c>>16), &hex[2], true);
2935 len = 4;
2937 NPT_ByteToHex((unsigned char)(c>>8), &hex[len ], true);
2938 NPT_ByteToHex((unsigned char)(c ), &hex[len+2], true);
2939 hex[len+4] = ';';
2940 encoded.Append(hex, len+5);
2941 } else {
2942 // no encoding required
2943 encoded += (char)c;
2947 return encoded;
2950 /*----------------------------------------------------------------------
2951 | NPT_HttpFileRequestHandler::SetupResponse
2952 +---------------------------------------------------------------------*/
2953 NPT_Result
2954 NPT_HttpFileRequestHandler::SetupResponse(NPT_HttpRequest& request,
2955 const NPT_HttpRequestContext& /* context */,
2956 NPT_HttpResponse& response)
2958 NPT_HttpEntity* entity = response.GetEntity();
2959 if (entity == NULL) return NPT_ERROR_INVALID_STATE;
2961 // check the method
2962 if (request.GetMethod() != NPT_HTTP_METHOD_GET &&
2963 request.GetMethod() != NPT_HTTP_METHOD_HEAD) {
2964 response.SetStatus(405, "Method Not Allowed");
2965 return NPT_SUCCESS;
2968 // set some default headers
2969 response.GetHeaders().SetHeader(NPT_HTTP_HEADER_ACCEPT_RANGES, "bytes");
2971 // declare HTTP/1.1 if the client asked for it
2972 if (request.GetProtocol() == NPT_HTTP_PROTOCOL_1_1) {
2973 response.SetProtocol(NPT_HTTP_PROTOCOL_1_1);
2976 // TODO: we need to normalize the request path
2978 // check that the request's path is an entry under the url root
2979 if (!request.GetUrl().GetPath(true).StartsWith(m_UrlRoot)) {
2980 return NPT_ERROR_INVALID_PARAMETERS;
2983 // compute the filename
2984 NPT_String filename = m_FileRoot;
2985 NPT_String relative_path = NPT_Url::PercentDecode(request.GetUrl().GetPath().GetChars()+m_UrlRoot.GetLength());
2986 filename += "/";
2987 filename += relative_path;
2988 NPT_LOG_FINE_1("filename = %s", filename.GetChars());
2990 // get info about the file
2991 NPT_FileInfo info;
2992 NPT_File::GetInfo(filename, &info);
2994 // check if this is a directory
2995 if (info.m_Type == NPT_FileInfo::FILE_TYPE_DIRECTORY) {
2996 NPT_LOG_FINE("file is a DIRECTORY");
2997 if (m_AutoDir) {
2998 if (m_AutoIndex.GetLength()) {
2999 NPT_LOG_FINE("redirecting to auto-index");
3000 filename += NPT_FilePath::Separator;
3001 filename += m_AutoIndex;
3002 if (NPT_File::Exists(filename)) {
3003 NPT_String location = m_UrlRoot+"/"+m_AutoIndex;
3004 response.SetStatus(302, "Found");
3005 response.GetHeaders().SetHeader(NPT_HTTP_HEADER_LOCATION, location);
3006 } else {
3007 return NPT_ERROR_PERMISSION_DENIED;
3009 } else {
3010 NPT_LOG_FINE("doing auto-dir");
3012 // get the dir entries
3013 NPT_List<NPT_String> entries;
3014 NPT_File::ListDir(filename, entries);
3016 NPT_String html;
3017 html.Reserve(1024+128*entries.GetItemCount());
3019 NPT_String html_dirname = NPT_HtmlEncode(relative_path, "<>&");
3020 html += "<hmtl><head><title>Directory Listing for /";
3021 html += html_dirname;
3022 html += "</title></head><body>";
3023 html += "<h2>Directory Listing for /";
3024 html += html_dirname;
3025 html += "</h2><hr><ul>\r\n";
3026 NPT_String url_base_path = NPT_HtmlEncode(request.GetUrl().GetPath(), "<>&\"");
3028 for (NPT_List<NPT_String>::Iterator i = entries.GetFirstItem();
3030 ++i) {
3031 NPT_String url_filename = NPT_HtmlEncode(*i, "<>&");
3032 html += "<li><a href=\"";
3033 html += url_base_path;
3034 if (!url_base_path.EndsWith("/")) html += "/";
3035 html += url_filename;
3036 html += "\">";
3037 html +=url_filename;
3039 NPT_String full_path = filename;
3040 full_path += "/";
3041 full_path += *i;
3042 NPT_File::GetInfo(full_path, &info);
3043 if (info.m_Type == NPT_FileInfo::FILE_TYPE_DIRECTORY) html += "/";
3045 html += "</a><br>\r\n";
3047 html += "</ul></body></html>";
3049 entity->SetContentType("text/html");
3050 entity->SetInputStream(html);
3051 return NPT_SUCCESS;
3053 } else {
3054 return NPT_ERROR_PERMISSION_DENIED;
3058 // open the file
3059 NPT_File file(filename);
3060 NPT_Result result = file.Open(NPT_FILE_OPEN_MODE_READ);
3061 if (NPT_FAILED(result)) {
3062 NPT_LOG_FINE("file not found");
3063 return NPT_ERROR_NO_SUCH_ITEM;
3065 NPT_InputStreamReference stream;
3066 file.GetInputStream(stream);
3068 // check for range requests
3069 const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE);
3071 // setup entity body
3072 NPT_CHECK(SetupResponseBody(response, stream, range_spec));
3074 // set the response body
3075 entity->SetContentType(GetContentType(filename));
3077 return NPT_SUCCESS;
3080 /*----------------------------------------------------------------------
3081 | NPT_HttpFileRequestHandler::SetupResponseBody
3082 +---------------------------------------------------------------------*/
3083 NPT_Result
3084 NPT_HttpFileRequestHandler::SetupResponseBody(NPT_HttpResponse& response,
3085 NPT_InputStreamReference& stream,
3086 const NPT_String* range_spec /* = NULL */)
3088 NPT_HttpEntity* entity = response.GetEntity();
3089 if (entity == NULL) return NPT_ERROR_INVALID_STATE;
3091 if (range_spec) {
3092 const NPT_String* accept_range = response.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_ACCEPT_RANGES);
3094 if (response.GetEntity()->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED ||
3095 (accept_range && accept_range->Compare("bytes"))) {
3096 NPT_LOG_FINE("range request not supported");
3097 response.SetStatus(416, "Requested Range Not Satisfiable");
3098 return NPT_SUCCESS;
3101 // measure the stream size
3102 bool has_stream_size = false;
3103 NPT_LargeSize stream_size = 0;
3104 NPT_Result result = stream->GetSize(stream_size);
3105 if (NPT_SUCCEEDED(result)) {
3106 has_stream_size = true;
3107 NPT_LOG_FINE_1("body size=%lld", stream_size);
3108 if (stream_size == 0) return NPT_SUCCESS;
3111 if (!range_spec->StartsWith("bytes=")) {
3112 NPT_LOG_FINE("unknown range spec");
3113 response.SetStatus(400, "Bad Request");
3114 return NPT_SUCCESS;
3116 NPT_String valid_range;
3117 NPT_String range(range_spec->GetChars()+6);
3118 if (range.Find(',') >= 0) {
3119 NPT_LOG_FINE("multi-range requests not supported");
3120 if (has_stream_size) {
3121 valid_range = "bytes */";
3122 valid_range += NPT_String::FromInteger(stream_size);
3123 response.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, valid_range.GetChars());
3125 response.SetStatus(416, "Requested Range Not Satisfiable");
3126 return NPT_SUCCESS;
3128 int sep = range.Find('-');
3129 NPT_UInt64 range_start = 0;
3130 NPT_UInt64 range_end = 0;
3131 bool has_start = false;
3132 bool has_end = false;
3133 bool satisfied = false;
3134 if (sep < 0) {
3135 NPT_LOG_FINE("invalid syntax");
3136 response.SetStatus(400, "Bad Request");
3137 return NPT_SUCCESS;
3138 } else {
3139 if ((unsigned int)sep+1 < range.GetLength()) {
3140 result = NPT_ParseInteger64(range.GetChars()+sep+1, range_end);
3141 if (NPT_FAILED(result)) {
3142 NPT_LOG_FINE("failed to parse range end");
3143 return result;
3145 range.SetLength(sep);
3146 has_end = true;
3148 if (sep > 0) {
3149 result = range.ToInteger64(range_start);
3150 if (NPT_FAILED(result)) {
3151 NPT_LOG_FINE("failed to parse range start");
3152 return result;
3154 has_start = true;
3157 if (!has_stream_size) {
3158 if (has_start && range_start == 0 && !has_end) {
3159 bool update_content_length = (entity->GetTransferEncoding() != NPT_HTTP_TRANSFER_ENCODING_CHUNKED);
3160 // use the whole file stream as a body
3161 return entity->SetInputStream(stream, update_content_length);
3162 } else {
3163 NPT_LOG_WARNING_2("file.GetSize() failed (%d:%s)", result, NPT_ResultText(result));
3164 NPT_LOG_FINE("range request not supported");
3165 response.SetStatus(416, "Requested Range Not Satisfiable");
3166 return NPT_SUCCESS;
3170 if (has_start) {
3171 // some clients sends incorrect range_end equal to size
3172 // we try to handle it
3173 if (!has_end || range_end == stream_size) range_end = stream_size-1;
3174 } else {
3175 if (has_end) {
3176 if (range_end <= stream_size) {
3177 range_start = stream_size-range_end;
3178 range_end = stream_size-1;
3182 NPT_LOG_FINE_2("final range: start=%lld, end=%lld", range_start, range_end);
3183 if (range_start > range_end) {
3184 NPT_LOG_FINE("invalid range");
3185 response.SetStatus(400, "Bad Request");
3186 satisfied = false;
3187 } else if (range_end >= stream_size) {
3188 response.SetStatus(416, "Requested Range Not Satisfiable");
3189 NPT_LOG_FINE("out of range");
3190 satisfied = false;
3191 } else {
3192 satisfied = true;
3195 if (satisfied && range_start != 0) {
3196 // seek in the stream
3197 result = stream->Seek(range_start);
3198 if (NPT_FAILED(result)) {
3199 NPT_LOG_WARNING_2("stream.Seek() failed (%d:%s)", result, NPT_ResultText(result));
3200 satisfied = false;
3203 if (!satisfied) {
3204 if (!valid_range.IsEmpty()) response.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, valid_range.GetChars());
3205 response.SetStatus(416, "Requested Range Not Satisfiable");
3206 return NPT_SUCCESS;
3209 // use a portion of the file stream as a body
3210 entity->SetInputStream(stream, false);
3211 entity->SetContentLength(range_end-range_start+1);
3212 response.SetStatus(206, "Partial Content");
3213 valid_range = "bytes ";
3214 valid_range += NPT_String::FromInteger(range_start);
3215 valid_range += "-";
3216 valid_range += NPT_String::FromInteger(range_end);
3217 valid_range += "/";
3218 valid_range += NPT_String::FromInteger(stream_size);
3219 response.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, valid_range.GetChars());
3220 } else {
3221 bool update_content_length = (entity->GetTransferEncoding() != NPT_HTTP_TRANSFER_ENCODING_CHUNKED);
3222 // use the whole file stream as a body
3223 entity->SetInputStream(stream, update_content_length);
3226 return NPT_SUCCESS;
3229 /*----------------------------------------------------------------------
3230 | NPT_HttpFileRequestHandler::GetContentType
3231 +---------------------------------------------------------------------*/
3232 const char*
3233 NPT_HttpFileRequestHandler::GetDefaultContentType(const char* extension)
3235 for (unsigned int i=0; i<NPT_ARRAY_SIZE(NPT_HttpFileRequestHandler_DefaultFileTypeMap); i++) {
3236 if (NPT_String::Compare(extension, NPT_HttpFileRequestHandler_DefaultFileTypeMap[i].extension, true) == 0) {
3237 const char* type = NPT_HttpFileRequestHandler_DefaultFileTypeMap[i].mime_type;
3238 NPT_LOG_FINE_1("using type from default list: %s", type);
3239 return type;
3243 return NULL;
3246 /*----------------------------------------------------------------------
3247 | NPT_HttpFileRequestHandler::GetContentType
3248 +---------------------------------------------------------------------*/
3249 const char*
3250 NPT_HttpFileRequestHandler::GetContentType(const NPT_String& filename)
3252 int last_dot = filename.ReverseFind('.');
3253 if (last_dot > 0) {
3254 NPT_String extension = filename.GetChars()+last_dot+1;
3255 extension.MakeLowercase();
3257 NPT_LOG_FINE_1("extension=%s", extension.GetChars());
3259 NPT_String* mime_type;
3260 if (NPT_SUCCEEDED(m_FileTypeMap.Get(extension, mime_type))) {
3261 NPT_LOG_FINE_1("found mime type in map: %s", mime_type->GetChars());
3262 return mime_type->GetChars();
3265 // not found, look in the default map if necessary
3266 if (m_UseDefaultFileTypeMap) {
3267 const char* type = NPT_HttpFileRequestHandler::GetDefaultContentType(extension);
3268 if (type) return type;
3272 NPT_LOG_FINE("using default mime type");
3273 return m_DefaultMimeType;
3276 /*----------------------------------------------------------------------
3277 | NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream
3278 +---------------------------------------------------------------------*/
3279 NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream(
3280 NPT_BufferedInputStreamReference& stream) :
3281 m_Source(stream),
3282 m_CurrentChunkSize(0),
3283 m_Eos(false)
3287 /*----------------------------------------------------------------------
3288 | NPT_HttpChunkedInputStream::~NPT_HttpChunkedInputStream
3289 +---------------------------------------------------------------------*/
3290 NPT_HttpChunkedInputStream::~NPT_HttpChunkedInputStream()
3294 /*----------------------------------------------------------------------
3295 | NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream
3296 +---------------------------------------------------------------------*/
3297 NPT_Result
3298 NPT_HttpChunkedInputStream::Read(void* buffer,
3299 NPT_Size bytes_to_read,
3300 NPT_Size* bytes_read /* = NULL */)
3302 // set the initial state of return values
3303 if (bytes_read) *bytes_read = 0;
3305 // check for end of stream
3306 if (m_Eos) return NPT_ERROR_EOS;
3308 // shortcut
3309 if (bytes_to_read == 0) return NPT_SUCCESS;
3311 // read next chunk size if needed
3312 if (m_CurrentChunkSize == 0) {
3313 // buffered mode
3314 m_Source->SetBufferSize(4096);
3316 NPT_String size_line;
3317 NPT_CHECK_FINE(m_Source->ReadLine(size_line));
3319 // decode size (in hex)
3320 m_CurrentChunkSize = 0;
3321 if (size_line.GetLength() < 1) {
3322 NPT_LOG_WARNING("empty chunk size line");
3323 return NPT_ERROR_INVALID_FORMAT;
3325 const char* size_hex = size_line.GetChars();
3326 while (*size_hex != '\0' &&
3327 *size_hex != ' ' &&
3328 *size_hex != ';' &&
3329 *size_hex != '\r' &&
3330 *size_hex != '\n') {
3331 int nibble = NPT_HexToNibble(*size_hex);
3332 if (nibble < 0) {
3333 NPT_LOG_WARNING_1("invalid chunk size format (%s)", size_line.GetChars());
3334 return NPT_ERROR_INVALID_FORMAT;
3336 m_CurrentChunkSize = (m_CurrentChunkSize<<4)|nibble;
3337 ++size_hex;
3339 NPT_LOG_FINEST_1("start of chunk, size=%d", m_CurrentChunkSize);
3341 // 0 = end of body
3342 if (m_CurrentChunkSize == 0) {
3343 NPT_LOG_FINEST("end of chunked stream, reading trailers");
3345 // read footers until empty line
3346 NPT_String footer;
3347 do {
3348 NPT_CHECK_FINE(m_Source->ReadLine(footer));
3349 } while (!footer.IsEmpty());
3350 m_Eos = true;
3352 NPT_LOG_FINEST("end of chunked stream, done");
3353 return NPT_ERROR_EOS;
3356 // unbuffer source
3357 m_Source->SetBufferSize(0);
3360 // read no more than what's left in chunk
3361 NPT_Size chunk_bytes_read;
3362 if (bytes_to_read > m_CurrentChunkSize) bytes_to_read = m_CurrentChunkSize;
3363 NPT_CHECK_FINE(m_Source->Read(buffer, bytes_to_read, &chunk_bytes_read));
3365 // ready to go to next chunk?
3366 m_CurrentChunkSize -= chunk_bytes_read;
3367 if (m_CurrentChunkSize == 0) {
3368 NPT_LOG_FINEST("reading end of chunk");
3370 // when a chunk is finished, a \r\n follows
3371 char newline[2];
3372 NPT_CHECK_FINE(m_Source->ReadFully(newline, 2));
3373 if (newline[0] != '\r' || newline[1] != '\n') {
3374 NPT_LOG_WARNING("invalid end of chunk (expected \\r\\n)");
3375 return NPT_ERROR_INVALID_FORMAT;
3379 // update output params
3380 if (bytes_read) *bytes_read = chunk_bytes_read;
3382 return NPT_SUCCESS;
3385 /*----------------------------------------------------------------------
3386 | NPT_HttpChunkedInputStream::Seek
3387 +---------------------------------------------------------------------*/
3388 NPT_Result
3389 NPT_HttpChunkedInputStream::Seek(NPT_Position /*offset*/)
3391 return NPT_ERROR_NOT_SUPPORTED;
3394 /*----------------------------------------------------------------------
3395 | NPT_HttpChunkedInputStream::Tell
3396 +---------------------------------------------------------------------*/
3397 NPT_Result
3398 NPT_HttpChunkedInputStream::Tell(NPT_Position& offset)
3400 offset = 0;
3401 return NPT_ERROR_NOT_SUPPORTED;
3404 /*----------------------------------------------------------------------
3405 | NPT_HttpChunkedInputStream::GetSize
3406 +---------------------------------------------------------------------*/
3407 NPT_Result
3408 NPT_HttpChunkedInputStream::GetSize(NPT_LargeSize& size)
3410 return m_Source->GetSize(size);
3413 /*----------------------------------------------------------------------
3414 | NPT_HttpChunkedInputStream::GetAvailable
3415 +---------------------------------------------------------------------*/
3416 NPT_Result
3417 NPT_HttpChunkedInputStream::GetAvailable(NPT_LargeSize& available)
3419 return m_Source->GetAvailable(available);
3422 /*----------------------------------------------------------------------
3423 | NPT_HttpChunkedOutputStream::NPT_HttpChunkedOutputStream
3424 +---------------------------------------------------------------------*/
3425 NPT_HttpChunkedOutputStream::NPT_HttpChunkedOutputStream(NPT_OutputStream& stream) :
3426 m_Stream(stream)
3430 /*----------------------------------------------------------------------
3431 | NPT_HttpChunkedOutputStream::~NPT_HttpChunkedOutputStream
3432 +---------------------------------------------------------------------*/
3433 NPT_HttpChunkedOutputStream::~NPT_HttpChunkedOutputStream()
3435 // zero size chunk followed by CRLF (no trailer)
3436 m_Stream.WriteFully("0" NPT_HTTP_LINE_TERMINATOR NPT_HTTP_LINE_TERMINATOR, 5);
3439 /*----------------------------------------------------------------------
3440 | NPT_HttpChunkedOutputStream::Write
3441 +---------------------------------------------------------------------*/
3442 NPT_Result
3443 NPT_HttpChunkedOutputStream::Write(const void* buffer,
3444 NPT_Size bytes_to_write,
3445 NPT_Size* bytes_written)
3447 // default values
3448 if (bytes_written) *bytes_written = 0;
3450 // shortcut
3451 if (bytes_to_write == 0) return NPT_SUCCESS;
3453 // write the chunk header
3454 char size[16];
3455 size[15] = '\n';
3456 size[14] = '\r';
3457 char* c = &size[14];
3458 unsigned int char_count = 2;
3459 unsigned int value = bytes_to_write;
3460 do {
3461 unsigned int digit = (unsigned int)(value%16);
3462 if (digit < 10) {
3463 *--c = '0'+digit;
3464 } else {
3465 *--c = 'A'+digit-10;
3467 char_count++;
3468 value /= 16;
3469 } while(value);
3470 NPT_Result result = m_Stream.WriteFully(c, char_count);
3471 if (NPT_FAILED(result)) return result;
3473 // write the chunk data
3474 result = m_Stream.WriteFully(buffer, bytes_to_write);
3475 if (NPT_FAILED(result)) return result;
3477 // finish the chunk
3478 result = m_Stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
3479 if (NPT_SUCCEEDED(result) && bytes_written) {
3480 *bytes_written = bytes_to_write;
3482 return result;