26 struct FallbackDownloadTask :
public URL::DownloadTask,
29 FallbackDownloadTask (FileOutputStream* outputStreamToUse,
30 size_t bufferSizeToUse,
31 WebInputStream* streamToUse,
32 URL::DownloadTask::Listener* listenerToUse)
33 :
Thread (
"DownloadTask thread"),
34 fileStream (outputStreamToUse),
36 bufferSize (bufferSizeToUse),
38 listener (listenerToUse)
40 jassert (fileStream !=
nullptr);
41 jassert (stream !=
nullptr);
43 targetLocation = fileStream->getFile();
44 contentLength = stream->getTotalLength();
45 httpCode = stream->getStatusCode();
50 ~FallbackDownloadTask()
override 62 if (listener !=
nullptr)
63 listener->progress (
this, downloaded, contentLength);
65 auto max = (int) jmin ((int64) bufferSize, contentLength < 0 ? std::numeric_limits<int64>::max()
66 : static_cast<int64> (contentLength - downloaded));
68 auto actual = stream->read (buffer.get(), max);
73 if (! fileStream->write (buffer.get(),
static_cast<size_t> (actual)))
81 if (downloaded == contentLength)
90 if (contentLength > 0 && downloaded < contentLength)
96 listener->finished (
this, ! error);
100 std::unique_ptr<FileOutputStream> fileStream;
101 const std::unique_ptr<WebInputStream> stream;
102 const size_t bufferSize;
103 HeapBlock<char> buffer;
104 URL::DownloadTask::Listener*
const listener;
106 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FallbackDownloadTask)
110 URL::DownloadTask::Listener::~Listener() {}
114 const File& targetFileToUse,
115 const String& extraHeadersToUse,
119 const size_t bufferSize = 0x8000;
122 if (
auto outputStream = std::unique_ptr<FileOutputStream> (targetFileToUse.
createOutputStream (bufferSize)))
124 std::unique_ptr<WebInputStream> stream (
new WebInputStream (urlToUse, usePostRequest));
125 stream->withExtraHeaders (extraHeadersToUse);
127 if (stream->connect (
nullptr))
128 return new FallbackDownloadTask (outputStream.release(), bufferSize, stream.release(), listenerToUse);
134 URL::DownloadTask::DownloadTask() {}
147 if (localFile ==
File())
154 while (! localFile.
isRoot())
174 url =
"file://" + url;
195 else if (nextAmp > 0 && equalsPos < nextAmp)
218 return url == other.url
219 && postData == other.postData
220 && parameterNames == other.parameterNames
221 && parameterValues == other.parameterValues
222 && filesToUpload == other.filesToUpload;
225 bool URL::operator!= (
const URL& other)
const 232 static String getMangledParameters (
const URL& url)
246 if (val.isNotEmpty())
253 static int findEndOfScheme (
const String& url)
258 || url[i] ==
'+' || url[i] ==
'-' || url[i] ==
'.')
264 static int findStartOfNetLocation (
const String& url)
266 int start = findEndOfScheme (url);
268 while (url[start] ==
'/')
274 static int findStartOfPath (
const String& url)
276 return url.
indexOfChar (findStartOfNetLocation (url),
'/') + 1;
279 static void concatenatePaths (
String& path,
const String& suffix)
292 auto startOfPath = findStartOfPath (url);
295 if (lastSlash > startOfPath && lastSlash == url.
length() - 1)
301 return url.
substring (0, std::max (startOfPath, lastSlash));
305 void URL::addParameter (
const String& name,
const String& value)
307 parameterNames.add (name);
308 parameterValues.add (value);
313 if (includeGetParameters)
321 return url.isEmpty();
327 return url.isNotEmpty();
332 return getDomainInternal (
false);
337 auto startOfPath = URLHelpers::findStartOfPath (url);
338 auto subPath = startOfPath <= 0 ?
String()
339 : url.substring (startOfPath);
341 if (includeGetParameters)
349 if (parameterNames.size() > 0)
350 return "?" + URLHelpers::getMangledParameters (*
this);
357 return url.
substring (0, URLHelpers::findEndOfScheme (url) - 1);
368 return fileFromFileSchemeURL (*
this);
377 File URL::fileFromFileSchemeURL (
const URL& fileURL)
385 auto path =
removeEscapeChars (fileURL.getDomainInternal (
true)).replace (
"+",
"%2B");
388 bool isUncPath = (! fileURL.url.
startsWith (
"file:///"));
395 for (
auto urlElement : urlElements)
400 path =
"\\\\" + path;
408 auto colonPos = url.indexOfChar (URLHelpers::findStartOfNetLocation (url),
':');
410 return colonPos > 0 ? url.substring (colonPos + 1).getIntValue() : 0;
424 auto startOfPath = URLHelpers::findStartOfPath (url);
429 URLHelpers::concatenatePaths (u.url, newPath);
436 u.url = URLHelpers::removeLastPathSection (u.url);
443 URLHelpers::concatenatePaths (u.url, subPath);
447 void URL::createHeadersAndPostData (
String& headers,
MemoryBlock& postDataToWrite)
const 451 if (filesToUpload.size() > 0)
454 jassert (postData.getSize() == 0);
458 headers <<
"Content-Type: multipart/form-data; boundary=" << boundary <<
"\r\n";
460 data <<
"--" << boundary;
462 for (
int i = 0; i < parameterNames.size(); ++i)
464 data <<
"\r\nContent-Disposition: form-data; name=\"" << parameterNames[i]
465 <<
"\"\r\n\r\n" << parameterValues[i]
466 <<
"\r\n--" << boundary;
469 for (
auto* f : filesToUpload)
471 data <<
"\r\nContent-Disposition: form-data; name=\"" << f->parameterName
472 <<
"\"; filename=\"" << f->filename <<
"\"\r\n";
474 if (f->mimeType.isNotEmpty())
475 data <<
"Content-Type: " << f->mimeType <<
"\r\n";
477 data <<
"Content-Transfer-Encoding: binary\r\n\r\n";
479 if (f->data !=
nullptr)
484 data <<
"\r\n--" << boundary;
491 data << URLHelpers::getMangledParameters (*
this)
496 headers <<
"Content-Type: application/x-www-form-urlencoded\r\n";
498 headers <<
"Content-length: " << (int) data.
getDataSize() <<
"\r\n";
505 for (
auto* protocol : {
"http:",
"https:",
"ftp:" })
515 return topLevelDomain.
isNotEmpty() && topLevelDomain.length() <= 3;
520 auto atSign = possibleEmailAddress.
indexOfChar (
'@');
527 String URL::getDomainInternal (
bool ignorePort)
const 529 auto start = URLHelpers::findStartOfNetLocation (url);
530 auto end1 = url.indexOfChar (start,
'/');
531 auto end2 = ignorePort ? -1 : url.indexOfChar (start,
':');
533 auto end = (end1 < 0 && end2 < 0) ? std::numeric_limits<int>::max()
534 : ((end1 < 0 || end2 < 0) ? jmax (end1, end2)
535 : jmin (end1, end2));
536 return url.substring (start, end);
540 URL::Bookmark::Bookmark (
void* bookmarkToUse) : data (bookmarkToUse)
544 URL::Bookmark::~Bookmark()
546 [(NSData*) data release];
549 void setURLBookmark (
URL& u,
void* bookmark)
551 u.bookmark =
new URL::Bookmark (bookmark);
554 void* getURLBookmark (
URL& u)
556 if (u.bookmark.get() ==
nullptr)
559 return u.bookmark.get()->data;
562 template <
typename Stream>
struct iOSFileStreamWrapperFlush {
static void flush (Stream*) {} };
563 template <>
struct iOSFileStreamWrapperFlush<FileOutputStream> {
static void flush (
OutputStream* o) { o->
flush(); } };
565 template <
typename Stream>
566 class iOSFileStreamWrapper :
public Stream
569 iOSFileStreamWrapper (
URL& urlToUse)
570 : Stream (getLocalFileAccess (urlToUse)),
574 ~iOSFileStreamWrapper()
576 iOSFileStreamWrapperFlush<Stream>::flush (
this);
578 if (NSData* bookmark = (NSData*) getURLBookmark (url))
580 BOOL isBookmarkStale =
false;
581 NSError* error = nil;
583 auto nsURL = [NSURL URLByResolvingBookmarkData: bookmark
586 bookmarkDataIsStale: &isBookmarkStale
592 updateStaleBookmark (nsURL, url);
594 [nsURL stopAccessingSecurityScopedResource];
598 auto desc = [error localizedDescription];
607 bool securityAccessSucceeded =
false;
609 File getLocalFileAccess (
URL& urlToUse)
611 if (NSData* bookmark = (NSData*) getURLBookmark (urlToUse))
613 BOOL isBookmarkStale =
false;
614 NSError* error = nil;
616 auto nsURL = [NSURL URLByResolvingBookmarkData: bookmark
619 bookmarkDataIsStale: &isBookmarkStale
624 securityAccessSucceeded = [nsURL startAccessingSecurityScopedResource];
627 updateStaleBookmark (nsURL, urlToUse);
632 auto desc = [error localizedDescription];
640 void updateStaleBookmark (NSURL* nsURL,
URL& juceUrl)
642 NSError* error = nil;
644 NSData* bookmark = [nsURL bookmarkDataWithOptions: NSURLBookmarkCreationSuitableForBookmarkFile
645 includingResourceValuesForKeys: nil
650 setURLBookmark (juceUrl, (
void*) bookmark);
660 void* progressCallbackContext,
665 int numRedirectsToFollow,
666 String httpRequestCmd)
const 672 return new iOSFileStreamWrapper<FileInputStream> (
const_cast<URL&
>(*this));
678 auto wi = std::make_unique<WebInputStream> (*
this, usePostCommand);
683 : callback (progressCallbackToUse), data (progressCallbackContextToUse)
686 bool postDataSendProgress (
WebInputStream&,
int bytesSent,
int totalBytes)
override 688 return callback (data, bytesSent, totalBytes);
695 std::unique_ptr<ProgressCallbackCaller> callbackCaller
696 (progressCallback !=
nullptr ?
new ProgressCallbackCaller (progressCallback, progressCallbackContext) :
nullptr);
699 wi->withExtraHeaders (headers);
702 wi->withConnectionTimeout (timeOutMs);
705 wi->withCustomRequestCommand (httpRequestCmd);
707 wi->withNumRedirectsToFollow (numRedirectsToFollow);
709 bool success = wi->connect (callbackCaller.get());
711 if (statusCode !=
nullptr)
712 *statusCode = wi->getStatusCode();
714 if (responseHeaders !=
nullptr)
715 *responseHeaders = wi->getResponseHeaders();
717 if (! success || wi->isError())
733 return new iOSFileStreamWrapper<FileOutputStream> (
const_cast<URL&
> (*this));
740 return juce_CreateContentURIOutputStream (*
this);
754 in->readIntoMemoryBlock (destData);
767 return in->readEntireStreamAsString();
779 const String& parameterValue)
const 782 u.addParameter (parameterName, parameterValue);
790 for (
int i = 0; i < parametersToAdd.
size(); ++i)
791 u.addParameter (parametersToAdd.
getAllKeys()[i],
805 u.postData = newPostData;
809 URL::Upload::Upload (
const String& param,
const String& name,
811 : parameterName (param), filename (name), mimeType (mime), file (f), data (mb)
813 jassert (mimeType.isNotEmpty());
816 URL URL::withUpload (Upload*
const f)
const 820 for (
int i = u.filesToUpload.
size(); --i >= 0;)
822 u.filesToUpload.
remove (i);
824 u.filesToUpload.
add (f);
829 const String& mimeType)
const 831 return withUpload (
new Upload (parameterName, fileToUpload.
getFileName(),
832 mimeType, fileToUpload,
nullptr));
838 return withUpload (
new Upload (parameterName, filename, mimeType,
File(),
847 if (! result.containsChar (
'%'))
852 Array<char> utf8 (result.toRawUTF8(), (int) result.getNumBytesAsUTF8());
854 for (
int i = 0; i < utf8.size(); ++i)
856 if (utf8.getUnchecked(i) ==
'%')
861 if (hexDigit1 >= 0 && hexDigit2 >= 0)
863 utf8.set (i, (
char) ((hexDigit1 << 4) + hexDigit2));
864 utf8.removeRange (i + 1, 2);
874 String legalChars (isParameter ?
"_-.~" 877 if (roundBracketsAreLegal)
882 for (
int i = 0; i < utf8.size(); ++i)
890 utf8.insert (++i,
"0123456789ABCDEF" [((uint8) c) >> 4]);
891 utf8.insert (++i,
"0123456789ABCDEF" [c & 15]);
903 if (u.containsChar (
'@') && ! u.containsChar (
':'))
bool isWellFormed() const
const StringArray & getAllValues() const noexcept
String getFileName() const
static Random & getSystemRandom() noexcept
URL withParameters(const StringPairArray ¶metersToAdd) const
ObjectClass * getObjectPointerUnchecked(int index) const noexcept
String fromFirstOccurrenceOf(StringRef substringToStartFrom, bool includeSubStringInResult, bool ignoreCase) const
const char * toRawUTF8() const
bool endsWithChar(juce_wchar character) const noexcept
bool isNotEmpty() const noexcept
InputStream * 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
URL getChildURL(const String &subPath) const
const StringArray & getParameterValues() const noexcept
File getParentDirectory() const
Thread(const String &threadName, size_t threadStackSize=0)
const StringArray & getAllKeys() const noexcept
void signalThreadShouldExit()
ObjectClass * add(ObjectClass *newObject)
bool operator==(const URL &) const
bool containsChar(juce_wchar character) const noexcept
String toString(bool includeGetParameters) const
bool isEmpty() const noexcept
String dropLastCharacters(int numberToDrop) const
URL withDataToUpload(const String ¶meterName, const String &filename, const MemoryBlock &fileContentToUpload, const String &mimeType) const
std::unique_ptr< XmlElement > readEntireXmlStream(bool usePostCommand=false) const
FileInputStream * createInputStream() const
File getLocalFile() const
static bool JUCE_CALLTYPE openDocument(const String &documentURL, const String ¶meters)
bool startsWithIgnoreCase(StringRef text) const noexcept
int size() const noexcept
URL withParameter(const String ¶meterName, const String ¶meterValue) const
ElementType getUnchecked(int index) const
String getQueryString() const
String fromLastOccurrenceOf(StringRef substringToFind, bool includeSubStringInResult, bool ignoreCase) const
String substring(int startIndex, int endIndex) const
URL withNewDomainAndPath(const String &newFullPath) const
URL withNewSubPath(const String &newPath) const
void remove(int indexToRemove)
static String removeEscapeChars(const String &stringToRemoveEscapeCharsFrom)
int size() const noexcept
static URL createWithoutParsing(const String &url)
URL withPOSTData(const String &postData) const
bool containsIgnoreCase(StringRef text) const noexcept
static int getHexDigitValue(juce_wchar digit) noexcept
static bool isProbablyAnEmailAddress(const String &possibleEmailAddress)
bool threadShouldExit() const
static String toHexString(IntegerType number)
const StringArray & getParameterNames() const noexcept
bool launchInDefaultBrowser() const
static StringArray fromTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
bool readEntireBinaryStream(MemoryBlock &destData, bool usePostCommand=false) const
String replaceCharacter(juce_wchar characterToReplace, juce_wchar characterToInsertInstead) const
static String addEscapeChars(const String &stringToAddEscapeCharsTo, bool isParameter, bool roundBracketsAreLegal=true)
URL withFileToUpload(const String ¶meterName, const File &fileToUpload, const String &mimeType) const
FileOutputStream * createOutputStream(size_t bufferSize=0x8000) const
size_t getDataSize() const noexcept
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) const
virtual void progress(URL::DownloadTask *task, int64 bytesDownloaded, int64 totalLength)
bool startsWithChar(juce_wchar character) const noexcept
bool waitForThreadToExit(int timeOutMilliseconds) const
static StringRef getSeparatorString()
String readEntireTextStream(bool usePostCommand=false) const
String getSubPath(bool includeGetParameters=false) const
int lastIndexOfChar(juce_wchar character) const noexcept
int size() const noexcept
int indexOfChar(juce_wchar characterToLookFor) const noexcept
int length() const noexcept
static bool isProbablyAWebsiteURL(const String &possibleURL)
size_t getNumBytesAsUTF8() const noexcept
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
static bool isLetterOrDigit(char character) noexcept
const String & getFullPathName() const noexcept
String getFileName() const
OutputStream * createOutputStream() const
bool(void *context, int bytesSent, int totalBytes) OpenStreamProgressCallback
bool startsWith(StringRef text) const noexcept