26struct FallbackDownloadTask final :
public URL::DownloadTask,
29 FallbackDownloadTask (std::unique_ptr<FileOutputStream> outputStreamToUse,
30 size_t bufferSizeToUse,
31 std::unique_ptr<WebInputStream> streamToUse,
32 URL::DownloadTask::Listener* listenerToUse)
33 :
Thread (
"DownloadTask thread"),
34 fileStream (std::move (outputStreamToUse)),
35 stream (std::move (streamToUse)),
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)
112std::unique_ptr<URL::DownloadTask> URL::DownloadTask::createFallbackDownloader (
const URL& urlToUse,
113 const File& targetFileToUse,
116 const size_t bufferSize = 0x8000;
121 auto stream = std::make_unique<WebInputStream> (urlToUse, options.usePost);
122 stream->withExtraHeaders (options.extraHeaders);
124 if (stream->connect (
nullptr))
125 return std::make_unique<FallbackDownloadTask> (std::move (outputStream),
134URL::DownloadTask::DownloadTask() {}
147 if (localFile ==
File())
154 while (! localFile.
isRoot())
174 url =
"file://" + url;
203 else if (nextAmp > 0 && equalsPos < nextAmp)
217URL::URL (
const String& u,
int) : url (u) {}
226 return url == other.url
227 && postData == other.postData
228 && parameterNames == other.parameterNames
229 && parameterValues == other.parameterValues
230 && filesToUpload == other.filesToUpload;
233bool URL::operator!= (
const URL& other)
const
240 static String getMangledParameters (
const URL& url)
242 jassert (url.getParameterNames().size() == url.getParameterValues().size());
245 for (
int i = 0; i < url.getParameterNames().size(); ++i)
250 auto val = url.getParameterValues()[i];
254 if (val.isNotEmpty())
261 static int findEndOfScheme (
const String& url)
266 || url[i] ==
'+' || url[i] ==
'-' || url[i] ==
'.')
269 return url.substring (i).startsWith (
"://") ? i + 1 : 0;
272 static int findStartOfNetLocation (
const String& url)
274 int start = findEndOfScheme (url);
276 while (url[start] ==
'/')
282 static int findStartOfPath (
const String& url)
284 return url.indexOfChar (findStartOfNetLocation (url),
'/') + 1;
287 static void concatenatePaths (String& path,
const String& suffix)
289 if (! path.endsWithChar (
'/'))
292 if (suffix.startsWithChar (
'/'))
293 path += suffix.substring (1);
298 static String removeLastPathSection (
const String& url)
300 auto startOfPath = findStartOfPath (url);
301 auto lastSlash = url.lastIndexOfChar (
'/');
303 if (lastSlash > startOfPath && lastSlash == url.length() - 1)
304 return removeLastPathSection (url.dropLastCharacters (1));
309 return url.substring (0, std::max (startOfPath, lastSlash));
313void URL::addParameter (
const String& name,
const String& value)
315 parameterNames.
add (name);
316 parameterValues.
add (value);
321 if (includeGetParameters)
329 return url.isEmpty();
335 return url.isNotEmpty();
340 return getDomainInternal (
false);
345 auto startOfPath = URLHelpers::findStartOfPath (url);
346 auto subPath = startOfPath <= 0 ?
String()
347 : url.substring (startOfPath);
349 if (includeGetParameters)
359 if (parameterNames.
size() > 0)
360 result +=
"?" + URLHelpers::getMangledParameters (*
this);
378 return url.
substring (0, URLHelpers::findEndOfScheme (url) - 1);
389 return fileFromFileSchemeURL (*
this);
398URL::ParameterHandling URL::toHandling (
bool usePostData)
400 return usePostData ? ParameterHandling::inPostData : ParameterHandling::inAddress;
403File URL::fileFromFileSchemeURL (
const URL& fileURL)
405 if (! fileURL.isLocalFile())
414 bool isUncPath = (! fileURL.url.startsWith (
"file:///"));
421 for (
auto urlElement : urlElements)
426 path =
"\\\\" + path;
434 auto colonPos = url.indexOfChar (URLHelpers::findStartOfNetLocation (url),
':');
436 return colonPos > 0 ? url.substring (colonPos + 1).getIntValue() : 0;
450 auto startOfPath = URLHelpers::findStartOfPath (url);
455 URLHelpers::concatenatePaths (u.url, newPath);
462 u.url = URLHelpers::removeLastPathSection (u.url);
469 URLHelpers::concatenatePaths (u.url, subPath);
473bool URL::hasBodyDataToSend()
const
475 return filesToUpload.
size() > 0 || ! postData.
isEmpty();
478void URL::createHeadersAndPostData (String& headers,
479 MemoryBlock& postDataToWrite,
480 bool addParametersToBody)
const
482 MemoryOutputStream data (postDataToWrite,
false);
484 if (filesToUpload.
size() > 0)
491 headers <<
"Content-Type: multipart/form-data; boundary=" << boundary <<
"\r\n";
493 data <<
"--" << boundary;
495 for (
int i = 0; i < parameterNames.
size(); ++i)
497 data <<
"\r\nContent-Disposition: form-data; name=\"" << parameterNames[i]
498 <<
"\"\r\n\r\n" << parameterValues[i]
499 <<
"\r\n--" << boundary;
502 for (
auto* f : filesToUpload)
504 data <<
"\r\nContent-Disposition: form-data; name=\"" << f->parameterName
505 <<
"\"; filename=\"" << f->filename <<
"\"\r\n";
507 if (f->mimeType.isNotEmpty())
508 data <<
"Content-Type: " << f->mimeType <<
"\r\n";
510 data <<
"Content-Transfer-Encoding: binary\r\n\r\n";
512 if (f->data !=
nullptr)
517 data <<
"\r\n--" << boundary;
524 if (addParametersToBody)
525 data << URLHelpers::getMangledParameters (*
this);
530 if (! headers.containsIgnoreCase (
"Content-Type"))
531 headers <<
"Content-Type: application/x-www-form-urlencoded\r\n";
533 headers <<
"Content-length: " << (int) data.getDataSize() <<
"\r\n";
540 for (
auto* protocol : {
"http:",
"https:",
"ftp:" })
550 return topLevelDomain.
isNotEmpty() && topLevelDomain.length() <= 3;
555 auto atSign = possibleEmailAddress.
indexOfChar (
'@');
562String URL::getDomainInternal (
bool ignorePort)
const
564 auto start = URLHelpers::findStartOfNetLocation (url);
565 auto end1 = url.indexOfChar (start,
'/');
566 auto end2 = ignorePort ? -1 : url.indexOfChar (start,
':');
568 auto end = (end1 < 0 && end2 < 0) ? std::numeric_limits<int>::max()
569 : ((end1 < 0 || end2 < 0) ? jmax (end1, end2)
570 : jmin (end1, end2));
571 return url.substring (start, end);
575URL::Bookmark::Bookmark (
void* bookmarkToUse) : data (bookmarkToUse)
579URL::Bookmark::~Bookmark()
581 [(NSData*) data release];
584void setURLBookmark (URL& u,
void* bookmark)
586 u.bookmark =
new URL::Bookmark (bookmark);
589void* getURLBookmark (URL& u)
591 if (u.bookmark.get() ==
nullptr)
594 return u.bookmark.get()->data;
597template <
typename Stream>
struct iOSFileStreamWrapperFlush {
static void flush (Stream*) {} };
598template <>
struct iOSFileStreamWrapperFlush<FileOutputStream> {
static void flush (OutputStream* o) { o->flush(); } };
600template <
typename Stream>
601class iOSFileStreamWrapper final :
public Stream
604 iOSFileStreamWrapper (URL& urlToUse)
605 : Stream (getLocalFileAccess (urlToUse)),
609 ~iOSFileStreamWrapper()
611 iOSFileStreamWrapperFlush<Stream>::flush (
this);
613 if (NSData* bookmark = (NSData*) getURLBookmark (url))
615 BOOL isBookmarkStale =
false;
616 NSError* error = nil;
618 auto nsURL = [NSURL URLByResolvingBookmarkData: bookmark
621 bookmarkDataIsStale: &isBookmarkStale
627 updateStaleBookmark (nsURL, url);
629 [nsURL stopAccessingSecurityScopedResource];
633 [[maybe_unused]]
auto desc = [error localizedDescription];
641 bool securityAccessSucceeded =
false;
643 File getLocalFileAccess (URL& urlToUse)
645 if (NSData* bookmark = (NSData*) getURLBookmark (urlToUse))
647 BOOL isBookmarkStale =
false;
648 NSError* error = nil;
650 auto nsURL = [NSURL URLByResolvingBookmarkData: bookmark
653 bookmarkDataIsStale: &isBookmarkStale
658 securityAccessSucceeded = [nsURL startAccessingSecurityScopedResource];
661 updateStaleBookmark (nsURL, urlToUse);
663 return urlToUse.getLocalFile();
666 [[maybe_unused]]
auto desc = [error localizedDescription];
670 return urlToUse.getLocalFile();
673 void updateStaleBookmark (NSURL* nsURL, URL& juceUrl)
675 NSError* error = nil;
677 NSData* bookmark = [nsURL bookmarkDataWithOptions: NSURLBookmarkCreationSuitableForBookmarkFile
678 includingResourceValuesForKeys: nil
683 setURLBookmark (juceUrl, (
void*) bookmark);
690template <
typename Member,
typename Item>
691static URL::InputStreamOptions with (URL::InputStreamOptions options, Member&& member, Item&& item)
693 options.*member = std::forward<Item> (item);
701 return with (*
this, &InputStreamOptions::progressCallback, std::move (cb));
706 return with (*
this, &InputStreamOptions::extraHeaders, headers);
711 return with (*
this, &InputStreamOptions::connectionTimeOutMs, timeout);
716 return with (*
this, &InputStreamOptions::responseHeaders, headers);
721 return with (*
this, &InputStreamOptions::statusCode, status);
726 return with (*
this, &InputStreamOptions::numRedirectsToFollow, numRedirects);
731 return with (*
this, &InputStreamOptions::httpRequestCmd, cmd);
741 return std::make_unique<iOSFileStreamWrapper<FileInputStream>> (
const_cast<URL&
> (*this));
747 auto webInputStream = [&]
749 const auto usePost = options.getParameterHandling() == ParameterHandling::inPostData;
750 auto stream = std::make_unique<WebInputStream> (*
this, usePost);
752 auto extraHeaders = options.getExtraHeaders();
754 if (extraHeaders.isNotEmpty())
755 stream->withExtraHeaders (extraHeaders);
757 auto timeout = options.getConnectionTimeoutMs();
760 stream->withConnectionTimeout (timeout);
762 auto requestCmd = options.getHttpRequestCmd();
764 if (requestCmd.isNotEmpty())
765 stream->withCustomRequestCommand (requestCmd);
767 stream->withNumRedirectsToFollow (options.getNumRedirectsToFollow());
774 ProgressCallbackCaller (std::function<
bool (
int,
int)> progressCallbackToUse)
775 : callback (std::move (progressCallbackToUse))
779 bool postDataSendProgress (
WebInputStream&,
int bytesSent,
int totalBytes)
override
781 return callback (bytesSent, totalBytes);
784 std::function<bool (
int,
int)> callback;
787 auto callbackCaller = [&options]() -> std::unique_ptr<ProgressCallbackCaller>
789 if (
auto progressCallback = options.getProgressCallback())
790 return std::make_unique<ProgressCallbackCaller> (progressCallback);
795 auto success = webInputStream->connect (callbackCaller.get());
797 if (
auto* status = options.getStatusCode())
798 *status = webInputStream->getStatusCode();
800 if (
auto* responseHeaders = options.getResponseHeaders())
801 *responseHeaders = webInputStream->getResponseHeaders();
803 if (! success || webInputStream->isError())
807 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE (
"-Wredundant-move")
808 return std::move (webInputStream);
809 JUCE_END_IGNORE_WARNINGS_GCC_LIKE
823 return std::make_unique<iOSFileStreamWrapper<FileOutputStream>> (
const_cast<URL&
> (*this));
825 return std::make_unique<FileOutputStream> (
getLocalFile());
840 in->readIntoMemoryBlock (destData);
853 return in->readEntireStreamAsString();
865 const String& parameterValue)
const
868 u.addParameter (parameterName, parameterValue);
876 for (
int i = 0; i < parametersToAdd.
size(); ++i)
877 u.addParameter (parametersToAdd.
getAllKeys()[i],
887 u.anchor = anchorToAdd;
899 u.postData = newPostData;
903URL::Upload::Upload (
const String& param,
const String& name,
905 : parameterName (param), filename (name), mimeType (mime), file (f), data (mb)
907 jassert (mimeType.isNotEmpty());
910URL URL::withUpload (Upload*
const f)
const
914 for (
int i = u.filesToUpload.size(); --i >= 0;)
915 if (u.filesToUpload.getObjectPointerUnchecked (i)->parameterName == f->parameterName)
916 u.filesToUpload.remove (i);
918 u.filesToUpload.add (f);
923 const String& mimeType)
const
925 return withUpload (
new Upload (parameterName, fileToUpload.
getFileName(),
926 mimeType, fileToUpload,
nullptr));
932 return withUpload (
new Upload (parameterName, filename, mimeType,
File(),
941 if (! result.containsChar (
'%'))
946 Array<char> utf8 (result.toRawUTF8(), (
int) result.getNumBytesAsUTF8());
948 for (
int i = 0; i < utf8.
size(); ++i)
955 if (hexDigit1 >= 0 && hexDigit2 >= 0)
957 utf8.
set (i, (
char) ((hexDigit1 << 4) + hexDigit2));
968 String legalChars (isParameter ?
"_-.~"
971 if (roundBracketsAreLegal)
976 for (
int i = 0; i < utf8.
size(); ++i)
984 utf8.
insert (++i,
"0123456789ABCDEF" [((uint8) c) >> 4]);
985 utf8.
insert (++i,
"0123456789ABCDEF" [c & 15]);
997 if (u.containsChar (
'@') && ! u.containsChar (
':'))
1005 OpenStreamProgressCallback* cb,
1011 int numRedirectsToFollow,
1012 String httpRequestCmd)
const
1014 std::function<bool (
int,
int)> callback;
1017 callback = [context, cb] (
int sent,
int total) {
return cb (context, sent, total); };
1020 .withProgressCallback (std::move (callback))
1021 .withExtraHeaders (headers)
1022 .withConnectionTimeoutMs (timeOutMs)
1023 .withResponseHeaders (responseHeaders)
1024 .withStatusCode (statusCode)
1025 .withNumRedirectsToFollow (numRedirectsToFollow)
1026 .withHttpRequestCmd (httpRequestCmd));
1032 bool usePostCommand)
1035 .withListener (listener)
1036 .withUsePost (usePostCommand);
static AndroidDocument fromDocument(const URL &documentUrl)
std::unique_ptr< OutputStream > createOutputStream() const
ElementType getUnchecked(int index) const
int size() const noexcept
void removeRange(int startIndex, int numberToRemove)
void insert(int indexToInsertAt, ParameterType newElement)
ElementType * getRawDataPointer() noexcept
void set(int indexToChange, ParameterType newValue)
static int getHexDigitValue(juce_wchar digit) noexcept
static bool isLetterOrDigit(char character) noexcept
std::unique_ptr< FileOutputStream > createOutputStream(size_t bufferSize=0x8000) const
const String & getFullPathName() const noexcept
String getFileName() const
File getParentDirectory() const
std::unique_ptr< FileInputStream > createInputStream() const
static StringRef getSeparatorString()
bool isEmpty() const noexcept
static bool JUCE_CALLTYPE openDocument(const String &documentURL, const String ¶meters)
static Random & getSystemRandom() noexcept
int size() const noexcept
static StringArray fromTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
int size() const noexcept
void add(String stringToAdd)
const StringArray & getAllValues() const noexcept
int size() const noexcept
const StringArray & getAllKeys() const noexcept
int indexOfChar(juce_wchar characterToLookFor) const noexcept
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) const
bool endsWithChar(juce_wchar character) const noexcept
const char * toRawUTF8() const
bool startsWithChar(juce_wchar character) const noexcept
bool startsWith(StringRef text) const noexcept
bool containsChar(juce_wchar character) const noexcept
bool startsWithIgnoreCase(StringRef text) const noexcept
size_t getNumBytesAsUTF8() const noexcept
static String toHexString(IntegerType number)
int lastIndexOfChar(juce_wchar character) const noexcept
String replace(StringRef stringToReplace, StringRef stringToInsertInstead, bool ignoreCase=false) const
String replaceCharacter(juce_wchar characterToReplace, juce_wchar characterToInsertInstead) const
String substring(int startIndex, int endIndex) const
String fromLastOccurrenceOf(StringRef substringToFind, bool includeSubStringInResult, bool ignoreCase) const
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
bool isNotEmpty() const noexcept
String fromFirstOccurrenceOf(StringRef substringToStartFrom, bool includeSubStringInResult, bool ignoreCase) const
bool waitForThreadToExit(int timeOutMilliseconds) const
Thread(const String &threadName, size_t threadStackSize=osDefaultStackSize)
bool threadShouldExit() const
void signalThreadShouldExit()
auto withExtraHeaders(String value) const
URL withParameter(const String ¶meterName, const String ¶meterValue) const
static URL createWithoutParsing(const String &url)
File getLocalFile() const
bool isWellFormed() const
bool readEntireBinaryStream(MemoryBlock &destData, bool usePostCommand=false) const
URL withDataToUpload(const String ¶meterName, const String &filename, const MemoryBlock &fileContentToUpload, const String &mimeType) const
String getFileName() const
URL getChildURL(const String &subPath) const
static String removeEscapeChars(const String &stringToRemoveEscapeCharsFrom)
String getAnchorString() const
static String addEscapeChars(const String &stringToAddEscapeCharsTo, bool isParameter, bool roundBracketsAreLegal=true)
URL withAnchor(const String &anchor) const
String toString(bool includeGetParameters) const
std::unique_ptr< OutputStream > createOutputStream() const
String getSubPath(bool includeGetParameters=false) const
String getQueryString() const
URL withNewSubPath(const String &newPath) const
static bool isProbablyAnEmailAddress(const String &possibleEmailAddress)
URL withNewDomainAndPath(const String &newFullPath) const
String readEntireTextStream(bool usePostCommand=false) const
bool isEmpty() const noexcept
static bool isProbablyAWebsiteURL(const String &possibleURL)
std::unique_ptr< InputStream > createInputStream(const InputStreamOptions &options) const
std::unique_ptr< DownloadTask > downloadToFile(const File &targetLocation, String extraHeaders=String(), DownloadTaskListener *listener=nullptr, bool usePostCommand=false)
URL withFileToUpload(const String ¶meterName, const File &fileToUpload, const String &mimeType) const
URL withPOSTData(const String &postData) const
URL withParameters(const StringPairArray ¶metersToAdd) const
std::unique_ptr< XmlElement > readEntireXmlStream(bool usePostCommand=false) const
bool operator==(const URL &) const
bool launchInDefaultBrowser() const
virtual void progress(DownloadTask *task, int64 bytesDownloaded, int64 totalLength)