26struct 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)
110URL::DownloadTask::Listener::~Listener() {}
114 const File& targetFileToUse,
115 const String& extraHeadersToUse,
116 Listener* listenerToUse,
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);
134URL::DownloadTask::DownloadTask() {}
147 if (localFile ==
File())
154 while (! localFile.
isRoot())
174 url =
"file://" + url;
195 else if (nextAmp > 0 && equalsPos < nextAmp)
209URL::URL (
const String& u,
int) : url (u) {}
218 return url == other.url
219 && postData == other.postData
220 && parameterNames == other.parameterNames
221 && parameterValues == other.parameterValues
222 && filesToUpload == other.filesToUpload;
225bool URL::operator!= (
const URL& other)
const
232 static String getMangledParameters (
const URL& url)
234 jassert (url.getParameterNames().size() == url.getParameterValues().size());
237 for (
int i = 0; i < url.getParameterNames().size(); ++i)
242 auto val = url.getParameterValues()[i];
246 if (val.isNotEmpty())
253 static int findEndOfScheme (
const String& url)
258 || url[i] ==
'+' || url[i] ==
'-' || url[i] ==
'.')
261 return url.substring (i).startsWith (
"://") ? i + 1 : 0;
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)
281 if (! path.endsWithChar (
'/'))
284 if (suffix.startsWithChar (
'/'))
285 path += suffix.substring (1);
290 static String removeLastPathSection (
const String& url)
292 auto startOfPath = findStartOfPath (url);
293 auto lastSlash = url.lastIndexOfChar (
'/');
295 if (lastSlash > startOfPath && lastSlash == url.length() - 1)
296 return removeLastPathSection (url.dropLastCharacters (1));
301 return url.substring (0, std::max (startOfPath, lastSlash));
305void 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);
377File URL::fileFromFileSchemeURL (
const URL& fileURL)
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);
447void 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 (
'@');
527String 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);
540URL::Bookmark::Bookmark (
void* bookmarkToUse) : data (bookmarkToUse)
544URL::Bookmark::~Bookmark()
546 [(NSData*) data release];
549void setURLBookmark (URL& u,
void* bookmark)
551 u.bookmark =
new URL::Bookmark (bookmark);
554void* getURLBookmark (URL& u)
556 if (u.bookmark.get() ==
nullptr)
559 return u.bookmark.get()->data;
562template <
typename Stream>
struct iOSFileStreamWrapperFlush {
static void flush (Stream*) {} };
563template <>
struct iOSFileStreamWrapperFlush<FileOutputStream> {
static void flush (OutputStream* o) { o->flush(); } };
565template <
typename Stream>
566class 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);
629 return urlToUse.getLocalFile();
632 auto desc = [error localizedDescription];
637 return urlToUse.getLocalFile();
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;
809URL::Upload::Upload (
const String& param,
const String& name,
811 : parameterName (param), filename (name), mimeType (mime), file (f), data (mb)
813 jassert (mimeType.isNotEmpty());
816URL URL::withUpload (Upload*
const f)
const
820 for (
int i = u.filesToUpload.size(); --i >= 0;)
821 if (u.filesToUpload.getObjectPointerUnchecked(i)->parameterName == f->parameterName)
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)
861 if (hexDigit1 >= 0 && hexDigit2 >= 0)
863 utf8.
set (i, (
char) ((hexDigit1 << 4) + hexDigit2));
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 (
':'))
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
FileOutputStream * createOutputStream(size_t bufferSize=0x8000) const
const String & getFullPathName() const noexcept
String getFileName() const
FileInputStream * createInputStream() const
File getParentDirectory() const
static StringRef getSeparatorString()
size_t getSize() 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 containsIgnoreCase(StringRef text) const noexcept
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=0)
bool threadShouldExit() const
void signalThreadShouldExit()
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
OutputStream * createOutputStream() 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)
static String addEscapeChars(const String &stringToAddEscapeCharsTo, bool isParameter, bool roundBracketsAreLegal=true)
String toString(bool includeGetParameters) 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
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
String readEntireTextStream(bool usePostCommand=false) const
bool isEmpty() const noexcept
static bool isProbablyAWebsiteURL(const String &possibleURL)
bool(void *context, int bytesSent, int totalBytes) OpenStreamProgressCallback
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(URL::DownloadTask *task, int64 bytesDownloaded, int64 totalLength)