| @@ -666,38 +666,103 @@ public final class JuceDemo extends Activity | |||||
| public static final HTTPStream createHTTPStream (String address, | public static final HTTPStream createHTTPStream (String address, | ||||
| boolean isPost, byte[] postData, String headers, | boolean isPost, byte[] postData, String headers, | ||||
| int timeOutMs, int[] statusCode, | int timeOutMs, int[] statusCode, | ||||
| StringBuffer responseHeaders) | |||||
| StringBuffer responseHeaders, | |||||
| int numRedirectsToFollow) | |||||
| { | { | ||||
| try | |||||
| // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL) | |||||
| if (timeOutMs < 0) | |||||
| timeOutMs = 0; | |||||
| else if (timeOutMs == 0) | |||||
| timeOutMs = 30000; | |||||
| // headers - 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. | |||||
| // So convert headers string to an array, with an element for each line | |||||
| String headerLines[] = headers.split("\\n"); | |||||
| for (;;) | |||||
| { | { | ||||
| HttpURLConnection connection = (HttpURLConnection) (new URL(address) | |||||
| .openConnection()); | |||||
| if (connection != null) | |||||
| try | |||||
| { | { | ||||
| try | |||||
| HttpURLConnection connection = (HttpURLConnection) (new URL(address).openConnection()); | |||||
| if (connection != null) | |||||
| { | { | ||||
| if (isPost) | |||||
| try | |||||
| { | { | ||||
| connection.setRequestMethod("POST"); | |||||
| connection.setConnectTimeout(timeOutMs); | |||||
| connection.setDoOutput(true); | |||||
| connection.setChunkedStreamingMode(0); | |||||
| OutputStream out = connection.getOutputStream(); | |||||
| out.write(postData); | |||||
| out.flush(); | |||||
| } | |||||
| connection.setInstanceFollowRedirects (false); | |||||
| connection.setConnectTimeout (timeOutMs); | |||||
| connection.setReadTimeout (timeOutMs); | |||||
| return new HTTPStream (connection, statusCode, responseHeaders); | |||||
| } | |||||
| catch (Throwable e) | |||||
| { | |||||
| connection.disconnect(); | |||||
| // Set request headers | |||||
| for (int i = 0; i < headerLines.length; ++i) | |||||
| { | |||||
| int pos = headerLines[i].indexOf (":"); | |||||
| if (pos > 0 && pos < headerLines[i].length()) | |||||
| { | |||||
| String field = headerLines[i].substring (0, pos); | |||||
| String value = headerLines[i].substring (pos + 1); | |||||
| if (value.length() > 0) | |||||
| connection.setRequestProperty (field, value); | |||||
| } | |||||
| } | |||||
| if (isPost) | |||||
| { | |||||
| connection.setRequestMethod ("POST"); | |||||
| connection.setDoOutput (true); | |||||
| if (postData != null) | |||||
| { | |||||
| OutputStream out = connection.getOutputStream(); | |||||
| out.write(postData); | |||||
| out.flush(); | |||||
| } | |||||
| } | |||||
| HTTPStream httpStream = new HTTPStream (connection, statusCode, responseHeaders); | |||||
| // Process redirect & continue as necessary | |||||
| int status = statusCode[0]; | |||||
| if (--numRedirectsToFollow >= 0 | |||||
| && (status == 301 || status == 302 || status == 303 || status == 307)) | |||||
| { | |||||
| // Assumes only one occurrence of "Location" | |||||
| int pos1 = responseHeaders.indexOf ("Location:") + 10; | |||||
| int pos2 = responseHeaders.indexOf ("\n", pos1); | |||||
| if (pos2 > pos1) | |||||
| { | |||||
| String newLocation = responseHeaders.substring(pos1, pos2); | |||||
| // Handle newLocation whether it's absolute or relative | |||||
| URL baseUrl = new URL (address); | |||||
| URL newUrl = new URL (baseUrl, newLocation); | |||||
| String transformedNewLocation = newUrl.toString(); | |||||
| if (transformedNewLocation != address) | |||||
| { | |||||
| address = transformedNewLocation; | |||||
| // Clear responseHeaders before next iteration | |||||
| responseHeaders.delete (0, responseHeaders.length()); | |||||
| continue; | |||||
| } | |||||
| } | |||||
| } | |||||
| return httpStream; | |||||
| } | |||||
| catch (Throwable e) | |||||
| { | |||||
| connection.disconnect(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | |||||
| catch (Throwable e) {} | |||||
| catch (Throwable e) {} | |||||
| return null; | |||||
| return null; | |||||
| } | |||||
| } | } | ||||
| public final void launchURL (String url) | public final void launchURL (String url) | ||||
| @@ -53,6 +53,10 @@ extern bool juceDemoRepaintDebuggingActive; | |||||
| //#define JUCE_WASAPI | //#define JUCE_WASAPI | ||||
| #endif | #endif | ||||
| #ifndef JUCE_WASAPI_EXCLUSIVE | |||||
| //#define JUCE_WASAPI_EXCLUSIVE | |||||
| #endif | |||||
| #ifndef JUCE_DIRECTSOUND | #ifndef JUCE_DIRECTSOUND | ||||
| //#define JUCE_DIRECTSOUND | //#define JUCE_DIRECTSOUND | ||||
| #endif | #endif | ||||
| @@ -666,38 +666,103 @@ public final class JuceAppActivity extends Activity | |||||
| public static final HTTPStream createHTTPStream (String address, | public static final HTTPStream createHTTPStream (String address, | ||||
| boolean isPost, byte[] postData, String headers, | boolean isPost, byte[] postData, String headers, | ||||
| int timeOutMs, int[] statusCode, | int timeOutMs, int[] statusCode, | ||||
| StringBuffer responseHeaders) | |||||
| StringBuffer responseHeaders, | |||||
| int numRedirectsToFollow) | |||||
| { | { | ||||
| try | |||||
| // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL) | |||||
| if (timeOutMs < 0) | |||||
| timeOutMs = 0; | |||||
| else if (timeOutMs == 0) | |||||
| timeOutMs = 30000; | |||||
| // headers - 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. | |||||
| // So convert headers string to an array, with an element for each line | |||||
| String headerLines[] = headers.split("\\n"); | |||||
| for (;;) | |||||
| { | { | ||||
| HttpURLConnection connection = (HttpURLConnection) (new URL(address) | |||||
| .openConnection()); | |||||
| if (connection != null) | |||||
| try | |||||
| { | { | ||||
| try | |||||
| HttpURLConnection connection = (HttpURLConnection) (new URL(address).openConnection()); | |||||
| if (connection != null) | |||||
| { | { | ||||
| if (isPost) | |||||
| try | |||||
| { | { | ||||
| connection.setRequestMethod("POST"); | |||||
| connection.setConnectTimeout(timeOutMs); | |||||
| connection.setDoOutput(true); | |||||
| connection.setChunkedStreamingMode(0); | |||||
| OutputStream out = connection.getOutputStream(); | |||||
| out.write(postData); | |||||
| out.flush(); | |||||
| } | |||||
| connection.setInstanceFollowRedirects (false); | |||||
| connection.setConnectTimeout (timeOutMs); | |||||
| connection.setReadTimeout (timeOutMs); | |||||
| return new HTTPStream (connection, statusCode, responseHeaders); | |||||
| } | |||||
| catch (Throwable e) | |||||
| { | |||||
| connection.disconnect(); | |||||
| // Set request headers | |||||
| for (int i = 0; i < headerLines.length; ++i) | |||||
| { | |||||
| int pos = headerLines[i].indexOf (":"); | |||||
| if (pos > 0 && pos < headerLines[i].length()) | |||||
| { | |||||
| String field = headerLines[i].substring (0, pos); | |||||
| String value = headerLines[i].substring (pos + 1); | |||||
| if (value.length() > 0) | |||||
| connection.setRequestProperty (field, value); | |||||
| } | |||||
| } | |||||
| if (isPost) | |||||
| { | |||||
| connection.setRequestMethod ("POST"); | |||||
| connection.setDoOutput (true); | |||||
| if (postData != null) | |||||
| { | |||||
| OutputStream out = connection.getOutputStream(); | |||||
| out.write(postData); | |||||
| out.flush(); | |||||
| } | |||||
| } | |||||
| HTTPStream httpStream = new HTTPStream (connection, statusCode, responseHeaders); | |||||
| // Process redirect & continue as necessary | |||||
| int status = statusCode[0]; | |||||
| if (--numRedirectsToFollow >= 0 | |||||
| && (status == 301 || status == 302 || status == 303 || status == 307)) | |||||
| { | |||||
| // Assumes only one occurrence of "Location" | |||||
| int pos1 = responseHeaders.indexOf ("Location:") + 10; | |||||
| int pos2 = responseHeaders.indexOf ("\n", pos1); | |||||
| if (pos2 > pos1) | |||||
| { | |||||
| String newLocation = responseHeaders.substring(pos1, pos2); | |||||
| // Handle newLocation whether it's absolute or relative | |||||
| URL baseUrl = new URL (address); | |||||
| URL newUrl = new URL (baseUrl, newLocation); | |||||
| String transformedNewLocation = newUrl.toString(); | |||||
| if (transformedNewLocation != address) | |||||
| { | |||||
| address = transformedNewLocation; | |||||
| // Clear responseHeaders before next iteration | |||||
| responseHeaders.delete (0, responseHeaders.length()); | |||||
| continue; | |||||
| } | |||||
| } | |||||
| } | |||||
| return httpStream; | |||||
| } | |||||
| catch (Throwable e) | |||||
| { | |||||
| connection.disconnect(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | |||||
| catch (Throwable e) {} | |||||
| catch (Throwable e) {} | |||||
| return null; | |||||
| return null; | |||||
| } | |||||
| } | } | ||||
| public final void launchURL (String url) | public final void launchURL (String url) | ||||
| @@ -378,7 +378,7 @@ struct AndroidThreadScope | |||||
| METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ | METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ | ||||
| METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ | METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ | ||||
| METHOD (renderGlyph, "renderGlyph", "(CLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \ | METHOD (renderGlyph, "renderGlyph", "(CLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \ | ||||
| STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \ | |||||
| STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;I)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \ | |||||
| METHOD (launchURL, "launchURL", "(Ljava/lang/String;)V") \ | METHOD (launchURL, "launchURL", "(Ljava/lang/String;)V") \ | ||||
| METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ | METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ | ||||
| METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ | METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ | ||||
| @@ -70,7 +70,7 @@ class WebInputStream : public InputStream | |||||
| public: | public: | ||||
| WebInputStream (String address, bool isPost, const MemoryBlock& postData, | WebInputStream (String address, bool isPost, const MemoryBlock& postData, | ||||
| URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | ||||
| const String& headers, int timeOutMs, StringPairArray* responseHeaders) | |||||
| const String& headers, int timeOutMs, StringPairArray* responseHeaders, const int numRedirectsToFollow) | |||||
| : statusCode (0) | : statusCode (0) | ||||
| { | { | ||||
| if (! address.contains ("://")) | if (! address.contains ("://")) | ||||
| @@ -103,7 +103,8 @@ public: | |||||
| javaString (headers).get(), | javaString (headers).get(), | ||||
| (jint) timeOutMs, | (jint) timeOutMs, | ||||
| statusCodeArray, | statusCodeArray, | ||||
| responseHeaderBuffer.get())); | |||||
| responseHeaderBuffer.get(), | |||||
| (jint) numRedirectsToFollow)); | |||||
| jint* const statusCodeElements = env->GetIntArrayElements (statusCodeArray, 0); | jint* const statusCodeElements = env->GetIntArrayElements (statusCodeArray, 0); | ||||
| statusCode = statusCodeElements[0]; | statusCode = statusCodeElements[0]; | ||||
| @@ -74,12 +74,13 @@ class WebInputStream : public InputStream | |||||
| public: | public: | ||||
| WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | ||||
| URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | ||||
| const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) | |||||
| const String& headers_, int timeOutMs_, StringPairArray* responseHeaders, | |||||
| const int maxRedirects) | |||||
| : statusCode (0), socketHandle (-1), levelsOfRedirection (0), | : statusCode (0), socketHandle (-1), levelsOfRedirection (0), | ||||
| address (address_), headers (headers_), postData (postData_), position (0), | address (address_), headers (headers_), postData (postData_), position (0), | ||||
| finished (false), isPost (isPost_), timeOutMs (timeOutMs_) | |||||
| finished (false), isPost (isPost_), timeOutMs (timeOutMs_), numRedirectsToFollow (maxRedirects) | |||||
| { | { | ||||
| statusCode = createConnection (progressCallback, progressCallbackContext); | |||||
| statusCode = createConnection (progressCallback, progressCallbackContext, numRedirectsToFollow); | |||||
| if (responseHeaders != nullptr && ! isError()) | if (responseHeaders != nullptr && ! isError()) | ||||
| { | { | ||||
| @@ -147,7 +148,7 @@ public: | |||||
| { | { | ||||
| closeSocket(); | closeSocket(); | ||||
| position = 0; | position = 0; | ||||
| statusCode = createConnection (0, 0); | |||||
| statusCode = createConnection (0, 0, numRedirectsToFollow); | |||||
| } | } | ||||
| skipNextBytes (wantedPos - position); | skipNextBytes (wantedPos - position); | ||||
| @@ -168,24 +169,27 @@ private: | |||||
| bool finished; | bool finished; | ||||
| const bool isPost; | const bool isPost; | ||||
| const int timeOutMs; | const int timeOutMs; | ||||
| const int numRedirectsToFollow; | |||||
| void closeSocket() | |||||
| void closeSocket (bool resetLevelsOfRedirection = true) | |||||
| { | { | ||||
| if (socketHandle >= 0) | if (socketHandle >= 0) | ||||
| close (socketHandle); | close (socketHandle); | ||||
| socketHandle = -1; | socketHandle = -1; | ||||
| levelsOfRedirection = 0; | |||||
| if (resetLevelsOfRedirection) | |||||
| levelsOfRedirection = 0; | |||||
| } | } | ||||
| int createConnection (URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) | |||||
| int createConnection (URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | |||||
| const int numRedirectsToFollow) | |||||
| { | { | ||||
| closeSocket(); | |||||
| closeSocket (false); | |||||
| uint32 timeOutTime = Time::getMillisecondCounter(); | uint32 timeOutTime = Time::getMillisecondCounter(); | ||||
| if (timeOutMs == 0) | if (timeOutMs == 0) | ||||
| timeOutTime += 60000; | |||||
| timeOutTime += 30000; | |||||
| else if (timeOutMs < 0) | else if (timeOutMs < 0) | ||||
| timeOutTime = 0xffffffff; | timeOutTime = 0xffffffff; | ||||
| else | else | ||||
| @@ -273,28 +277,28 @@ private: | |||||
| const int status = responseHeader.fromFirstOccurrenceOf (" ", false, false) | const int status = responseHeader.fromFirstOccurrenceOf (" ", false, false) | ||||
| .substring (0, 3).getIntValue(); | .substring (0, 3).getIntValue(); | ||||
| //int contentLength = findHeaderItem (lines, "Content-Length:").getIntValue(); | |||||
| //bool isChunked = findHeaderItem (lines, "Transfer-Encoding:").equalsIgnoreCase ("chunked"); | |||||
| String location (findHeaderItem (headerLines, "Location:")); | String location (findHeaderItem (headerLines, "Location:")); | ||||
| if (status >= 300 && status < 400 | |||||
| if (++levelsOfRedirection <= numRedirectsToFollow | |||||
| && status >= 300 && status < 400 | |||||
| && location.isNotEmpty() && location != address) | && location.isNotEmpty() && location != address) | ||||
| { | { | ||||
| if (! location.startsWithIgnoreCase ("http://")) | |||||
| location = "http://" + location; | |||||
| if (++levelsOfRedirection <= 3) | |||||
| if (! (location.startsWithIgnoreCase ("http://") | |||||
| || location.startsWithIgnoreCase ("https://") | |||||
| || location.startsWithIgnoreCase ("ftp://"))) | |||||
| { | { | ||||
| address = location; | |||||
| return createConnection (progressCallback, progressCallbackContext); | |||||
| // The following is a bit dodgy. Ideally, we should do a proper transform of the relative URI to a target URI | |||||
| if (location.startsWithChar ('/')) | |||||
| location = URL (address).withNewSubPath (location).toString (true); | |||||
| else | |||||
| location = address + "/" + location; | |||||
| } | } | ||||
| address = location; | |||||
| return createConnection (progressCallback, progressCallbackContext, numRedirectsToFollow); | |||||
| } | } | ||||
| else | |||||
| { | |||||
| levelsOfRedirection = 0; | |||||
| return status; | |||||
| } | |||||
| return status; | |||||
| } | } | ||||
| closeSocket(); | closeSocket(); | ||||
| @@ -363,10 +367,14 @@ private: | |||||
| writeValueIfNotPresent (header, userHeaders, "Connection:", "close"); | writeValueIfNotPresent (header, userHeaders, "Connection:", "close"); | ||||
| if (isPost) | if (isPost) | ||||
| { | |||||
| writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postData.getSize())); | writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postData.getSize())); | ||||
| header << "\r\n" << userHeaders | |||||
| << "\r\n" << postData; | |||||
| header << userHeaders << "\r\n" << postData; | |||||
| } | |||||
| else | |||||
| { | |||||
| header << "\r\n" << userHeaders << "\r\n"; | |||||
| } | |||||
| return header.getMemoryBlock(); | return header.getMemoryBlock(); | ||||
| } | } | ||||
| @@ -115,7 +115,7 @@ bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& targetEmailA | |||||
| class URLConnectionState : public Thread | class URLConnectionState : public Thread | ||||
| { | { | ||||
| public: | public: | ||||
| URLConnectionState (NSURLRequest* req) | |||||
| URLConnectionState (NSURLRequest* req, const int maxRedirects) | |||||
| : Thread ("http connection"), | : Thread ("http connection"), | ||||
| contentLength (-1), | contentLength (-1), | ||||
| delegate (nil), | delegate (nil), | ||||
| @@ -126,7 +126,9 @@ public: | |||||
| statusCode (0), | statusCode (0), | ||||
| initialised (false), | initialised (false), | ||||
| hasFailed (false), | hasFailed (false), | ||||
| hasFinished (false) | |||||
| hasFinished (false), | |||||
| numRedirectsToFollow (maxRedirects), | |||||
| numRedirects (0) | |||||
| { | { | ||||
| static DelegateClass cls; | static DelegateClass cls; | ||||
| delegate = [cls.createInstance() init]; | delegate = [cls.createInstance() init]; | ||||
| @@ -215,6 +217,19 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| NSURLRequest* willSendRequest (NSURLRequest* newRequest, NSURLResponse* redirectResponse) | |||||
| { | |||||
| if (redirectResponse != nullptr) | |||||
| { | |||||
| if (numRedirects >= numRedirectsToFollow) | |||||
| return nil; // Cancel redirect and allow connection to continue | |||||
| ++numRedirects; | |||||
| } | |||||
| return newRequest; | |||||
| } | |||||
| void didFailWithError (NSError* error) | void didFailWithError (NSError* error) | ||||
| { | { | ||||
| DBG (nsStringToJuce ([error description])); (void) error; | DBG (nsStringToJuce ([error description])); (void) error; | ||||
| @@ -263,6 +278,8 @@ public: | |||||
| NSDictionary* headers; | NSDictionary* headers; | ||||
| int statusCode; | int statusCode; | ||||
| bool initialised, hasFailed, hasFinished; | bool initialised, hasFailed, hasFinished; | ||||
| const int numRedirectsToFollow; | |||||
| int numRedirects; | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -278,7 +295,7 @@ private: | |||||
| addMethod (@selector (connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:), | addMethod (@selector (connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:), | ||||
| connectionDidSendBodyData, "v@:@iii"); | connectionDidSendBodyData, "v@:@iii"); | ||||
| addMethod (@selector (connectionDidFinishLoading:), connectionDidFinishLoading, "v@:@"); | addMethod (@selector (connectionDidFinishLoading:), connectionDidFinishLoading, "v@:@"); | ||||
| addMethod (@selector (connection:willSendRequest:redirectResponse:), willSendRequest, "@@:@@"); | |||||
| addMethod (@selector (connection:willSendRequest:redirectResponse:), willSendRequest, "@@:@@@"); | |||||
| registerClass(); | registerClass(); | ||||
| } | } | ||||
| @@ -302,9 +319,9 @@ private: | |||||
| getState (self)->didReceiveData (newData); | getState (self)->didReceiveData (newData); | ||||
| } | } | ||||
| static NSURLRequest* willSendRequest (id, SEL, NSURLConnection*, NSURLRequest* request, NSURLResponse*) | |||||
| static NSURLRequest* willSendRequest (id self, SEL, NSURLConnection*, NSURLRequest* request, NSURLResponse* response) | |||||
| { | { | ||||
| return request; | |||||
| return getState (self)->willSendRequest (request, response); | |||||
| } | } | ||||
| static void connectionDidSendBodyData (id self, SEL, NSURLConnection*, NSInteger, NSInteger totalBytesWritten, NSInteger totalBytesExpected) | static void connectionDidSendBodyData (id self, SEL, NSURLConnection*, NSInteger, NSInteger totalBytesWritten, NSInteger totalBytesExpected) | ||||
| @@ -328,23 +345,27 @@ class WebInputStream : public InputStream | |||||
| public: | public: | ||||
| WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | ||||
| URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | ||||
| const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) | |||||
| const String& headers_, int timeOutMs_, StringPairArray* responseHeaders, | |||||
| const int numRedirectsToFollow_) | |||||
| : statusCode (0), address (address_), headers (headers_), postData (postData_), position (0), | : statusCode (0), address (address_), headers (headers_), postData (postData_), position (0), | ||||
| finished (false), isPost (isPost_), timeOutMs (timeOutMs_) | |||||
| finished (false), isPost (isPost_), timeOutMs (timeOutMs_), numRedirectsToFollow (numRedirectsToFollow_) | |||||
| { | { | ||||
| JUCE_AUTORELEASEPOOL | JUCE_AUTORELEASEPOOL | ||||
| { | { | ||||
| createConnection (progressCallback, progressCallbackContext); | createConnection (progressCallback, progressCallbackContext); | ||||
| if (responseHeaders != nullptr && connection != nullptr && connection->headers != nil) | |||||
| if (connection != nullptr && connection->headers != nil) | |||||
| { | { | ||||
| statusCode = connection->statusCode; | statusCode = connection->statusCode; | ||||
| NSEnumerator* enumerator = [connection->headers keyEnumerator]; | |||||
| if (responseHeaders != nullptr) | |||||
| { | |||||
| NSEnumerator* enumerator = [connection->headers keyEnumerator]; | |||||
| while (NSString* key = [enumerator nextObject]) | |||||
| responseHeaders->set (nsStringToJuce (key), | |||||
| nsStringToJuce ((NSString*) [connection->headers objectForKey: key])); | |||||
| while (NSString* key = [enumerator nextObject]) | |||||
| responseHeaders->set (nsStringToJuce (key), | |||||
| nsStringToJuce ((NSString*) [connection->headers objectForKey: key])); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -403,6 +424,7 @@ private: | |||||
| bool finished; | bool finished; | ||||
| const bool isPost; | const bool isPost; | ||||
| const int timeOutMs; | const int timeOutMs; | ||||
| const int numRedirectsToFollow; | |||||
| void createConnection (URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) | void createConnection (URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) | ||||
| { | { | ||||
| @@ -434,7 +456,7 @@ private: | |||||
| [req setHTTPBody: [NSData dataWithBytes: postData.getData() | [req setHTTPBody: [NSData dataWithBytes: postData.getData() | ||||
| length: postData.getSize()]]; | length: postData.getSize()]]; | ||||
| connection = new URLConnectionState (req); | |||||
| connection = new URLConnectionState (req, numRedirectsToFollow); | |||||
| if (! connection->start (progressCallback, progressCallbackContext)) | if (! connection->start (progressCallback, progressCallbackContext)) | ||||
| connection = nullptr; | connection = nullptr; | ||||
| @@ -40,12 +40,12 @@ class WebInputStream : public InputStream | |||||
| public: | public: | ||||
| WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | ||||
| URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | ||||
| const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) | |||||
| const String& headers_, int timeOutMs_, StringPairArray* responseHeaders, int numRedirectsToFollow) | |||||
| : statusCode (0), connection (0), request (0), | : statusCode (0), connection (0), request (0), | ||||
| address (address_), headers (headers_), postData (postData_), position (0), | address (address_), headers (headers_), postData (postData_), position (0), | ||||
| finished (false), isPost (isPost_), timeOutMs (timeOutMs_) | finished (false), isPost (isPost_), timeOutMs (timeOutMs_) | ||||
| { | { | ||||
| for (int maxRedirects = 10; --maxRedirects >= 0;) | |||||
| while (numRedirectsToFollow-- >= 0) | |||||
| { | { | ||||
| createConnection (progressCallback, progressCallbackContext); | createConnection (progressCallback, progressCallbackContext); | ||||
| @@ -88,9 +88,22 @@ public: | |||||
| { | { | ||||
| statusCode = (int) status; | statusCode = (int) status; | ||||
| if (status == 301 || status == 302 || status == 303 || status == 307) | |||||
| if (numRedirectsToFollow >= 0 | |||||
| && (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307)) | |||||
| { | { | ||||
| const String newLocation (headers["Location"]); | |||||
| String newLocation (headers["Location"]); | |||||
| // Check whether location is a relative URI - this is an incomplete test for relative path, | |||||
| // but we'll use it for now (valid protocols for this implementation are http, https & ftp) | |||||
| if (! (newLocation.startsWithIgnoreCase ("http://") | |||||
| || newLocation.startsWithIgnoreCase ("https://") | |||||
| || newLocation.startsWithIgnoreCase ("ftp://"))) | |||||
| { | |||||
| if (newLocation.startsWithChar ('/')) | |||||
| newLocation = URL (address).withNewSubPath (newLocation).toString (true); | |||||
| else | |||||
| newLocation = address + "/" + newLocation; | |||||
| } | |||||
| if (newLocation.isNotEmpty() && newLocation != address) | if (newLocation.isNotEmpty() && newLocation != address) | ||||
| { | { | ||||
| @@ -334,7 +334,8 @@ InputStream* URL::createInputStream (const bool usePostCommand, | |||||
| String headers, | String headers, | ||||
| const int timeOutMs, | const int timeOutMs, | ||||
| StringPairArray* const responseHeaders, | StringPairArray* const responseHeaders, | ||||
| int* statusCode) const | |||||
| int* statusCode, | |||||
| const int numRedirectsToFollow) const | |||||
| { | { | ||||
| MemoryBlock headersAndPostData; | MemoryBlock headersAndPostData; | ||||
| @@ -350,7 +351,8 @@ InputStream* URL::createInputStream (const bool usePostCommand, | |||||
| ScopedPointer<WebInputStream> wi (new WebInputStream (toString (! usePostCommand), | ScopedPointer<WebInputStream> wi (new WebInputStream (toString (! usePostCommand), | ||||
| usePostCommand, headersAndPostData, | usePostCommand, headersAndPostData, | ||||
| progressCallback, progressCallbackContext, | progressCallback, progressCallbackContext, | ||||
| headers, timeOutMs, responseHeaders)); | |||||
| headers, timeOutMs, responseHeaders, | |||||
| numRedirectsToFollow)); | |||||
| if (statusCode != nullptr) | if (statusCode != nullptr) | ||||
| *statusCode = wi->statusCode; | *statusCode = wi->statusCode; | ||||
| @@ -266,6 +266,8 @@ public: | |||||
| in the response will be stored in this array | 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 | @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 | 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) | |||||
| @returns an input stream that the caller must delete, or a null pointer if there was an | @returns an input stream that the caller must delete, or a null pointer if there was an | ||||
| error trying to open it. | error trying to open it. | ||||
| */ | */ | ||||
| @@ -275,7 +277,8 @@ public: | |||||
| String extraHeaders = String(), | String extraHeaders = String(), | ||||
| int connectionTimeOutMs = 0, | int connectionTimeOutMs = 0, | ||||
| StringPairArray* responseHeaders = nullptr, | StringPairArray* responseHeaders = nullptr, | ||||
| int* statusCode = nullptr) const; | |||||
| int* statusCode = nullptr, | |||||
| int numRedirectsToFollow = 5) const; | |||||
| //============================================================================== | //============================================================================== | ||||