VST3: fetch midi mappings all at once, use it for note/sound-off
[carla.git] / source / modules / juce_core / network / juce_URL.cpp
blob93c63245cfb9a3b9acd4f12eb6531b428c882d18
1 /*
2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
20 ==============================================================================
23 namespace juce
26 //==============================================================================
27 URL::URL() {}
29 URL::URL (const String& u) : url (u)
31 init();
34 URL::URL (File localFile)
36 if (localFile == File())
37 return;
39 #if JUCE_WINDOWS
40 bool isUncPath = localFile.getFullPathName().startsWith ("\\\\");
41 #endif
43 while (! localFile.isRoot())
45 url = "/" + addEscapeChars (localFile.getFileName(), false) + url;
46 localFile = localFile.getParentDirectory();
49 url = addEscapeChars (localFile.getFileName(), false) + url;
51 #if JUCE_WINDOWS
52 if (isUncPath)
54 url = url.fromFirstOccurrenceOf ("/", false, false);
56 else
57 #endif
59 if (! url.startsWithChar (L'/'))
60 url = "/" + url;
63 url = "file://" + url;
65 jassert (isWellFormed());
68 void URL::init()
70 auto i = url.indexOfChar ('?');
72 if (i >= 0)
76 auto nextAmp = url.indexOfChar (i + 1, '&');
77 auto equalsPos = url.indexOfChar (i + 1, '=');
79 if (nextAmp < 0)
81 addParameter (removeEscapeChars (equalsPos < 0 ? url.substring (i + 1) : url.substring (i + 1, equalsPos)),
82 equalsPos < 0 ? String() : removeEscapeChars (url.substring (equalsPos + 1)));
84 else if (nextAmp > 0 && equalsPos < nextAmp)
86 addParameter (removeEscapeChars (equalsPos < 0 ? url.substring (i + 1, nextAmp) : url.substring (i + 1, equalsPos)),
87 equalsPos < 0 ? String() : removeEscapeChars (url.substring (equalsPos + 1, nextAmp)));
90 i = nextAmp;
92 while (i >= 0);
94 url = url.upToFirstOccurrenceOf ("?", false, false);
98 URL::URL (const String& u, int) : url (u) {}
100 URL URL::createWithoutParsing (const String& u)
102 return URL (u, 0);
105 bool URL::operator== (const URL& other) const
107 return url == other.url
108 && postData == other.postData
109 && parameterNames == other.parameterNames
110 && parameterValues == other.parameterValues
111 && filesToUpload == other.filesToUpload;
114 bool URL::operator!= (const URL& other) const
116 return ! operator== (other);
119 namespace URLHelpers
121 static String getMangledParameters (const URL& url)
123 jassert (url.getParameterNames().size() == url.getParameterValues().size());
124 String p;
126 for (int i = 0; i < url.getParameterNames().size(); ++i)
128 if (i > 0)
129 p << '&';
131 auto val = url.getParameterValues()[i];
133 p << URL::addEscapeChars (url.getParameterNames()[i], true);
135 if (val.isNotEmpty())
136 p << '=' << URL::addEscapeChars (val, true);
139 return p;
142 static int findEndOfScheme (const String& url)
144 int i = 0;
146 while (CharacterFunctions::isLetterOrDigit (url[i])
147 || url[i] == '+' || url[i] == '-' || url[i] == '.')
148 ++i;
150 return url.substring (i).startsWith ("://") ? i + 1 : 0;
153 static int findStartOfNetLocation (const String& url)
155 int start = findEndOfScheme (url);
157 while (url[start] == '/')
158 ++start;
160 return start;
163 static int findStartOfPath (const String& url)
165 return url.indexOfChar (findStartOfNetLocation (url), '/') + 1;
168 static void concatenatePaths (String& path, const String& suffix)
170 if (! path.endsWithChar ('/'))
171 path << '/';
173 if (suffix.startsWithChar ('/'))
174 path += suffix.substring (1);
175 else
176 path += suffix;
179 static String removeLastPathSection (const String& url)
181 auto startOfPath = findStartOfPath (url);
182 auto lastSlash = url.lastIndexOfChar ('/');
184 if (lastSlash > startOfPath && lastSlash == url.length() - 1)
185 return removeLastPathSection (url.dropLastCharacters (1));
187 if (lastSlash < 0)
188 return url;
190 return url.substring (0, std::max (startOfPath, lastSlash));
194 void URL::addParameter (const String& name, const String& value)
196 parameterNames.add (name);
197 parameterValues.add (value);
200 String URL::toString (bool includeGetParameters) const
202 if (includeGetParameters)
203 return url + getQueryString();
205 return url;
208 bool URL::isEmpty() const noexcept
210 return url.isEmpty();
213 bool URL::isWellFormed() const
215 //xxx TODO
216 return url.isNotEmpty();
219 String URL::getDomain() const
221 return getDomainInternal (false);
224 String URL::getSubPath (bool includeGetParameters) const
226 auto startOfPath = URLHelpers::findStartOfPath (url);
227 auto subPath = startOfPath <= 0 ? String()
228 : url.substring (startOfPath);
230 if (includeGetParameters)
231 subPath += getQueryString();
233 return subPath;
236 String URL::getQueryString() const
238 if (parameterNames.size() > 0)
239 return "?" + URLHelpers::getMangledParameters (*this);
241 return {};
244 String URL::getScheme() const
246 return url.substring (0, URLHelpers::findEndOfScheme (url) - 1);
249 #if ! JUCE_ANDROID
250 bool URL::isLocalFile() const
252 return getScheme() == "file";
255 File URL::getLocalFile() const
257 return fileFromFileSchemeURL (*this);
260 String URL::getFileName() const
262 return toString (false).fromLastOccurrenceOf ("/", false, true);
264 #endif
266 URL::ParameterHandling URL::toHandling (bool usePostData)
268 return usePostData ? ParameterHandling::inPostData : ParameterHandling::inAddress;
271 File URL::fileFromFileSchemeURL (const URL& fileURL)
273 if (! fileURL.isLocalFile())
275 jassertfalse;
276 return {};
279 auto path = removeEscapeChars (fileURL.getDomainInternal (true)).replace ("+", "%2B");
281 #if JUCE_WINDOWS
282 bool isUncPath = (! fileURL.url.startsWith ("file:///"));
283 #else
284 path = File::getSeparatorString() + path;
285 #endif
287 auto urlElements = StringArray::fromTokens (fileURL.getSubPath(), "/", "");
289 for (auto urlElement : urlElements)
290 path += File::getSeparatorString() + removeEscapeChars (urlElement.replace ("+", "%2B"));
292 #if JUCE_WINDOWS
293 if (isUncPath)
294 path = "\\\\" + path;
295 #endif
297 return path;
300 int URL::getPort() const
302 auto colonPos = url.indexOfChar (URLHelpers::findStartOfNetLocation (url), ':');
304 return colonPos > 0 ? url.substring (colonPos + 1).getIntValue() : 0;
307 URL URL::withNewDomainAndPath (const String& newURL) const
309 URL u (*this);
310 u.url = newURL;
311 return u;
314 URL URL::withNewSubPath (const String& newPath) const
316 URL u (*this);
318 auto startOfPath = URLHelpers::findStartOfPath (url);
320 if (startOfPath > 0)
321 u.url = url.substring (0, startOfPath);
323 URLHelpers::concatenatePaths (u.url, newPath);
324 return u;
327 URL URL::getParentURL() const
329 URL u (*this);
330 u.url = URLHelpers::removeLastPathSection (u.url);
331 return u;
334 URL URL::getChildURL (const String& subPath) const
336 URL u (*this);
337 URLHelpers::concatenatePaths (u.url, subPath);
338 return u;
341 bool URL::hasBodyDataToSend() const
343 return filesToUpload.size() > 0 || ! postData.isEmpty();
346 void URL::createHeadersAndPostData (String& headers,
347 MemoryBlock& postDataToWrite,
348 bool addParametersToBody) const
350 MemoryOutputStream data (postDataToWrite, false);
352 if (filesToUpload.size() > 0)
354 // (this doesn't currently support mixing custom post-data with uploads..)
355 jassert (postData.isEmpty());
357 auto boundary = String::toHexString (Random::getSystemRandom().nextInt64());
359 headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n";
361 data << "--" << boundary;
363 for (int i = 0; i < parameterNames.size(); ++i)
365 data << "\r\nContent-Disposition: form-data; name=\"" << parameterNames[i]
366 << "\"\r\n\r\n" << parameterValues[i]
367 << "\r\n--" << boundary;
370 for (auto* f : filesToUpload)
372 data << "\r\nContent-Disposition: form-data; name=\"" << f->parameterName
373 << "\"; filename=\"" << f->filename << "\"\r\n";
375 if (f->mimeType.isNotEmpty())
376 data << "Content-Type: " << f->mimeType << "\r\n";
378 data << "Content-Transfer-Encoding: binary\r\n\r\n";
380 if (f->data != nullptr)
381 data << *f->data;
382 else
383 data << f->file;
385 data << "\r\n--" << boundary;
388 data << "--\r\n";
390 else
392 if (addParametersToBody)
393 data << URLHelpers::getMangledParameters (*this);
395 data << postData;
397 // if the user-supplied headers didn't contain a content-type, add one now..
398 if (! headers.containsIgnoreCase ("Content-Type"))
399 headers << "Content-Type: application/x-www-form-urlencoded\r\n";
401 headers << "Content-length: " << (int) data.getDataSize() << "\r\n";
405 //==============================================================================
406 bool URL::isProbablyAWebsiteURL (const String& possibleURL)
408 for (auto* protocol : { "http:", "https:", "ftp:" })
409 if (possibleURL.startsWithIgnoreCase (protocol))
410 return true;
412 if (possibleURL.containsChar ('@') || possibleURL.containsChar (' '))
413 return false;
415 auto topLevelDomain = possibleURL.upToFirstOccurrenceOf ("/", false, false)
416 .fromLastOccurrenceOf (".", false, false);
418 return topLevelDomain.isNotEmpty() && topLevelDomain.length() <= 3;
421 bool URL::isProbablyAnEmailAddress (const String& possibleEmailAddress)
423 auto atSign = possibleEmailAddress.indexOfChar ('@');
425 return atSign > 0
426 && possibleEmailAddress.lastIndexOfChar ('.') > (atSign + 1)
427 && ! possibleEmailAddress.endsWithChar ('.');
430 String URL::getDomainInternal (bool ignorePort) const
432 auto start = URLHelpers::findStartOfNetLocation (url);
433 auto end1 = url.indexOfChar (start, '/');
434 auto end2 = ignorePort ? -1 : url.indexOfChar (start, ':');
436 auto end = (end1 < 0 && end2 < 0) ? std::numeric_limits<int>::max()
437 : ((end1 < 0 || end2 < 0) ? jmax (end1, end2)
438 : jmin (end1, end2));
439 return url.substring (start, end);
442 #if JUCE_IOS
443 URL::Bookmark::Bookmark (void* bookmarkToUse) : data (bookmarkToUse)
447 URL::Bookmark::~Bookmark()
449 [(NSData*) data release];
452 void setURLBookmark (URL& u, void* bookmark)
454 u.bookmark = new URL::Bookmark (bookmark);
457 void* getURLBookmark (URL& u)
459 if (u.bookmark.get() == nullptr)
460 return nullptr;
462 return u.bookmark.get()->data;
465 template <typename Stream> struct iOSFileStreamWrapperFlush { static void flush (Stream*) {} };
466 template <> struct iOSFileStreamWrapperFlush<FileOutputStream> { static void flush (OutputStream* o) { o->flush(); } };
468 template <typename Stream>
469 class iOSFileStreamWrapper : public Stream
471 public:
472 iOSFileStreamWrapper (URL& urlToUse)
473 : Stream (getLocalFileAccess (urlToUse)),
474 url (urlToUse)
477 ~iOSFileStreamWrapper()
479 iOSFileStreamWrapperFlush<Stream>::flush (this);
481 if (NSData* bookmark = (NSData*) getURLBookmark (url))
483 BOOL isBookmarkStale = false;
484 NSError* error = nil;
486 auto nsURL = [NSURL URLByResolvingBookmarkData: bookmark
487 options: 0
488 relativeToURL: nil
489 bookmarkDataIsStale: &isBookmarkStale
490 error: &error];
492 if (error == nil)
494 if (isBookmarkStale)
495 updateStaleBookmark (nsURL, url);
497 [nsURL stopAccessingSecurityScopedResource];
499 else
501 auto desc = [error localizedDescription];
502 ignoreUnused (desc);
503 jassertfalse;
508 private:
509 URL url;
510 bool securityAccessSucceeded = false;
512 File getLocalFileAccess (URL& urlToUse)
514 if (NSData* bookmark = (NSData*) getURLBookmark (urlToUse))
516 BOOL isBookmarkStale = false;
517 NSError* error = nil;
519 auto nsURL = [NSURL URLByResolvingBookmarkData: bookmark
520 options: 0
521 relativeToURL: nil
522 bookmarkDataIsStale: &isBookmarkStale
523 error: &error];
525 if (error == nil)
527 securityAccessSucceeded = [nsURL startAccessingSecurityScopedResource];
529 if (isBookmarkStale)
530 updateStaleBookmark (nsURL, urlToUse);
532 return urlToUse.getLocalFile();
535 auto desc = [error localizedDescription];
536 ignoreUnused (desc);
537 jassertfalse;
540 return urlToUse.getLocalFile();
543 void updateStaleBookmark (NSURL* nsURL, URL& juceUrl)
545 NSError* error = nil;
547 NSData* bookmark = [nsURL bookmarkDataWithOptions: NSURLBookmarkCreationSuitableForBookmarkFile
548 includingResourceValuesForKeys: nil
549 relativeToURL: nil
550 error: &error];
552 if (error == nil)
553 setURLBookmark (juceUrl, (void*) bookmark);
554 else
555 jassertfalse;
558 #endif
559 //==============================================================================
560 template <typename Member, typename Item>
561 static URL::InputStreamOptions with (URL::InputStreamOptions options, Member&& member, Item&& item)
563 options.*member = std::forward<Item> (item);
564 return options;
567 URL::InputStreamOptions::InputStreamOptions (ParameterHandling handling) : parameterHandling (handling) {}
569 URL::InputStreamOptions URL::InputStreamOptions::withProgressCallback (std::function<bool (int, int)> cb) const
571 return with (*this, &InputStreamOptions::progressCallback, std::move (cb));
574 URL::InputStreamOptions URL::InputStreamOptions::withExtraHeaders (const String& headers) const
576 return with (*this, &InputStreamOptions::extraHeaders, headers);
579 URL::InputStreamOptions URL::InputStreamOptions::withConnectionTimeoutMs (int timeout) const
581 return with (*this, &InputStreamOptions::connectionTimeOutMs, timeout);
584 URL::InputStreamOptions URL::InputStreamOptions::withResponseHeaders (StringPairArray* headers) const
586 return with (*this, &InputStreamOptions::responseHeaders, headers);
589 URL::InputStreamOptions URL::InputStreamOptions::withStatusCode (int* status) const
591 return with (*this, &InputStreamOptions::statusCode, status);
594 URL::InputStreamOptions URL::InputStreamOptions::withNumRedirectsToFollow (int numRedirects) const
596 return with (*this, &InputStreamOptions::numRedirectsToFollow, numRedirects);
599 URL::InputStreamOptions URL::InputStreamOptions::withHttpRequestCmd (const String& cmd) const
601 return with (*this, &InputStreamOptions::httpRequestCmd, cmd);
604 //==============================================================================
605 std::unique_ptr<InputStream> URL::createInputStream (const InputStreamOptions& options) const
607 if (isLocalFile())
609 #if JUCE_IOS
610 // We may need to refresh the embedded bookmark.
611 return std::make_unique<iOSFileStreamWrapper<FileInputStream>> (const_cast<URL&> (*this));
612 #else
613 return getLocalFile().createInputStream();
614 #endif
617 return nullptr;
620 std::unique_ptr<OutputStream> URL::createOutputStream() const
622 if (isLocalFile())
624 #if JUCE_IOS
625 // We may need to refresh the embedded bookmark.
626 return std::make_unique<iOSFileStreamWrapper<FileOutputStream>> (const_cast<URL&> (*this));
627 #else
628 return std::make_unique<FileOutputStream> (getLocalFile());
629 #endif
632 return nullptr;
635 //==============================================================================
636 bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) const
638 const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
639 : createInputStream (InputStreamOptions (toHandling (usePostCommand))));
641 if (in != nullptr)
643 in->readIntoMemoryBlock (destData);
644 return true;
647 return false;
650 String URL::readEntireTextStream (bool usePostCommand) const
652 const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
653 : createInputStream (InputStreamOptions (toHandling (usePostCommand))));
655 if (in != nullptr)
656 return in->readEntireStreamAsString();
658 return {};
661 std::unique_ptr<XmlElement> URL::readEntireXmlStream (bool usePostCommand) const
663 return parseXML (readEntireTextStream (usePostCommand));
666 //==============================================================================
667 URL URL::withParameter (const String& parameterName,
668 const String& parameterValue) const
670 auto u = *this;
671 u.addParameter (parameterName, parameterValue);
672 return u;
675 URL URL::withParameters (const StringPairArray& parametersToAdd) const
677 auto u = *this;
679 for (int i = 0; i < parametersToAdd.size(); ++i)
680 u.addParameter (parametersToAdd.getAllKeys()[i],
681 parametersToAdd.getAllValues()[i]);
683 return u;
686 URL URL::withPOSTData (const String& newPostData) const
688 return withPOSTData (MemoryBlock (newPostData.toRawUTF8(), newPostData.getNumBytesAsUTF8()));
691 URL URL::withPOSTData (const MemoryBlock& newPostData) const
693 auto u = *this;
694 u.postData = newPostData;
695 return u;
698 URL::Upload::Upload (const String& param, const String& name,
699 const String& mime, const File& f, MemoryBlock* mb)
700 : parameterName (param), filename (name), mimeType (mime), file (f), data (mb)
702 jassert (mimeType.isNotEmpty()); // You need to supply a mime type!
705 URL URL::withUpload (Upload* const f) const
707 auto u = *this;
709 for (int i = u.filesToUpload.size(); --i >= 0;)
710 if (u.filesToUpload.getObjectPointerUnchecked (i)->parameterName == f->parameterName)
711 u.filesToUpload.remove (i);
713 u.filesToUpload.add (f);
714 return u;
717 URL URL::withFileToUpload (const String& parameterName, const File& fileToUpload,
718 const String& mimeType) const
720 return withUpload (new Upload (parameterName, fileToUpload.getFileName(),
721 mimeType, fileToUpload, nullptr));
724 URL URL::withDataToUpload (const String& parameterName, const String& filename,
725 const MemoryBlock& fileContentToUpload, const String& mimeType) const
727 return withUpload (new Upload (parameterName, filename, mimeType, File(),
728 new MemoryBlock (fileContentToUpload)));
731 //==============================================================================
732 String URL::removeEscapeChars (const String& s)
734 auto result = s.replaceCharacter ('+', ' ');
736 if (! result.containsChar ('%'))
737 return result;
739 // We need to operate on the string as raw UTF8 chars, and then recombine them into unicode
740 // after all the replacements have been made, so that multi-byte chars are handled.
741 Array<char> utf8 (result.toRawUTF8(), (int) result.getNumBytesAsUTF8());
743 for (int i = 0; i < utf8.size(); ++i)
745 if (utf8.getUnchecked(i) == '%')
747 auto hexDigit1 = CharacterFunctions::getHexDigitValue ((juce_wchar) (uint8) utf8 [i + 1]);
748 auto hexDigit2 = CharacterFunctions::getHexDigitValue ((juce_wchar) (uint8) utf8 [i + 2]);
750 if (hexDigit1 >= 0 && hexDigit2 >= 0)
752 utf8.set (i, (char) ((hexDigit1 << 4) + hexDigit2));
753 utf8.removeRange (i + 1, 2);
758 return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size());
761 String URL::addEscapeChars (const String& s, bool isParameter, bool roundBracketsAreLegal)
763 String legalChars (isParameter ? "_-.~"
764 : ",$_-.*!'");
766 if (roundBracketsAreLegal)
767 legalChars += "()";
769 Array<char> utf8 (s.toRawUTF8(), (int) s.getNumBytesAsUTF8());
771 for (int i = 0; i < utf8.size(); ++i)
773 auto c = utf8.getUnchecked(i);
775 if (! (CharacterFunctions::isLetterOrDigit (c)
776 || legalChars.containsChar ((juce_wchar) c)))
778 utf8.set (i, '%');
779 utf8.insert (++i, "0123456789ABCDEF" [((uint8) c) >> 4]);
780 utf8.insert (++i, "0123456789ABCDEF" [c & 15]);
784 return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size());
787 //==============================================================================
788 bool URL::launchInDefaultBrowser() const
790 auto u = toString (true);
792 if (u.containsChar ('@') && ! u.containsChar (':'))
793 u = "mailto:" + u;
795 return Process::openDocument (u, {});
798 //==============================================================================
799 std::unique_ptr<InputStream> URL::createInputStream (bool usePostCommand,
800 OpenStreamProgressCallback* cb,
801 void* context,
802 String headers,
803 int timeOutMs,
804 StringPairArray* responseHeaders,
805 int* statusCode,
806 int numRedirectsToFollow,
807 String httpRequestCmd) const
809 std::function<bool (int, int)> callback;
811 if (cb != nullptr)
812 callback = [context, cb] (int sent, int total) { return cb (context, sent, total); };
814 return createInputStream (InputStreamOptions (toHandling (usePostCommand))
815 .withProgressCallback (std::move (callback))
816 .withExtraHeaders (headers)
817 .withConnectionTimeoutMs (timeOutMs)
818 .withResponseHeaders (responseHeaders)
819 .withStatusCode (statusCode)
820 .withNumRedirectsToFollow(numRedirectsToFollow)
821 .withHttpRequestCmd (httpRequestCmd));
824 std::unique_ptr<URL::DownloadTask> URL::downloadToFile (const File& targetLocation,
825 String extraHeaders,
826 DownloadTask::Listener* listener,
827 bool usePostCommand)
829 return nullptr;
832 } // namespace juce