@@ -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; | |||||
//============================================================================== | //============================================================================== | ||||