diff --git a/modules/juce_core/native/juce_android_Network.cpp b/modules/juce_core/native/juce_android_Network.cpp index 2ddd79f977..791c8dbbbc 100644 --- a/modules/juce_core/native/juce_android_Network.cpp +++ b/modules/juce_core/native/juce_android_Network.cpp @@ -324,12 +324,13 @@ class WebInputStream::Pimpl public: enum { contentStreamCacheSize = 1024 }; - Pimpl (WebInputStream&, const URL& urlToCopy, bool shouldBePost) + Pimpl (WebInputStream&, const URL& urlToCopy, bool isPOSTLike) : url (urlToCopy), isContentURL (urlToCopy.getScheme() == "content"), - isPost (shouldBePost), - httpRequest (isPost ? "POST" : "GET") - {} + addParametersToRequestBody (isPOSTLike), + httpRequest (isPOSTLike || url.hasPOSTData() ? "POST" : "GET") + { + } ~Pimpl() { @@ -373,14 +374,14 @@ public: } else { - String address = url.toString (! isPost); + String address = url.toString (! addParametersToRequestBody); if (! address.contains ("://")) address = "http://" + address; MemoryBlock postData; - if (isPost) - WebInputStream::createHeadersAndPostData (url, headers, postData); + if (url.hasPOSTData()) + WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody); jbyteArray postDataArray = nullptr; @@ -406,7 +407,7 @@ public: stream = GlobalRef (LocalRef (env->CallStaticObjectMethod (HTTPStream, HTTPStream.createHTTPStream, javaString (address).get(), - (jboolean) isPost, + (jboolean) addParametersToRequestBody, postDataArray, javaString (headers).get(), (jint) timeOutMs, @@ -535,7 +536,8 @@ public: private: const URL url; - bool isContentURL, isPost, eofStreamReached = false; + const bool isContentURL, addParametersToRequestBody; + bool eofStreamReached = false; int numRedirectsToFollow = 5, timeOutMs = 0; String httpRequest, headers; StringPairArray responseHeaders; diff --git a/modules/juce_core/native/juce_curl_Network.cpp b/modules/juce_core/native/juce_curl_Network.cpp index 00d4283508..47e36e0135 100644 --- a/modules/juce_core/native/juce_curl_Network.cpp +++ b/modules/juce_core/native/juce_curl_Network.cpp @@ -110,9 +110,12 @@ private: class WebInputStream::Pimpl { public: - Pimpl (WebInputStream& ownerStream, const URL& urlToCopy, bool shouldUsePost) - : owner (ownerStream), url (urlToCopy), isPost (shouldUsePost), - httpRequest (isPost ? "POST" : "GET") + Pimpl (WebInputStream& ownerStream, const URL& urlToCopy, bool isPOSTLike) + : owner (ownerStream), + url (urlToCopy), + addParametersToRequestBody (isPOSTLike), + hasPOSTData (url.hasPOSTData()), + httpRequest (isPOSTLike || url.hasPOSTData() ? "POST" : "GET") { jassert (symbols); // Unable to load libcurl! @@ -220,7 +223,7 @@ public: //============================================================================== bool setOptions() { - auto address = url.toString (! isPost); + auto address = url.toString (! addParametersToRequestBody); curl_version_info_data* data = symbols->curl_version_info (CURLVERSION_NOW); jassert (data != nullptr); @@ -228,8 +231,8 @@ public: if (! requestHeaders.endsWithChar ('\n')) requestHeaders << "\r\n"; - if (isPost) - WebInputStream::createHeadersAndPostData (url, requestHeaders, headersAndPostData); + if (hasPOSTData) + WebInputStream::createHeadersAndPostData (url, requestHeaders, headersAndPostData, addParametersToRequestBody); if (! requestHeaders.endsWithChar ('\n')) requestHeaders << "\r\n"; @@ -244,7 +247,7 @@ public: && symbols->curl_easy_setopt (curl, CURLOPT_USERAGENT, userAgent.toRawUTF8()) == CURLE_OK && symbols->curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, (maxRedirects > 0 ? 1 : 0)) == CURLE_OK) { - if (isPost) + if (hasPOSTData) { if (symbols->curl_easy_setopt (curl, CURLOPT_READDATA, this) != CURLE_OK || symbols->curl_easy_setopt (curl, CURLOPT_READFUNCTION, StaticCurlRead) != CURLE_OK) @@ -256,7 +259,7 @@ public: } // handle special http request commands - bool hasSpecialRequestCmd = isPost ? (httpRequest != "POST") : (httpRequest != "GET"); + const auto hasSpecialRequestCmd = hasPOSTData ? (httpRequest != "POST") : (httpRequest != "GET"); if (hasSpecialRequestCmd) if (symbols->curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, httpRequest.toRawUTF8()) != CURLE_OK) @@ -323,7 +326,7 @@ public: listener = webInputListener; - if (isPost) + if (hasPOSTData) postBuffer = &headersAndPostData; size_t lastPos = static_cast (-1); @@ -342,7 +345,7 @@ public: singleStep(); // call callbacks if this is a post request - if (isPost && listener != nullptr && lastPos != postPosition) + if (hasPOSTData && listener != nullptr && lastPos != postPosition) { lastPos = postPosition; @@ -613,7 +616,7 @@ public: // Options int timeOutMs = 0; int maxRedirects = 5; - const bool isPost; + const bool addParametersToRequestBody, hasPOSTData; String httpRequest; //============================================================================== diff --git a/modules/juce_core/native/juce_linux_Network.cpp b/modules/juce_core/native/juce_linux_Network.cpp index f323139114..a786baa89d 100644 --- a/modules/juce_core/native/juce_linux_Network.cpp +++ b/modules/juce_core/native/juce_linux_Network.cpp @@ -70,10 +70,13 @@ bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& /* targetEma class WebInputStream::Pimpl { public: - Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool shouldUsePost) - : owner (pimplOwner), url (urlToCopy), - isPost (shouldUsePost), httpRequestCmd (shouldUsePost ? "POST" : "GET") - {} + Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool isPOSTLike) + : owner (pimplOwner), + url (urlToCopy), + addParametersToRequestBody (isPOSTLike), + httpRequestCmd (isPOSTLike || url.hasPOSTData() ? "POST" : "GET") + { + } ~Pimpl() { @@ -127,7 +130,7 @@ public: return false; } - address = url.toString (! isPost); + address = url.toString (! addParametersToRequestBody); statusCode = createConnection (listener, numRedirectsToFollow); return statusCode != 0; @@ -256,7 +259,7 @@ private: MemoryBlock postData; int64 contentLength = -1, position = 0; bool finished = false; - const bool isPost; + const bool addParametersToRequestBody; int timeOutMs = 0; int numRedirectsToFollow = 5; String httpRequestCmd; @@ -285,8 +288,8 @@ private: { closeSocket (false); - if (isPost) - WebInputStream::createHeadersAndPostData (url, headers, postData); + if (url.hasPOSTData()) + WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody); auto timeOutTime = Time::getMillisecondCounter(); @@ -367,8 +370,8 @@ private: freeaddrinfo (result); { - const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, proxyName, proxyPort, hostPath, - address, headers, postData, isPost, httpRequestCmd)); + const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, proxyName, proxyPort, hostPath, address, + headers, postData, httpRequestCmd)); if (! sendHeader (socketHandle, requestHeader, timeOutTime, owner, listener)) { @@ -474,7 +477,7 @@ private: const String& proxyName, int proxyPort, const String& hostPath, const String& originalURL, const String& userHeaders, const MemoryBlock& postData, - bool isPost, const String& httpRequestCmd) + const String& httpRequestCmd) { MemoryOutputStream header; @@ -488,15 +491,18 @@ private: "." JUCE_STRINGIFY(JUCE_BUILDNUMBER)); writeValueIfNotPresent (header, userHeaders, "Connection:", "close"); - if (isPost) - writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postData.getSize())); + const auto postDataSize = postData.getSize(); + const auto hasPostData = postDataSize > 0; + + if (hasPostData) + writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postDataSize)); if (userHeaders.isNotEmpty()) header << "\r\n" << userHeaders; header << "\r\n\r\n"; - if (isPost) + if (hasPostData) header << postData; return header.getMemoryBlock(); diff --git a/modules/juce_core/native/juce_mac_Network.mm b/modules/juce_core/native/juce_mac_Network.mm index a092a58398..3a77615c1a 100644 --- a/modules/juce_core/native/juce_mac_Network.mm +++ b/modules/juce_core/native/juce_mac_Network.mm @@ -943,9 +943,11 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE class WebInputStream::Pimpl { public: - Pimpl (WebInputStream& pimplOwner, const URL& urlToUse, bool shouldBePost) - : owner (pimplOwner), url (urlToUse), isPost (shouldBePost), - httpRequestCmd (shouldBePost ? "POST" : "GET") + Pimpl (WebInputStream& pimplOwner, const URL& urlToUse, bool isPOSTLike) + : owner (pimplOwner), + url (urlToUse), + addParametersToRequestBody (isPOSTLike), + httpRequestCmd (isPOSTLike || url.hasPOSTData() ? "POST" : "GET") { } @@ -1089,7 +1091,7 @@ private: MemoryBlock postData; int64 position = 0; bool finished = false; - const bool isPost; + const bool addParametersToRequestBody; int timeOutMs = 0; int numRedirectsToFollow = 5; String httpRequestCmd; @@ -1101,7 +1103,7 @@ private: { jassert (connection == nullptr); - if (NSURL* nsURL = [NSURL URLWithString: juceStringToNS (url.toString (! isPost))]) + if (NSURL* nsURL = [NSURL URLWithString: juceStringToNS (url.toString (! addParametersToRequestBody))]) { if (NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: nsURL cachePolicy: NSURLRequestReloadIgnoringLocalCacheData @@ -1111,9 +1113,9 @@ private: { [req setHTTPMethod: httpMethod]; - if (isPost) + if (url.hasPOSTData()) { - WebInputStream::createHeadersAndPostData (url, headers, postData); + WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody); if (postData.getSize() > 0) [req setHTTPBody: [NSData dataWithBytes: postData.getData() diff --git a/modules/juce_core/native/juce_win32_Network.cpp b/modules/juce_core/native/juce_win32_Network.cpp index c0289fc2fc..eb3a533bed 100644 --- a/modules/juce_core/native/juce_win32_Network.cpp +++ b/modules/juce_core/native/juce_win32_Network.cpp @@ -35,10 +35,13 @@ namespace juce class WebInputStream::Pimpl { public: - Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool shouldBePost) - : statusCode (0), owner (pimplOwner), url (urlToCopy), isPost (shouldBePost), - httpRequestCmd (isPost ? "POST" : "GET") - {} + Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool isPOSTLike) + : owner (pimplOwner), + url (urlToCopy), + addParametersToRequestBody (isPOSTLike), + httpRequestCmd (isPOSTLike || url.hasPOSTData() ? "POST" : "GET") + { + } ~Pimpl() { @@ -75,7 +78,7 @@ public: return false; } - String address = url.toString (! isPost); + auto address = url.toString (! addParametersToRequestBody); while (numRedirectsToFollow-- >= 0) { @@ -226,7 +229,7 @@ public: return true; } - int statusCode; + int statusCode = 0; private: //============================================================================== @@ -237,7 +240,7 @@ private: MemoryBlock postData; int64 position = 0; bool finished = false; - const bool isPost; + const bool addParametersToRequestBody; int timeOutMs = 0; String httpRequestCmd; int numRedirectsToFollow = 5; @@ -288,8 +291,8 @@ private: uc.lpszPassword = password; uc.dwPasswordLength = passwordNumChars; - if (isPost) - WebInputStream::createHeadersAndPostData (url, headers, postData); + if (url.hasPOSTData()) + WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody); if (InternetCrackUrl (address.toWideCharPointer(), 0, 0, &uc)) openConnection (uc, sessionHandle, address, listener); diff --git a/modules/juce_core/network/juce_URL.cpp b/modules/juce_core/network/juce_URL.cpp index 36307db8d6..e9dd39ab83 100644 --- a/modules/juce_core/network/juce_URL.cpp +++ b/modules/juce_core/network/juce_URL.cpp @@ -377,6 +377,11 @@ String URL::getFileName() const } #endif +URL::ParameterHandling URL::toHandling (bool usePostData) +{ + return usePostData ? ParameterHandling::inPostData : ParameterHandling::inAddress; +} + File URL::fileFromFileSchemeURL (const URL& fileURL) { if (! fileURL.isLocalFile()) @@ -447,7 +452,9 @@ URL URL::getChildURL (const String& subPath) const return u; } -void URL::createHeadersAndPostData (String& headers, MemoryBlock& postDataToWrite) const +void URL::createHeadersAndPostData (String& headers, + MemoryBlock& postDataToWrite, + bool addParametersToBody) const { MemoryOutputStream data (postDataToWrite, false); @@ -491,8 +498,10 @@ void URL::createHeadersAndPostData (String& headers, MemoryBlock& postDataToWrit } else { - data << URLHelpers::getMangledParameters (*this) - << postData; + if (addParametersToBody) + data << URLHelpers::getMangledParameters (*this) << postData; + else + data << postData; // if the user-supplied headers didn't contain a content-type, add one now.. if (! headers.containsIgnoreCase ("Content-Type")) @@ -656,73 +665,127 @@ private: } }; #endif +//============================================================================== +template +static URL::InputStreamOptions with (URL::InputStreamOptions options, Member&& member, Item&& item) +{ + options.*member = std::forward (item); + return options; +} + +URL::InputStreamOptions::InputStreamOptions (ParameterHandling handling) : parameterHandling (handling) {} + +URL::InputStreamOptions URL::InputStreamOptions::withProgressCallback (std::function cb) const +{ + return with (*this, &InputStreamOptions::progressCallback, std::move (cb)); +} + +URL::InputStreamOptions URL::InputStreamOptions::withExtraHeaders (const String& headers) const +{ + return with (*this, &InputStreamOptions::extraHeaders, headers); +} + +URL::InputStreamOptions URL::InputStreamOptions::withConnectionTimeoutMs (int timeout) const +{ + return with (*this, &InputStreamOptions::connectionTimeOutMs, timeout); +} + +URL::InputStreamOptions URL::InputStreamOptions::withResponseHeaders (StringPairArray* headers) const +{ + return with (*this, &InputStreamOptions::responseHeaders, headers); +} + +URL::InputStreamOptions URL::InputStreamOptions::withStatusCode (int* status) const +{ + return with (*this, &InputStreamOptions::statusCode, status); +} + +URL::InputStreamOptions URL::InputStreamOptions::withNumRedirectsToFollow (int numRedirects) const +{ + return with (*this, &InputStreamOptions::numRedirectsToFollow, numRedirects); +} + +URL::InputStreamOptions URL::InputStreamOptions::withHttpRequestCmd (const String& cmd) const +{ + return with (*this, &InputStreamOptions::httpRequestCmd, cmd); +} //============================================================================== -std::unique_ptr URL::createInputStream (bool usePostCommand, - OpenStreamProgressCallback* progressCallback, - void* progressCallbackContext, - String headers, - int timeOutMs, - StringPairArray* responseHeaders, - int* statusCode, - int numRedirectsToFollow, - String httpRequestCmd) const +std::unique_ptr URL::createInputStream (const InputStreamOptions& options) const { if (isLocalFile()) { #if JUCE_IOS // We may need to refresh the embedded bookmark. - return std::make_unique> (const_cast(*this)); + return std::make_unique> (const_cast (*this)); #else return getLocalFile().createInputStream(); #endif } - auto wi = std::make_unique (*this, usePostCommand); + auto webInputStream = [&] + { + const auto usePost = options.getParameterHandling() == ParameterHandling::inPostData; + auto stream = std::make_unique (*this, usePost); + + auto extraHeaders = options.getExtraHeaders(); + + if (extraHeaders.isNotEmpty()) + stream->withExtraHeaders (extraHeaders); + + auto timeout = options.getConnectionTimeoutMs(); + + if (timeout != 0) + stream->withConnectionTimeout (timeout); + + auto requestCmd = options.getHttpRequestCmd(); + + if (requestCmd.isNotEmpty()) + stream->withCustomRequestCommand (requestCmd); + + stream->withNumRedirectsToFollow (options.getNumRedirectsToFollow()); + + return stream; + }(); struct ProgressCallbackCaller : public WebInputStream::Listener { - ProgressCallbackCaller (OpenStreamProgressCallback* progressCallbackToUse, void* progressCallbackContextToUse) - : callback (progressCallbackToUse), data (progressCallbackContextToUse) - {} + ProgressCallbackCaller (std::function progressCallbackToUse) + : callback (std::move (progressCallbackToUse)) + { + } bool postDataSendProgress (WebInputStream&, int bytesSent, int totalBytes) override { - return callback (data, bytesSent, totalBytes); + return callback (bytesSent, totalBytes); } - OpenStreamProgressCallback* callback; - void* const data; + std::function callback; }; - std::unique_ptr callbackCaller - (progressCallback != nullptr ? new ProgressCallbackCaller (progressCallback, progressCallbackContext) : nullptr); + auto callbackCaller = [&options]() -> std::unique_ptr + { + if (auto progressCallback = options.getProgressCallback()) + return std::make_unique (progressCallback); - if (headers.isNotEmpty()) - wi->withExtraHeaders (headers); + return {}; + }(); - if (timeOutMs != 0) - wi->withConnectionTimeout (timeOutMs); + auto success = webInputStream->connect (callbackCaller.get()); - if (httpRequestCmd.isNotEmpty()) - wi->withCustomRequestCommand (httpRequestCmd); + if (auto* status = options.getStatusCode()) + *status = webInputStream->getStatusCode(); - wi->withNumRedirectsToFollow (numRedirectsToFollow); + if (auto* responseHeaders = options.getResponseHeaders()) + *responseHeaders = webInputStream->getResponseHeaders(); - bool success = wi->connect (callbackCaller.get()); - - if (statusCode != nullptr) - *statusCode = wi->getStatusCode(); - - if (responseHeaders != nullptr) - *responseHeaders = wi->getResponseHeaders(); - - if (! success || wi->isError()) + if (! success || webInputStream->isError()) return nullptr; - // Older GCCs complain about binding unique_ptr&& to unique_ptr - // if we just `return wi` here. - return std::unique_ptr (std::move (wi)); + // std::move() needed here for older compilers + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wredundant-move") + return std::move (webInputStream); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } #if JUCE_ANDROID @@ -752,7 +815,7 @@ std::unique_ptr URL::createOutputStream() const bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) const { const std::unique_ptr in (isLocalFile() ? getLocalFile().createInputStream() - : createInputStream (usePostCommand)); + : createInputStream (InputStreamOptions (toHandling (usePostCommand)))); if (in != nullptr) { @@ -766,7 +829,7 @@ bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) co String URL::readEntireTextStream (bool usePostCommand) const { const std::unique_ptr in (isLocalFile() ? getLocalFile().createInputStream() - : createInputStream (usePostCommand)); + : createInputStream (InputStreamOptions (toHandling (usePostCommand)))); if (in != nullptr) return in->readEntireStreamAsString(); @@ -911,4 +974,30 @@ bool URL::launchInDefaultBrowser() const return Process::openDocument (u, {}); } +//============================================================================== +std::unique_ptr URL::createInputStream (bool usePostCommand, + OpenStreamProgressCallback* cb, + void* context, + String headers, + int timeOutMs, + StringPairArray* responseHeaders, + int* statusCode, + int numRedirectsToFollow, + String httpRequestCmd) const +{ + std::function callback; + + if (cb != nullptr) + callback = [context, cb] (int sent, int total) { return cb (context, sent, total); }; + + return createInputStream (InputStreamOptions (toHandling (usePostCommand)) + .withProgressCallback (std::move (callback)) + .withExtraHeaders (headers) + .withConnectionTimeoutMs (timeOutMs) + .withResponseHeaders (responseHeaders) + .withStatusCode (statusCode) + .withNumRedirectsToFollow(numRedirectsToFollow) + .withHttpRequestCmd (httpRequestCmd)); +} + } // namespace juce diff --git a/modules/juce_core/network/juce_URL.h b/modules/juce_core/network/juce_URL.h index 2d03bd4bdf..293903a8b4 100644 --- a/modules/juce_core/network/juce_URL.h +++ b/modules/juce_core/network/juce_URL.h @@ -258,38 +258,33 @@ public: /** Returns a copy of this URL, with a block of data to send as the POST data. - If you're setting the POST data, be careful not to have any parameters set - as well, otherwise it'll all get thrown in together, and might not have the - desired effect. - If the URL already contains some POST data, this will replace it, rather than being appended to it. - This data will only be used if you specify a post operation when you call - createInputStream(). + If no HTTP command is set when calling createInputStream() to read from + this URL and some data has been set, it will do a POST request. */ URL withPOSTData (const String& postData) const; /** Returns a copy of this URL, with a block of data to send as the POST data. - If you're setting the POST data, be careful not to have any parameters set - as well, otherwise it'll all get thrown in together, and might not have the - desired effect. - If the URL already contains some POST data, this will replace it, rather than being appended to it. - This data will only be used if you specify a post operation when you call - createInputStream(). + If no HTTP command is set when calling createInputStream() to read from + this URL and some data has been set, it will do a POST request. */ URL withPOSTData (const MemoryBlock& postData) const; /** Returns the data that was set using withPOSTData(). */ String getPostData() const { return postData.toString(); } - /** Returns the data that was set using withPOSTData() as MemoryBlock. */ + /** Returns the data that was set using withPOSTData() as a MemoryBlock. */ const MemoryBlock& getPostDataAsMemoryBlock() const noexcept { return postData; } + /** Returns true if this URL has POST data set using withPOSTData(). */ + bool hasPOSTData() const noexcept { return postData.getSize() > 0; } + //============================================================================== /** Tries to launch the system's default browser to open the URL. @@ -308,13 +303,103 @@ public: */ static bool isProbablyAnEmailAddress (const String& possibleEmailAddress); - //============================================================================== - /** This callback function can be used by the createInputStream() method. + enum class ParameterHandling + { + inAddress, + inPostData + }; - It allows your app to receive progress updates during a lengthy POST operation. If you - want to continue the operation, this should return true, or false to abort. + //============================================================================== + /** Class used to create a set of options to pass to the createInputStream() method. + + You can chain together a series of calls to this class's methods to create + a set of whatever options you want to specify, e.g. + @code + if (auto inputStream = URL ("http://www.xyz.com/foobar") + .createInputStream (URL::InputStreamOptions (false).withConnectionTimeoutMs (1000) + .withNumRedirectsToFollow (0))) + { + ... + } + @endcode */ - using OpenStreamProgressCallback = bool (void* context, int bytesSent, int totalBytes); + class JUCE_API InputStreamOptions + { + public: + /** Constructor. + + If parameterHandling is ParameterHandling::inPostData and the URL contains some + POST data to send set via one of its withPOSTData() methods, the URL parameters + will be transferred via the request body data. Otherwise the parameters will + be added to the URL address. + */ + explicit InputStreamOptions (ParameterHandling parameterHandling); + + //============================================================================== + /** A callback function to keep track of the operation's progress. + + This can be useful for lengthy POST operations, so that you can provide user feedback. + */ + InputStreamOptions withProgressCallback (std::function progressCallback) const; + + /** A string that will be appended onto the headers that are used for the request. + + It must be a valid set of HTML header directives, separated by newlines. + */ + InputStreamOptions withExtraHeaders (const String& extraHeaders) const; + + /** Specifies a timeout for the request in milliseconds. + + If 0, this will use whatever default setting the OS chooses. If a negative + number, it will be infinite. + */ + InputStreamOptions withConnectionTimeoutMs (int connectionTimeoutMs) const; + + /** If this is non-null, all the (key, value) pairs received as headers + in the response will be stored in this array. + */ + InputStreamOptions withResponseHeaders (StringPairArray* responseHeaders) const; + + /** If this is non-null, it will get set to the http status code, if one + is known, or 0 if a code isn't available. + */ + InputStreamOptions withStatusCode (int* statusCode) const; + + /** Specifies the number of redirects that will be followed before returning a response. + + N.B. This will be ignored on Android which follows up to 5 redirects. + */ + InputStreamOptions withNumRedirectsToFollow (int numRedirectsToFollow) const; + + /** Specifies which HTTP request to use. + + If this is not set, then this will be determined by the value of `doPostLikeRequest` + or the presence of POST data set via URL::withPOSTData(). + */ + InputStreamOptions withHttpRequestCmd (const String& httpRequestCmd) const; + + //============================================================================== + ParameterHandling getParameterHandling() const noexcept { return parameterHandling; } + std::function getProgressCallback() const noexcept { return progressCallback; } + String getExtraHeaders() const noexcept { return extraHeaders; } + int getConnectionTimeoutMs() const noexcept { return connectionTimeOutMs; } + StringPairArray* getResponseHeaders() const noexcept { return responseHeaders; } + int* getStatusCode() const noexcept { return statusCode; } + int getNumRedirectsToFollow() const noexcept { return numRedirectsToFollow; } + String getHttpRequestCmd() const noexcept { return httpRequestCmd; } + + private: + //============================================================================== + const ParameterHandling parameterHandling; + + std::function progressCallback = nullptr; + String extraHeaders; + int connectionTimeOutMs = 0; + StringPairArray* responseHeaders = nullptr; + int* statusCode = nullptr; + int numRedirectsToFollow = 5; + String httpRequestCmd; + }; /** Attempts to open a stream that can read from this URL. @@ -330,43 +415,11 @@ public: If the URL represents a local file, then this method simply returns a FileInputStream. - @param doPostLikeRequest if true, the parameters added to this class will be transferred - via the HTTP headers which is typical for POST requests. Otherwise - the parameters will be added to the URL address. Additionally, - if the parameter httpRequestCmd is not specified (or empty) then this - parameter will determine which HTTP request command will be used - (POST or GET). - @param progressCallback if this is not a nullptr, it lets you supply a callback function - to keep track of the operation's progress. This can be useful - for lengthy POST operations, so that you can provide user feedback. - @param progressCallbackContext if a callback is specified, this value will be passed to - the function - @param extraHeaders if not empty, this string is appended onto the headers that - are used for the request. It must therefore be a valid set of HTML - header directives, separated by newlines. - @param connectionTimeOutMs if 0, this will use whatever default setting the OS chooses. If - a negative number, it will be infinite. Otherwise it specifies a - time in milliseconds. - @param responseHeaders if this is non-null, all the (key, value) pairs received as headers - in the response will be stored in this array - @param statusCode if this is non-null, it will get set to the http status code, if one - is known, or 0 if a code isn't available - @param numRedirectsToFollow specifies the number of redirects that will be followed before - returning a response (ignored for Android which follows up to 5 redirects) - @param httpRequestCmd Specify which HTTP Request to use. If this is empty, then doPostRequest - will determine the HTTP request. + @param options a set of options that will be used when opening the stream. - @returns a valid input stream, or nullptr if there was an error trying to open it. - */ - std::unique_ptr createInputStream (bool doPostLikeRequest, - OpenStreamProgressCallback* progressCallback = nullptr, - void* progressCallbackContext = nullptr, - String extraHeaders = {}, - int connectionTimeOutMs = 0, - StringPairArray* responseHeaders = nullptr, - int* statusCode = nullptr, - int numRedirectsToFollow = 5, - String httpRequestCmd = {}) const; + @returns a valid input stream, or nullptr if there was an error trying to open it. + */ + std::unique_ptr createInputStream (const InputStreamOptions& options) const; /** Attempts to open an output stream to a URL for writing @@ -558,6 +611,25 @@ public: */ static URL createWithoutParsing (const String& url); + //============================================================================== + using OpenStreamProgressCallback = bool (void* context, int bytesSent, int totalBytes); + + /** This method has been deprecated. + + New code should use the method which takes an InputStreamOptions argument instead. + + @see InputStreamOptions + */ + std::unique_ptr createInputStream (bool doPostLikeRequest, + OpenStreamProgressCallback* progressCallback = nullptr, + void* progressCallbackContext = nullptr, + String extraHeaders = {}, + int connectionTimeOutMs = 0, + StringPairArray* responseHeaders = nullptr, + int* statusCode = nullptr, + int numRedirectsToFollow = 5, + String httpRequestCmd = {}) const; + private: //============================================================================== #if JUCE_IOS @@ -594,9 +666,10 @@ private: URL (const String&, int); void init(); void addParameter (const String&, const String&); - void createHeadersAndPostData (String&, MemoryBlock&) const; + void createHeadersAndPostData (String&, MemoryBlock&, bool) const; URL withUpload (Upload*) const; + static ParameterHandling toHandling (bool); static File fileFromFileSchemeURL (const URL&); String getDomainInternal (bool) const; diff --git a/modules/juce_core/network/juce_WebInputStream.cpp b/modules/juce_core/network/juce_WebInputStream.cpp index 38eda1502d..5d6d3a04bf 100644 --- a/modules/juce_core/network/juce_WebInputStream.cpp +++ b/modules/juce_core/network/juce_WebInputStream.cpp @@ -24,13 +24,12 @@ namespace juce { WebInputStream::WebInputStream (const URL& url, const bool usePost) - : pimpl (new Pimpl (*this, url, usePost)), hasCalledConnect (false) + : pimpl (std::make_unique (*this, url, usePost)) { } WebInputStream::~WebInputStream() { - delete pimpl; } WebInputStream& WebInputStream::withExtraHeaders (const String& extra) { pimpl->withExtraHeaders (extra); return *this; } @@ -60,28 +59,41 @@ bool WebInputStream::connect (Listener* listener) StringPairArray WebInputStream::parseHttpHeaders (const String& headerData) { StringPairArray headerPairs; - StringArray headerLines = StringArray::fromLines (headerData); + auto headerLines = StringArray::fromLines (headerData); // ignore the first line as this is the status line for (int i = 1; i < headerLines.size(); ++i) { - const String& headersEntry = headerLines[i]; + const auto& headersEntry = headerLines[i]; if (headersEntry.isNotEmpty()) { - const String key (headersEntry.upToFirstOccurrenceOf (": ", false, false)); - const String value (headersEntry.fromFirstOccurrenceOf (": ", false, false)); - const String previousValue (headerPairs [key]); - headerPairs.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); + const auto key = headersEntry.upToFirstOccurrenceOf (": ", false, false); + + auto value = [&headersEntry, &headerPairs, &key] + { + const auto currentValue = headersEntry.fromFirstOccurrenceOf (": ", false, false); + const auto previousValue = headerPairs [key]; + + if (previousValue.isNotEmpty()) + return previousValue + "," + currentValue; + + return currentValue; + }(); + + headerPairs.set (key, value); } } return headerPairs; } -void WebInputStream::createHeadersAndPostData (const URL& aURL, String& headers, MemoryBlock& data) +void WebInputStream::createHeadersAndPostData (const URL& aURL, + String& headers, + MemoryBlock& data, + bool addParametersToBody) { - aURL.createHeadersAndPostData (headers, data); + aURL.createHeadersAndPostData (headers, data, addParametersToBody); } } // namespace juce diff --git a/modules/juce_core/network/juce_WebInputStream.h b/modules/juce_core/network/juce_WebInputStream.h index 126fcecf68..f7cf04292a 100644 --- a/modules/juce_core/network/juce_WebInputStream.h +++ b/modules/juce_core/network/juce_WebInputStream.h @@ -220,14 +220,14 @@ class JUCE_API WebInputStream : public InputStream bool setPosition (int64 wantedPos) override; private: - static void createHeadersAndPostData (const URL&, String&, MemoryBlock&); - static StringPairArray parseHttpHeaders (const String& headerData); + static void createHeadersAndPostData (const URL&, String&, MemoryBlock&, bool); + static StringPairArray parseHttpHeaders (const String&); class Pimpl; friend class Pimpl; - Pimpl* const pimpl; - bool hasCalledConnect; + std::unique_ptr pimpl; + bool hasCalledConnect = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) };