2 * Copyright 2010-2015 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
6 * Christophe Huriaux, c.huriaux@gmail.com
7 * Niels Sascha Reedijk, niels.reedijk@gmail.com
8 * Adrien Destugues, pulkomandy@pulkomandy.tk
12 #include <HttpRequest.h>
14 #include <arpa/inet.h>
21 #include <AutoDeleter.h>
22 #include <Certificate.h>
24 #include <DynamicBuffer.h>
26 #include <ProxySecureSocket.h>
28 #include <SecureSocket.h>
29 #include <StackOrHeapArray.h>
30 #include <ZlibCompressionAlgorithm.h>
33 static const int32 kHttpBufferSize
= 4096;
38 class CheckedSecureSocket
: public BSecureSocket
41 CheckedSecureSocket(BHttpRequest
* request
);
43 bool CertificateVerificationFailed(BCertificate
& certificate
,
47 BHttpRequest
* fRequest
;
51 CheckedSecureSocket::CheckedSecureSocket(BHttpRequest
* request
)
60 CheckedSecureSocket::CertificateVerificationFailed(BCertificate
& certificate
,
63 return fRequest
->_CertificateVerificationFailed(certificate
, message
);
67 class CheckedProxySecureSocket
: public BProxySecureSocket
70 CheckedProxySecureSocket(const BNetworkAddress
& proxy
, BHttpRequest
* request
);
72 bool CertificateVerificationFailed(BCertificate
& certificate
,
76 BHttpRequest
* fRequest
;
80 CheckedProxySecureSocket::CheckedProxySecureSocket(const BNetworkAddress
& proxy
,
81 BHttpRequest
* request
)
83 BProxySecureSocket(proxy
),
90 CheckedProxySecureSocket::CertificateVerificationFailed(BCertificate
& certificate
,
93 return fRequest
->_CertificateVerificationFailed(certificate
, message
);
98 BHttpRequest::BHttpRequest(const BUrl
& url
, bool ssl
, const char* protocolName
,
99 BUrlProtocolListener
* listener
, BUrlContext
* context
)
101 BNetworkRequest(url
, listener
, context
, "BUrlProtocol.HTTP", protocolName
),
103 fRequestMethod(B_HTTP_GET
),
104 fHttpVersion(B_HTTP_11
),
106 fRequestStatus(kRequestInitialState
),
108 fOptPostFields(NULL
),
110 fOptInputDataSize(-1),
113 fOptFollowLocation(true)
120 BHttpRequest::BHttpRequest(const BHttpRequest
& other
)
122 BNetworkRequest(other
.Url(), other
.fListener
, other
.fContext
,
123 "BUrlProtocol.HTTP", other
.fSSL
? "HTTPS" : "HTTP"),
125 fRequestMethod(other
.fRequestMethod
),
126 fHttpVersion(other
.fHttpVersion
),
128 fRequestStatus(kRequestInitialState
),
130 fOptPostFields(NULL
),
132 fOptInputDataSize(-1),
133 fOptRangeStart(other
.fOptRangeStart
),
134 fOptRangeEnd(other
.fOptRangeEnd
),
135 fOptFollowLocation(other
.fOptFollowLocation
)
138 // FIXME some options may be copied from other instead.
143 BHttpRequest::~BHttpRequest()
149 delete fOptInputData
;
151 delete fOptPostFields
;
156 BHttpRequest::SetMethod(const char* const method
)
158 fRequestMethod
= method
;
163 BHttpRequest::SetFollowLocation(bool follow
)
165 fOptFollowLocation
= follow
;
170 BHttpRequest::SetMaxRedirections(int8 redirections
)
172 fOptMaxRedirs
= redirections
;
177 BHttpRequest::SetReferrer(const BString
& referrer
)
179 fOptReferer
= referrer
;
184 BHttpRequest::SetUserAgent(const BString
& agent
)
186 fOptUserAgent
= agent
;
191 BHttpRequest::SetDiscardData(bool discard
)
193 fOptDiscardData
= discard
;
198 BHttpRequest::SetDisableListener(bool disable
)
200 fOptDisableListener
= disable
;
205 BHttpRequest::SetAutoReferrer(bool enable
)
207 fOptAutoReferer
= enable
;
212 BHttpRequest::SetHeaders(const BHttpHeaders
& headers
)
214 AdoptHeaders(new(std::nothrow
) BHttpHeaders(headers
));
219 BHttpRequest::AdoptHeaders(BHttpHeaders
* const headers
)
222 fOptHeaders
= headers
;
227 BHttpRequest::SetPostFields(const BHttpForm
& fields
)
229 AdoptPostFields(new(std::nothrow
) BHttpForm(fields
));
234 BHttpRequest::AdoptPostFields(BHttpForm
* const fields
)
236 delete fOptPostFields
;
237 fOptPostFields
= fields
;
239 if (fOptPostFields
!= NULL
)
240 fRequestMethod
= B_HTTP_POST
;
245 BHttpRequest::AdoptInputData(BDataIO
* const data
, const ssize_t size
)
247 delete fOptInputData
;
248 fOptInputData
= data
;
249 fOptInputDataSize
= size
;
254 BHttpRequest::SetUserName(const BString
& name
)
261 BHttpRequest::SetPassword(const BString
& password
)
263 fOptPassword
= password
;
268 BHttpRequest::IsInformationalStatusCode(int16 code
)
270 return (code
>= B_HTTP_STATUS__INFORMATIONAL_BASE
)
271 && (code
< B_HTTP_STATUS__INFORMATIONAL_END
);
276 BHttpRequest::IsSuccessStatusCode(int16 code
)
278 return (code
>= B_HTTP_STATUS__SUCCESS_BASE
)
279 && (code
< B_HTTP_STATUS__SUCCESS_END
);
284 BHttpRequest::IsRedirectionStatusCode(int16 code
)
286 return (code
>= B_HTTP_STATUS__REDIRECTION_BASE
)
287 && (code
< B_HTTP_STATUS__REDIRECTION_END
);
292 BHttpRequest::IsClientErrorStatusCode(int16 code
)
294 return (code
>= B_HTTP_STATUS__CLIENT_ERROR_BASE
)
295 && (code
< B_HTTP_STATUS__CLIENT_ERROR_END
);
300 BHttpRequest::IsServerErrorStatusCode(int16 code
)
302 return (code
>= B_HTTP_STATUS__SERVER_ERROR_BASE
)
303 && (code
< B_HTTP_STATUS__SERVER_ERROR_END
);
308 BHttpRequest::StatusCodeClass(int16 code
)
310 if (BHttpRequest::IsInformationalStatusCode(code
))
311 return B_HTTP_STATUS_CLASS_INFORMATIONAL
;
312 else if (BHttpRequest::IsSuccessStatusCode(code
))
313 return B_HTTP_STATUS_CLASS_SUCCESS
;
314 else if (BHttpRequest::IsRedirectionStatusCode(code
))
315 return B_HTTP_STATUS_CLASS_REDIRECTION
;
316 else if (BHttpRequest::IsClientErrorStatusCode(code
))
317 return B_HTTP_STATUS_CLASS_CLIENT_ERROR
;
318 else if (BHttpRequest::IsServerErrorStatusCode(code
))
319 return B_HTTP_STATUS_CLASS_SERVER_ERROR
;
321 return B_HTTP_STATUS_CLASS_INVALID
;
326 BHttpRequest::Result() const
335 if (fSocket
!= NULL
) {
336 fSocket
->Disconnect();
337 // Unlock any pending connect, read or write operation.
339 return BNetworkRequest::Stop();
344 BHttpRequest::_ResetOptions()
346 delete fOptPostFields
;
349 fOptFollowLocation
= true;
352 fOptUserAgent
= "Services Kit (Haiku)";
355 fOptAuthMethods
= B_HTTP_AUTHENTICATION_BASIC
| B_HTTP_AUTHENTICATION_DIGEST
356 | B_HTTP_AUTHENTICATION_IE_DIGEST
;
358 fOptPostFields
= NULL
;
359 fOptSetCookies
= true;
360 fOptDiscardData
= false;
361 fOptDisableListener
= false;
362 fOptAutoReferer
= true;
367 BHttpRequest::_ProtocolLoop()
369 // Initialize the request redirection loop
370 int8 maxRedirs
= fOptMaxRedirs
;
378 _ResultHeaders().Clear();
380 BString host
= fUrl
.Host();
381 int port
= fSSL
? 443 : 80;
386 if (fContext
->UseProxy()) {
387 host
= fContext
->GetProxyHost();
388 port
= fContext
->GetProxyPort();
391 status_t result
= fInputBuffer
.InitCheck();
395 if (!_ResolveHostName(host
, port
)) {
396 _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR
,
397 "Unable to resolve hostname (%s), aborting.",
398 fUrl
.Host().String());
399 return B_SERVER_NOT_FOUND
;
402 status_t requestStatus
= _MakeRequest();
403 if (requestStatus
!= B_OK
)
404 return requestStatus
;
406 // Prepare the referer for the next request if needed
408 fOptReferer
= fUrl
.UrlString();
410 switch (StatusCodeClass(fResult
.StatusCode())) {
411 case B_HTTP_STATUS_CLASS_INFORMATIONAL
:
412 // Header 100:continue should have been
413 // handled in the _MakeRequest read loop
416 case B_HTTP_STATUS_CLASS_SUCCESS
:
419 case B_HTTP_STATUS_CLASS_REDIRECTION
:
421 // Redirection has been explicitly disabled
422 if (!fOptFollowLocation
)
425 int code
= fResult
.StatusCode();
426 if (code
== B_HTTP_STATUS_MOVED_PERMANENTLY
427 || code
== B_HTTP_STATUS_FOUND
428 || code
== B_HTTP_STATUS_SEE_OTHER
429 || code
== B_HTTP_STATUS_TEMPORARY_REDIRECT
) {
430 BString locationUrl
= fHeaders
["Location"];
432 fUrl
= BUrl(fUrl
, locationUrl
);
434 // 302 and 303 redirections also convert POST requests to GET
435 // (and remove the posted form data)
436 if ((code
== B_HTTP_STATUS_FOUND
437 || code
== B_HTTP_STATUS_SEE_OTHER
)
438 && fRequestMethod
== B_HTTP_POST
) {
439 SetMethod(B_HTTP_GET
);
440 delete fOptPostFields
;
441 fOptPostFields
= NULL
;
442 delete fOptInputData
;
443 fOptInputData
= NULL
;
444 fOptInputDataSize
= 0;
447 if (--maxRedirs
> 0) {
450 // Redirections may need a switch from http to https.
451 if (fUrl
.Protocol() == "https")
453 else if (fUrl
.Protocol() == "http")
456 _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT
,
458 fUrl
.UrlString().String());
464 case B_HTTP_STATUS_CLASS_CLIENT_ERROR
:
465 if (fResult
.StatusCode() == B_HTTP_STATUS_UNAUTHORIZED
) {
466 BHttpAuthentication
* authentication
467 = &fContext
->GetAuthentication(fUrl
);
468 status_t status
= B_OK
;
470 if (authentication
->Method() == B_HTTP_AUTHENTICATION_NONE
) {
471 // There is no authentication context for this
472 // url yet, so let's create one.
473 BHttpAuthentication newAuth
;
474 newAuth
.Initialize(fHeaders
["WWW-Authenticate"]);
475 fContext
->AddAuthentication(fUrl
, newAuth
);
477 // Get the copy of the authentication we just added.
478 // That copy is owned by the BUrlContext and won't be
479 // deleted (unlike the temporary object above)
480 authentication
= &fContext
->GetAuthentication(fUrl
);
484 if (fOptUsername
.Length() > 0 && status
== B_OK
) {
485 // If we received an username and password, add them
486 // to the request. This will either change the
487 // credentials for an existing request, or set them
488 // for a new one we created just above.
490 // If this request handles HTTP redirections, it will
491 // also automatically retry connecting and send the
492 // login information.
493 authentication
->SetUserName(fOptUsername
);
494 authentication
->SetPassword(fOptPassword
);
500 case B_HTTP_STATUS_CLASS_SERVER_ERROR
:
504 case B_HTTP_STATUS_CLASS_INVALID
:
507 } while (newRequest
);
509 _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT
,
510 "%ld headers and %ld bytes of data remaining",
511 fHeaders
.CountHeaders(), fInputBuffer
.Size());
513 if (fResult
.StatusCode() == 404)
514 return B_RESOURCE_NOT_FOUND
;
521 BHttpRequest::_MakeRequest()
526 if (fContext
->UseProxy()) {
527 BNetworkAddress
proxy(fContext
->GetProxyHost(), fContext
->GetProxyPort());
528 fSocket
= new(std::nothrow
) BPrivate::CheckedProxySecureSocket(proxy
, this);
530 fSocket
= new(std::nothrow
) BPrivate::CheckedSecureSocket(this);
532 fSocket
= new(std::nothrow
) BSocket();
537 _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT
, "Connection to %s on port %d.",
538 fUrl
.Authority().String(), fRemoteAddr
.Port());
539 status_t connectError
= fSocket
->Connect(fRemoteAddr
);
541 if (connectError
!= B_OK
) {
542 _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR
, "Socket connection error %s",
543 strerror(connectError
));
547 //! ProtocolHook:ConnectionOpened
548 if (fListener
!= NULL
)
549 fListener
->ConnectionOpened(this);
551 _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT
,
552 "Connection opened, sending request.");
556 fSocket
->Write("\r\n", 2);
557 _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT
, "Request sent.");
560 fRequestStatus
= kRequestInitialState
;
565 bool receiveEnd
= false;
566 bool parseEnd
= false;
567 bool readByChunks
= false;
568 bool decompress
= false;
569 status_t readError
= B_OK
;
570 ssize_t bytesRead
= 0;
571 ssize_t bytesReceived
= 0;
572 ssize_t bytesTotal
= 0;
573 size_t previousBufferSize
= 0;
574 off_t bytesUnpacked
= 0;
575 char* inputTempBuffer
= new(std::nothrow
) char[kHttpBufferSize
];
576 ssize_t inputTempSize
= kHttpBufferSize
;
577 ssize_t chunkSize
= -1;
578 DynamicBuffer decompressorStorage
;
579 BDataIO
* decompressingStream
;
580 ObjectDeleter
<BDataIO
> decompressingStreamDeleter
;
582 while (!fQuit
&& !(receiveEnd
&& parseEnd
)) {
583 if ((!receiveEnd
) && (fInputBuffer
.Size() == previousBufferSize
)) {
584 fSocket
->WaitForReadable();
585 BStackOrHeapArray
<char, 4096> chunk(kHttpBufferSize
);
586 bytesRead
= fSocket
->Read(chunk
, kHttpBufferSize
);
589 readError
= bytesRead
;
591 } else if (bytesRead
== 0)
594 fInputBuffer
.AppendData(chunk
, bytesRead
);
598 previousBufferSize
= fInputBuffer
.Size();
600 if (fRequestStatus
< kRequestStatusReceived
) {
603 //! ProtocolHook:ResponseStarted
604 if (fRequestStatus
>= kRequestStatusReceived
&& fListener
!= NULL
)
605 fListener
->ResponseStarted(this);
608 if (fRequestStatus
< kRequestHeadersReceived
) {
611 if (fRequestStatus
>= kRequestHeadersReceived
) {
612 _ResultHeaders() = fHeaders
;
614 // Parse received cookies
615 if (fContext
!= NULL
) {
616 for (int32 i
= 0; i
< fHeaders
.CountHeaders(); i
++) {
617 if (fHeaders
.HeaderAt(i
).NameIs("Set-Cookie")) {
618 fContext
->GetCookieJar().AddCookie(
619 fHeaders
.HeaderAt(i
).Value(), fUrl
);
624 //! ProtocolHook:HeadersReceived
625 if (fListener
!= NULL
)
626 fListener
->HeadersReceived(this, fResult
);
629 if (BString(fHeaders
["Transfer-Encoding"]) == "chunked")
632 BString
contentEncoding(fHeaders
["Content-Encoding"]);
633 // We don't advertise "deflate" support (see above), but we
634 // still try to decompress it, if a server ever sends a deflate
635 // stream despite it not being in our Accept-Encoding list.
636 if (contentEncoding
== "gzip"
637 || contentEncoding
== "deflate") {
639 readError
= BZlibCompressionAlgorithm()
640 .CreateDecompressingOutputStream(&decompressorStorage
,
641 NULL
, decompressingStream
);
642 if (readError
!= B_OK
)
645 decompressingStreamDeleter
.SetTo(decompressingStream
);
648 int32 index
= fHeaders
.HasHeader("Content-Length");
649 if (index
!= B_ERROR
)
650 bytesTotal
= atoi(fHeaders
.HeaderAt(index
).Value());
656 if (fRequestStatus
>= kRequestHeadersReceived
) {
657 // If Transfer-Encoding is chunked, we should read a complete
658 // chunk in buffer before handling it
660 if (chunkSize
>= 0) {
661 if ((ssize_t
)fInputBuffer
.Size() >= chunkSize
+ 2) {
662 // 2 more bytes to handle the closing CR+LF
663 bytesRead
= chunkSize
;
664 if (inputTempSize
< chunkSize
+ 2) {
665 delete[] inputTempBuffer
;
666 inputTempSize
= chunkSize
+ 2;
668 = new(std::nothrow
) char[inputTempSize
];
671 if (inputTempBuffer
== NULL
) {
672 readError
= B_NO_MEMORY
;
676 fInputBuffer
.RemoveData(inputTempBuffer
,
680 // Not enough data, try again later
685 if (_GetLine(chunkHeader
) == B_ERROR
) {
689 // Format of a chunk header:
690 // <chunk size in hex>[; optional data]
691 int32 semiColonIndex
= chunkHeader
.FindFirst(';', 0);
693 // Cut-off optional data if present
694 if (semiColonIndex
!= -1) {
695 chunkHeader
.Remove(semiColonIndex
,
696 chunkHeader
.Length() - semiColonIndex
);
699 chunkSize
= strtol(chunkHeader
.String(), NULL
, 16);
701 fRequestStatus
= kRequestContentReceived
;
707 // A chunk of 0 bytes indicates the end of the chunked transfer
711 bytesRead
= fInputBuffer
.Size();
714 if (inputTempSize
< bytesRead
) {
715 inputTempSize
= bytesRead
;
716 delete[] inputTempBuffer
;
717 inputTempBuffer
= new(std::nothrow
) char[bytesRead
];
720 if (inputTempBuffer
== NULL
) {
721 readError
= B_NO_MEMORY
;
724 fInputBuffer
.RemoveData(inputTempBuffer
, bytesRead
);
728 if (bytesRead
>= 0) {
729 bytesReceived
+= bytesRead
;
731 if (fListener
!= NULL
) {
733 readError
= decompressingStream
->WriteExactly(
734 inputTempBuffer
, bytesRead
);
735 if (readError
!= B_OK
)
738 ssize_t size
= decompressorStorage
.Size();
739 BStackOrHeapArray
<char, 4096> buffer(size
);
740 size
= decompressorStorage
.Read(buffer
, size
);
742 fListener
->DataReceived(this, buffer
, bytesUnpacked
,
744 bytesUnpacked
+= size
;
746 } else if (bytesRead
> 0) {
747 fListener
->DataReceived(this, inputTempBuffer
,
748 bytesReceived
- bytesRead
, bytesRead
);
750 fListener
->DownloadProgress(this, bytesReceived
,
751 std::max((ssize_t
)0, bytesTotal
));
754 if (bytesTotal
>= 0 && bytesReceived
>= bytesTotal
)
757 if (decompress
&& receiveEnd
) {
758 readError
= decompressingStream
->Flush();
760 if (readError
== B_BUFFER_OVERFLOW
)
763 if (readError
!= B_OK
)
766 ssize_t size
= decompressorStorage
.Size();
767 BStackOrHeapArray
<char, 4096> buffer(size
);
768 size
= decompressorStorage
.Read(buffer
, size
);
769 if (fListener
!= NULL
&& size
> 0) {
770 fListener
->DataReceived(this, buffer
,
771 bytesUnpacked
, size
);
772 bytesUnpacked
+= size
;
778 parseEnd
= (fInputBuffer
.Size() == 0);
781 fSocket
->Disconnect();
782 delete[] inputTempBuffer
;
784 if (readError
!= B_OK
)
787 return fQuit
? B_INTERRUPTED
: B_OK
;
792 BHttpRequest::_ParseStatus()
794 // Status line should be formatted like: HTTP/M.m SSS ...
795 // With: M = Major version of the protocol
796 // m = Minor version of the protocol
797 // SSS = three-digit status code of the response
798 // ... = additional text info
800 if (_GetLine(statusLine
) == B_ERROR
)
803 if (statusLine
.CountChars() < 12)
806 fRequestStatus
= kRequestStatusReceived
;
808 BString statusCodeStr
;
810 statusLine
.CopyInto(statusCodeStr
, 9, 3);
811 _SetResultStatusCode(atoi(statusCodeStr
.String()));
813 statusLine
.CopyInto(_ResultStatusText(), 13, statusLine
.Length() - 13);
815 _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT
, "Status line received: Code %d (%s)",
816 atoi(statusCodeStr
.String()), _ResultStatusText().String());
821 BHttpRequest::_ParseHeaders()
823 BString currentHeader
;
824 while (_GetLine(currentHeader
) != B_ERROR
) {
825 // An empty line means the end of the header section
826 if (currentHeader
.Length() == 0) {
827 fRequestStatus
= kRequestHeadersReceived
;
831 _EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_IN
, "%s",
832 currentHeader
.String());
833 fHeaders
.AddHeader(currentHeader
.String());
839 BHttpRequest::_SendRequest()
841 BString
request(fRequestMethod
);
844 if (fContext
->UseProxy()) {
845 // When there is a proxy, the request must include the host and port so
846 // the proxy knows where to send the request.
847 request
<< Url().Protocol() << "://" << Url().Host();
849 request
<< ':' << Url().Port();
852 if (Url().HasPath() && Url().Path().Length() > 0)
853 request
<< Url().Path();
857 if (Url().HasRequest())
858 request
<< '?' << Url().Request();
860 switch (fHttpVersion
) {
862 request
<< " HTTP/1.1\r\n";
867 request
<< " HTTP/1.0\r\n";
871 fSocket
->Write(request
.String(), request
.Length());
876 BHttpRequest::_SendHeaders()
878 BHttpHeaders outputHeaders
;
880 // HTTP 1.1 additional headers
881 if (fHttpVersion
== B_HTTP_11
) {
882 BString host
= Url().Host();
883 if (Url().HasPort() && !_IsDefaultPort())
884 host
<< ':' << Url().Port();
886 outputHeaders
.AddHeader("Host", host
);
888 outputHeaders
.AddHeader("Accept", "*/*");
889 outputHeaders
.AddHeader("Accept-Encoding", "gzip");
890 // Allows the server to compress data using the "gzip" format.
891 // "deflate" is not supported, because there are two interpretations
892 // of what it means (the RFC and Microsoft products), and we don't
893 // want to handle this. Very few websites support only deflate,
894 // and most of them will send gzip, or at worst, uncompressed data.
896 outputHeaders
.AddHeader("Connection", "close");
897 // Let the remote server close the connection after response since
898 // we don't handle multiple request on a single connection
901 // Classic HTTP headers
902 if (fOptUserAgent
.CountChars() > 0)
903 outputHeaders
.AddHeader("User-Agent", fOptUserAgent
.String());
905 if (fOptReferer
.CountChars() > 0)
906 outputHeaders
.AddHeader("Referer", fOptReferer
.String());
909 if (fContext
!= NULL
) {
910 BHttpAuthentication
& authentication
= fContext
->GetAuthentication(fUrl
);
911 if (authentication
.Method() != B_HTTP_AUTHENTICATION_NONE
) {
912 if (fOptUsername
.Length() > 0) {
913 authentication
.SetUserName(fOptUsername
);
914 authentication
.SetPassword(fOptPassword
);
917 BString
request(fRequestMethod
);
918 outputHeaders
.AddHeader("Authorization",
919 authentication
.Authorization(fUrl
, request
));
923 // Required headers for POST data
924 if (fOptPostFields
!= NULL
&& fRequestMethod
== B_HTTP_POST
) {
927 switch (fOptPostFields
->GetFormType()) {
928 case B_HTTP_FORM_MULTIPART
:
929 contentType
<< "multipart/form-data; boundary="
930 << fOptPostFields
->GetMultipartBoundary() << "";
933 case B_HTTP_FORM_URL_ENCODED
:
934 contentType
<< "application/x-www-form-urlencoded";
938 outputHeaders
.AddHeader("Content-Type", contentType
);
939 outputHeaders
.AddHeader("Content-Length",
940 fOptPostFields
->ContentLength());
941 } else if (fOptInputData
!= NULL
942 && (fRequestMethod
== B_HTTP_POST
943 || fRequestMethod
== B_HTTP_PUT
)) {
944 if (fOptInputDataSize
>= 0)
945 outputHeaders
.AddHeader("Content-Length", fOptInputDataSize
);
947 outputHeaders
.AddHeader("Transfer-Encoding", "chunked");
950 // Optional headers specified by the user
951 if (fOptHeaders
!= NULL
) {
952 for (int32 headerIndex
= 0; headerIndex
< fOptHeaders
->CountHeaders();
954 BHttpHeader
& optHeader
= (*fOptHeaders
)[headerIndex
];
955 int32 replaceIndex
= outputHeaders
.HasHeader(optHeader
.Name());
957 // Add or replace the current option header to the
958 // output header list
959 if (replaceIndex
== -1)
960 outputHeaders
.AddHeader(optHeader
.Name(), optHeader
.Value());
962 outputHeaders
[replaceIndex
].SetValue(optHeader
.Value());
967 if (fOptSetCookies
&& fContext
!= NULL
) {
968 BString cookieString
;
970 BNetworkCookieJar::UrlIterator iterator
971 = fContext
->GetCookieJar().GetUrlIterator(fUrl
);
972 const BNetworkCookie
* cookie
= iterator
.Next();
973 if (cookie
!= NULL
) {
975 cookieString
<< cookie
->RawCookie(false);
976 cookie
= iterator
.Next();
979 cookieString
<< "; ";
982 outputHeaders
.AddHeader("Cookie", cookieString
);
986 // Write output headers to output stream
989 for (int32 headerIndex
= 0; headerIndex
< outputHeaders
.CountHeaders();
991 const char* header
= outputHeaders
.HeaderAt(headerIndex
).Header();
993 headerData
<< header
;
994 headerData
<< "\r\n";
996 _EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT
, "%s", header
);
999 fSocket
->Write(headerData
.String(), headerData
.Length());
1004 BHttpRequest::_SendPostData()
1006 if (fRequestMethod
== B_HTTP_POST
&& fOptPostFields
!= NULL
) {
1007 if (fOptPostFields
->GetFormType() != B_HTTP_FORM_MULTIPART
) {
1008 BString outputBuffer
= fOptPostFields
->RawData();
1009 _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT
,
1010 "%s", outputBuffer
.String());
1011 fSocket
->Write(outputBuffer
.String(), outputBuffer
.Length());
1013 for (BHttpForm::Iterator it
= fOptPostFields
->GetIterator();
1014 const BHttpFormData
* currentField
= it
.Next();
1016 _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT
,
1017 it
.MultipartHeader().String());
1018 fSocket
->Write(it
.MultipartHeader().String(),
1019 it
.MultipartHeader().Length());
1021 switch (currentField
->Type()) {
1023 case B_HTTPFORM_UNKNOWN
:
1027 case B_HTTPFORM_STRING
:
1028 fSocket
->Write(currentField
->String().String(),
1029 currentField
->String().Length());
1032 case B_HTTPFORM_FILE
:
1034 BFile
upFile(currentField
->File().Path(),
1036 char readBuffer
[kHttpBufferSize
];
1039 readSize
= upFile
.Read(readBuffer
,
1040 sizeof(readBuffer
));
1041 while (readSize
> 0) {
1042 fSocket
->Write(readBuffer
, readSize
);
1043 readSize
= upFile
.Read(readBuffer
,
1044 sizeof(readBuffer
));
1049 case B_HTTPFORM_BUFFER
:
1050 fSocket
->Write(currentField
->Buffer(),
1051 currentField
->BufferSize());
1055 fSocket
->Write("\r\n", 2);
1058 BString footer
= fOptPostFields
->GetMultipartFooter();
1059 fSocket
->Write(footer
.String(), footer
.Length());
1061 } else if ((fRequestMethod
== B_HTTP_POST
|| fRequestMethod
== B_HTTP_PUT
)
1062 && fOptInputData
!= NULL
) {
1064 // If the input data is seekable, we rewind it for each new request.
1065 BPositionIO
* seekableData
1066 = dynamic_cast<BPositionIO
*>(fOptInputData
);
1068 seekableData
->Seek(0, SEEK_SET
);
1071 char outputTempBuffer
[kHttpBufferSize
];
1072 ssize_t read
= fOptInputData
->Read(outputTempBuffer
,
1073 sizeof(outputTempBuffer
));
1078 if (fOptInputDataSize
< 0) {
1081 size_t hexLength
= sprintf(hexSize
, "%ld", read
);
1083 fSocket
->Write(hexSize
, hexLength
);
1084 fSocket
->Write("\r\n", 2);
1085 fSocket
->Write(outputTempBuffer
, read
);
1086 fSocket
->Write("\r\n", 2);
1088 fSocket
->Write(outputTempBuffer
, read
);
1092 if (fOptInputDataSize
< 0) {
1093 // Chunked transfer terminating sequence
1094 fSocket
->Write("0\r\n\r\n", 5);
1102 BHttpRequest::_ResultHeaders()
1104 return fResult
.fHeaders
;
1109 BHttpRequest::_SetResultStatusCode(int32 statusCode
)
1111 fResult
.fStatusCode
= statusCode
;
1116 BHttpRequest::_ResultStatusText()
1118 return fResult
.fStatusString
;
1123 BHttpRequest::_CertificateVerificationFailed(BCertificate
& certificate
,
1124 const char* message
)
1126 if (fContext
->HasCertificateException(certificate
))
1129 if (fListener
!= NULL
1130 && fListener
->CertificateVerificationFailed(this, certificate
, message
)) {
1131 // User asked us to continue anyway, let's add a temporary exception for this certificate
1132 fContext
->AddCertificateException(certificate
);
1141 BHttpRequest::_IsDefaultPort()
1143 if (fSSL
&& Url().Port() == 443)
1145 if (!fSSL
&& Url().Port() == 80)