OpenShot Audio Library | OpenShotAudio  0.6.0
juce_URL.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2022 - Raw Material Software Limited
6 
7  JUCE is an open source library subject to commercial or open-source
8  licensing.
9 
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.
15 
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.
19 
20  ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 struct FallbackDownloadTask final : public URL::DownloadTask,
27  public Thread
28 {
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),
37  buffer (bufferSize),
38  listener (listenerToUse)
39  {
40  jassert (fileStream != nullptr);
41  jassert (stream != nullptr);
42 
43  targetLocation = fileStream->getFile();
44  contentLength = stream->getTotalLength();
45  httpCode = stream->getStatusCode();
46 
47  startThread();
48  }
49 
50  ~FallbackDownloadTask() override
51  {
53  stream->cancel();
55  }
56 
57  //==============================================================================
58  void run() override
59  {
60  while (! (stream->isExhausted() || stream->isError() || threadShouldExit()))
61  {
62  if (listener != nullptr)
63  listener->progress (this, downloaded, contentLength);
64 
65  auto max = (int) jmin ((int64) bufferSize, contentLength < 0 ? std::numeric_limits<int64>::max()
66  : static_cast<int64> (contentLength - downloaded));
67 
68  auto actual = stream->read (buffer.get(), max);
69 
70  if (actual < 0 || threadShouldExit() || stream->isError())
71  break;
72 
73  if (! fileStream->write (buffer.get(), static_cast<size_t> (actual)))
74  {
75  error = true;
76  break;
77  }
78 
79  downloaded += actual;
80 
81  if (downloaded == contentLength)
82  break;
83  }
84 
85  fileStream.reset();
86 
87  if (threadShouldExit() || stream->isError())
88  error = true;
89 
90  if (contentLength > 0 && downloaded < contentLength)
91  error = true;
92 
93  finished = true;
94 
95  if (listener != nullptr && ! threadShouldExit())
96  listener->finished (this, ! error);
97  }
98 
99  //==============================================================================
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;
105 
106  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FallbackDownloadTask)
107 };
108 
110 
111 //==============================================================================
112 std::unique_ptr<URL::DownloadTask> URL::DownloadTask::createFallbackDownloader (const URL& urlToUse,
113  const File& targetFileToUse,
114  const DownloadTaskOptions& options)
115 {
116  const size_t bufferSize = 0x8000;
117  targetFileToUse.deleteFile();
118 
119  if (auto outputStream = targetFileToUse.createOutputStream (bufferSize))
120  {
121  auto stream = std::make_unique<WebInputStream> (urlToUse, options.usePost);
122  stream->withExtraHeaders (options.extraHeaders);
123 
124  if (stream->connect (nullptr))
125  return std::make_unique<FallbackDownloadTask> (std::move (outputStream),
126  bufferSize,
127  std::move (stream),
128  options.listener);
129  }
130 
131  return nullptr;
132 }
133 
134 URL::DownloadTask::DownloadTask() {}
136 
137 //==============================================================================
139 
140 URL::URL (const String& u) : url (u)
141 {
142  init();
143 }
144 
145 URL::URL (File localFile)
146 {
147  if (localFile == File())
148  return;
149 
150  #if JUCE_WINDOWS
151  bool isUncPath = localFile.getFullPathName().startsWith ("\\\\");
152  #endif
153 
154  while (! localFile.isRoot())
155  {
156  url = "/" + addEscapeChars (localFile.getFileName(), false) + url;
157  localFile = localFile.getParentDirectory();
158  }
159 
160  url = addEscapeChars (localFile.getFileName(), false) + url;
161 
162  #if JUCE_WINDOWS
163  if (isUncPath)
164  {
165  url = url.fromFirstOccurrenceOf ("/", false, false);
166  }
167  else
168  #endif
169  {
170  if (! url.startsWithChar (L'/'))
171  url = "/" + url;
172  }
173 
174  url = "file://" + url;
175 
176  jassert (isWellFormed());
177 }
178 
179 void URL::init()
180 {
181  auto i = url.indexOfChar ('#');
182 
183  if (i >= 0)
184  {
185  anchor = removeEscapeChars (url.substring (i + 1));
186  url = url.upToFirstOccurrenceOf ("#", false, false);
187  }
188 
189  i = url.indexOfChar ('?');
190 
191  if (i >= 0)
192  {
193  do
194  {
195  auto nextAmp = url.indexOfChar (i + 1, '&');
196  auto equalsPos = url.indexOfChar (i + 1, '=');
197 
198  if (nextAmp < 0)
199  {
200  addParameter (removeEscapeChars (equalsPos < 0 ? url.substring (i + 1) : url.substring (i + 1, equalsPos)),
201  equalsPos < 0 ? String() : removeEscapeChars (url.substring (equalsPos + 1)));
202  }
203  else if (nextAmp > 0 && equalsPos < nextAmp)
204  {
205  addParameter (removeEscapeChars (equalsPos < 0 ? url.substring (i + 1, nextAmp) : url.substring (i + 1, equalsPos)),
206  equalsPos < 0 ? String() : removeEscapeChars (url.substring (equalsPos + 1, nextAmp)));
207  }
208 
209  i = nextAmp;
210  }
211  while (i >= 0);
212 
213  url = url.upToFirstOccurrenceOf ("?", false, false);
214  }
215 }
216 
217 URL::URL (const String& u, int) : url (u) {}
218 
220 {
221  return URL (u, 0);
222 }
223 
224 bool URL::operator== (const URL& other) const
225 {
226  return url == other.url
227  && postData == other.postData
228  && parameterNames == other.parameterNames
229  && parameterValues == other.parameterValues
230  && filesToUpload == other.filesToUpload;
231 }
232 
233 bool URL::operator!= (const URL& other) const
234 {
235  return ! operator== (other);
236 }
237 
238 namespace URLHelpers
239 {
240  static String getMangledParameters (const URL& url)
241  {
242  jassert (url.getParameterNames().size() == url.getParameterValues().size());
243  String p;
244 
245  for (int i = 0; i < url.getParameterNames().size(); ++i)
246  {
247  if (i > 0)
248  p << '&';
249 
250  auto val = url.getParameterValues()[i];
251 
252  p << URL::addEscapeChars (url.getParameterNames()[i], true);
253 
254  if (val.isNotEmpty())
255  p << '=' << URL::addEscapeChars (val, true);
256  }
257 
258  return p;
259  }
260 
261  static int findEndOfScheme (const String& url)
262  {
263  int i = 0;
264 
266  || url[i] == '+' || url[i] == '-' || url[i] == '.')
267  ++i;
268 
269  return url.substring (i).startsWith ("://") ? i + 1 : 0;
270  }
271 
272  static int findStartOfNetLocation (const String& url)
273  {
274  int start = findEndOfScheme (url);
275 
276  while (url[start] == '/')
277  ++start;
278 
279  return start;
280  }
281 
282  static int findStartOfPath (const String& url)
283  {
284  return url.indexOfChar (findStartOfNetLocation (url), '/') + 1;
285  }
286 
287  static void concatenatePaths (String& path, const String& suffix)
288  {
289  if (! path.endsWithChar ('/'))
290  path << '/';
291 
292  if (suffix.startsWithChar ('/'))
293  path += suffix.substring (1);
294  else
295  path += suffix;
296  }
297 
298  static String removeLastPathSection (const String& url)
299  {
300  auto startOfPath = findStartOfPath (url);
301  auto lastSlash = url.lastIndexOfChar ('/');
302 
303  if (lastSlash > startOfPath && lastSlash == url.length() - 1)
304  return removeLastPathSection (url.dropLastCharacters (1));
305 
306  if (lastSlash < 0)
307  return url;
308 
309  return url.substring (0, std::max (startOfPath, lastSlash));
310  }
311 }
312 
313 void URL::addParameter (const String& name, const String& value)
314 {
315  parameterNames.add (name);
316  parameterValues.add (value);
317 }
318 
319 String URL::toString (bool includeGetParameters) const
320 {
321  if (includeGetParameters)
322  return url + getQueryString();
323 
324  return url;
325 }
326 
327 bool URL::isEmpty() const noexcept
328 {
329  return url.isEmpty();
330 }
331 
332 bool URL::isWellFormed() const
333 {
334  //xxx TODO
335  return url.isNotEmpty();
336 }
337 
339 {
340  return getDomainInternal (false);
341 }
342 
343 String URL::getSubPath (bool includeGetParameters) const
344 {
345  auto startOfPath = URLHelpers::findStartOfPath (url);
346  auto subPath = startOfPath <= 0 ? String()
347  : url.substring (startOfPath);
348 
349  if (includeGetParameters)
350  subPath += getQueryString();
351 
352  return subPath;
353 }
354 
356 {
357  String result;
358 
359  if (parameterNames.size() > 0)
360  result += "?" + URLHelpers::getMangledParameters (*this);
361 
362  if (anchor.isNotEmpty())
363  result += getAnchorString();
364 
365  return result;
366 }
367 
369 {
370  if (anchor.isNotEmpty())
371  return "#" + URL::addEscapeChars (anchor, true);
372 
373  return {};
374 }
375 
377 {
378  return url.substring (0, URLHelpers::findEndOfScheme (url) - 1);
379 }
380 
381 #if ! JUCE_ANDROID
382 bool URL::isLocalFile() const
383 {
384  return getScheme() == "file";
385 }
386 
388 {
389  return fileFromFileSchemeURL (*this);
390 }
391 
393 {
394  return toString (false).fromLastOccurrenceOf ("/", false, true);
395 }
396 #endif
397 
398 URL::ParameterHandling URL::toHandling (bool usePostData)
399 {
400  return usePostData ? ParameterHandling::inPostData : ParameterHandling::inAddress;
401 }
402 
403 File URL::fileFromFileSchemeURL (const URL& fileURL)
404 {
405  if (! fileURL.isLocalFile())
406  {
407  jassertfalse;
408  return {};
409  }
410 
411  auto path = removeEscapeChars (fileURL.getDomainInternal (true)).replace ("+", "%2B");
412 
413  #if JUCE_WINDOWS
414  bool isUncPath = (! fileURL.url.startsWith ("file:///"));
415  #else
416  path = File::getSeparatorString() + path;
417  #endif
418 
419  auto urlElements = StringArray::fromTokens (fileURL.getSubPath(), "/", "");
420 
421  for (auto urlElement : urlElements)
422  path += File::getSeparatorString() + removeEscapeChars (urlElement.replace ("+", "%2B"));
423 
424  #if JUCE_WINDOWS
425  if (isUncPath)
426  path = "\\\\" + path;
427  #endif
428 
429  return path;
430 }
431 
432 int URL::getPort() const
433 {
434  auto colonPos = url.indexOfChar (URLHelpers::findStartOfNetLocation (url), ':');
435 
436  return colonPos > 0 ? url.substring (colonPos + 1).getIntValue() : 0;
437 }
438 
439 URL URL::withNewDomainAndPath (const String& newURL) const
440 {
441  URL u (*this);
442  u.url = newURL;
443  return u;
444 }
445 
446 URL URL::withNewSubPath (const String& newPath) const
447 {
448  URL u (*this);
449 
450  auto startOfPath = URLHelpers::findStartOfPath (url);
451 
452  if (startOfPath > 0)
453  u.url = url.substring (0, startOfPath);
454 
455  URLHelpers::concatenatePaths (u.url, newPath);
456  return u;
457 }
458 
460 {
461  URL u (*this);
462  u.url = URLHelpers::removeLastPathSection (u.url);
463  return u;
464 }
465 
466 URL URL::getChildURL (const String& subPath) const
467 {
468  URL u (*this);
469  URLHelpers::concatenatePaths (u.url, subPath);
470  return u;
471 }
472 
473 bool URL::hasBodyDataToSend() const
474 {
475  return filesToUpload.size() > 0 || ! postData.isEmpty();
476 }
477 
478 void URL::createHeadersAndPostData (String& headers,
479  MemoryBlock& postDataToWrite,
480  bool addParametersToBody) const
481 {
482  MemoryOutputStream data (postDataToWrite, false);
483 
484  if (filesToUpload.size() > 0)
485  {
486  // (this doesn't currently support mixing custom post-data with uploads..)
487  jassert (postData.isEmpty());
488 
489  auto boundary = String::toHexString (Random::getSystemRandom().nextInt64());
490 
491  headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n";
492 
493  data << "--" << boundary;
494 
495  for (int i = 0; i < parameterNames.size(); ++i)
496  {
497  data << "\r\nContent-Disposition: form-data; name=\"" << parameterNames[i]
498  << "\"\r\n\r\n" << parameterValues[i]
499  << "\r\n--" << boundary;
500  }
501 
502  for (auto* f : filesToUpload)
503  {
504  data << "\r\nContent-Disposition: form-data; name=\"" << f->parameterName
505  << "\"; filename=\"" << f->filename << "\"\r\n";
506 
507  if (f->mimeType.isNotEmpty())
508  data << "Content-Type: " << f->mimeType << "\r\n";
509 
510  data << "Content-Transfer-Encoding: binary\r\n\r\n";
511 
512  if (f->data != nullptr)
513  data << *f->data;
514  else
515  data << f->file;
516 
517  data << "\r\n--" << boundary;
518  }
519 
520  data << "--\r\n";
521  }
522  else
523  {
524  if (addParametersToBody)
525  data << URLHelpers::getMangledParameters (*this);
526 
527  data << postData;
528 
529  // if the user-supplied headers didn't contain a content-type, add one now..
530  if (! headers.containsIgnoreCase ("Content-Type"))
531  headers << "Content-Type: application/x-www-form-urlencoded\r\n";
532 
533  headers << "Content-length: " << (int) data.getDataSize() << "\r\n";
534  }
535 }
536 
537 //==============================================================================
538 bool URL::isProbablyAWebsiteURL (const String& possibleURL)
539 {
540  for (auto* protocol : { "http:", "https:", "ftp:" })
541  if (possibleURL.startsWithIgnoreCase (protocol))
542  return true;
543 
544  if (possibleURL.containsChar ('@') || possibleURL.containsChar (' '))
545  return false;
546 
547  auto topLevelDomain = possibleURL.upToFirstOccurrenceOf ("/", false, false)
548  .fromLastOccurrenceOf (".", false, false);
549 
550  return topLevelDomain.isNotEmpty() && topLevelDomain.length() <= 3;
551 }
552 
553 bool URL::isProbablyAnEmailAddress (const String& possibleEmailAddress)
554 {
555  auto atSign = possibleEmailAddress.indexOfChar ('@');
556 
557  return atSign > 0
558  && possibleEmailAddress.lastIndexOfChar ('.') > (atSign + 1)
559  && ! possibleEmailAddress.endsWithChar ('.');
560 }
561 
562 String URL::getDomainInternal (bool ignorePort) const
563 {
564  auto start = URLHelpers::findStartOfNetLocation (url);
565  auto end1 = url.indexOfChar (start, '/');
566  auto end2 = ignorePort ? -1 : url.indexOfChar (start, ':');
567 
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);
572 }
573 
574 #if JUCE_IOS
575 URL::Bookmark::Bookmark (void* bookmarkToUse) : data (bookmarkToUse)
576 {
577 }
578 
579 URL::Bookmark::~Bookmark()
580 {
581  [(NSData*) data release];
582 }
583 
584 void setURLBookmark (URL& u, void* bookmark)
585 {
586  u.bookmark = new URL::Bookmark (bookmark);
587 }
588 
589 void* getURLBookmark (URL& u)
590 {
591  if (u.bookmark.get() == nullptr)
592  return nullptr;
593 
594  return u.bookmark.get()->data;
595 }
596 
597 template <typename Stream> struct iOSFileStreamWrapperFlush { static void flush (Stream*) {} };
598 template <> struct iOSFileStreamWrapperFlush<FileOutputStream> { static void flush (OutputStream* o) { o->flush(); } };
599 
600 template <typename Stream>
601 class iOSFileStreamWrapper final : public Stream
602 {
603 public:
604  iOSFileStreamWrapper (URL& urlToUse)
605  : Stream (getLocalFileAccess (urlToUse)),
606  url (urlToUse)
607  {}
608 
609  ~iOSFileStreamWrapper()
610  {
611  iOSFileStreamWrapperFlush<Stream>::flush (this);
612 
613  if (NSData* bookmark = (NSData*) getURLBookmark (url))
614  {
615  BOOL isBookmarkStale = false;
616  NSError* error = nil;
617 
618  auto nsURL = [NSURL URLByResolvingBookmarkData: bookmark
619  options: 0
620  relativeToURL: nil
621  bookmarkDataIsStale: &isBookmarkStale
622  error: &error];
623 
624  if (error == nil)
625  {
626  if (isBookmarkStale)
627  updateStaleBookmark (nsURL, url);
628 
629  [nsURL stopAccessingSecurityScopedResource];
630  }
631  else
632  {
633  [[maybe_unused]] auto desc = [error localizedDescription];
634  jassertfalse;
635  }
636  }
637  }
638 
639 private:
640  URL url;
641  bool securityAccessSucceeded = false;
642 
643  File getLocalFileAccess (URL& urlToUse)
644  {
645  if (NSData* bookmark = (NSData*) getURLBookmark (urlToUse))
646  {
647  BOOL isBookmarkStale = false;
648  NSError* error = nil;
649 
650  auto nsURL = [NSURL URLByResolvingBookmarkData: bookmark
651  options: 0
652  relativeToURL: nil
653  bookmarkDataIsStale: &isBookmarkStale
654  error: &error];
655 
656  if (error == nil)
657  {
658  securityAccessSucceeded = [nsURL startAccessingSecurityScopedResource];
659 
660  if (isBookmarkStale)
661  updateStaleBookmark (nsURL, urlToUse);
662 
663  return urlToUse.getLocalFile();
664  }
665 
666  [[maybe_unused]] auto desc = [error localizedDescription];
667  jassertfalse;
668  }
669 
670  return urlToUse.getLocalFile();
671  }
672 
673  void updateStaleBookmark (NSURL* nsURL, URL& juceUrl)
674  {
675  NSError* error = nil;
676 
677  NSData* bookmark = [nsURL bookmarkDataWithOptions: NSURLBookmarkCreationSuitableForBookmarkFile
678  includingResourceValuesForKeys: nil
679  relativeToURL: nil
680  error: &error];
681 
682  if (error == nil)
683  setURLBookmark (juceUrl, (void*) bookmark);
684  else
685  jassertfalse;
686  }
687 };
688 #endif
689 //==============================================================================
690 template <typename Member, typename Item>
691 static URL::InputStreamOptions with (URL::InputStreamOptions options, Member&& member, Item&& item)
692 {
693  options.*member = std::forward<Item> (item);
694  return options;
695 }
696 
697 URL::InputStreamOptions::InputStreamOptions (ParameterHandling handling) : parameterHandling (handling) {}
698 
700 {
701  return with (*this, &InputStreamOptions::progressCallback, std::move (cb));
702 }
703 
705 {
706  return with (*this, &InputStreamOptions::extraHeaders, headers);
707 }
708 
710 {
711  return with (*this, &InputStreamOptions::connectionTimeOutMs, timeout);
712 }
713 
715 {
716  return with (*this, &InputStreamOptions::responseHeaders, headers);
717 }
718 
720 {
721  return with (*this, &InputStreamOptions::statusCode, status);
722 }
723 
725 {
726  return with (*this, &InputStreamOptions::numRedirectsToFollow, numRedirects);
727 }
728 
730 {
731  return with (*this, &InputStreamOptions::httpRequestCmd, cmd);
732 }
733 
734 //==============================================================================
735 std::unique_ptr<InputStream> URL::createInputStream (const InputStreamOptions& options) const
736 {
737  if (isLocalFile())
738  {
739  #if JUCE_IOS
740  // We may need to refresh the embedded bookmark.
741  return std::make_unique<iOSFileStreamWrapper<FileInputStream>> (const_cast<URL&> (*this));
742  #else
743  return getLocalFile().createInputStream();
744  #endif
745  }
746 
747  auto webInputStream = [&]
748  {
749  const auto usePost = options.getParameterHandling() == ParameterHandling::inPostData;
750  auto stream = std::make_unique<WebInputStream> (*this, usePost);
751 
752  auto extraHeaders = options.getExtraHeaders();
753 
754  if (extraHeaders.isNotEmpty())
755  stream->withExtraHeaders (extraHeaders);
756 
757  auto timeout = options.getConnectionTimeoutMs();
758 
759  if (timeout != 0)
760  stream->withConnectionTimeout (timeout);
761 
762  auto requestCmd = options.getHttpRequestCmd();
763 
764  if (requestCmd.isNotEmpty())
765  stream->withCustomRequestCommand (requestCmd);
766 
767  stream->withNumRedirectsToFollow (options.getNumRedirectsToFollow());
768 
769  return stream;
770  }();
771 
772  struct ProgressCallbackCaller final : public WebInputStream::Listener
773  {
774  ProgressCallbackCaller (std::function<bool (int, int)> progressCallbackToUse)
775  : callback (std::move (progressCallbackToUse))
776  {
777  }
778 
779  bool postDataSendProgress (WebInputStream&, int bytesSent, int totalBytes) override
780  {
781  return callback (bytesSent, totalBytes);
782  }
783 
784  std::function<bool (int, int)> callback;
785  };
786 
787  auto callbackCaller = [&options]() -> std::unique_ptr<ProgressCallbackCaller>
788  {
789  if (auto progressCallback = options.getProgressCallback())
790  return std::make_unique<ProgressCallbackCaller> (progressCallback);
791 
792  return {};
793  }();
794 
795  auto success = webInputStream->connect (callbackCaller.get());
796 
797  if (auto* status = options.getStatusCode())
798  *status = webInputStream->getStatusCode();
799 
800  if (auto* responseHeaders = options.getResponseHeaders())
801  *responseHeaders = webInputStream->getResponseHeaders();
802 
803  if (! success || webInputStream->isError())
804  return nullptr;
805 
806  // std::move() needed here for older compilers
807  JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wredundant-move")
808  return std::move (webInputStream);
809  JUCE_END_IGNORE_WARNINGS_GCC_LIKE
810 }
811 
812 std::unique_ptr<OutputStream> URL::createOutputStream() const
813 {
814  #if JUCE_ANDROID
815  if (auto stream = AndroidDocument::fromDocument (*this).createOutputStream())
816  return stream;
817  #endif
818 
819  if (isLocalFile())
820  {
821  #if JUCE_IOS
822  // We may need to refresh the embedded bookmark.
823  return std::make_unique<iOSFileStreamWrapper<FileOutputStream>> (const_cast<URL&> (*this));
824  #else
825  return std::make_unique<FileOutputStream> (getLocalFile());
826  #endif
827  }
828 
829  return nullptr;
830 }
831 
832 //==============================================================================
833 bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) const
834 {
835  const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
836  : createInputStream (InputStreamOptions (toHandling (usePostCommand))));
837 
838  if (in != nullptr)
839  {
840  in->readIntoMemoryBlock (destData);
841  return true;
842  }
843 
844  return false;
845 }
846 
847 String URL::readEntireTextStream (bool usePostCommand) const
848 {
849  const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
850  : createInputStream (InputStreamOptions (toHandling (usePostCommand))));
851 
852  if (in != nullptr)
853  return in->readEntireStreamAsString();
854 
855  return {};
856 }
857 
858 std::unique_ptr<XmlElement> URL::readEntireXmlStream (bool usePostCommand) const
859 {
860  return parseXML (readEntireTextStream (usePostCommand));
861 }
862 
863 //==============================================================================
864 URL URL::withParameter (const String& parameterName,
865  const String& parameterValue) const
866 {
867  auto u = *this;
868  u.addParameter (parameterName, parameterValue);
869  return u;
870 }
871 
872 URL URL::withParameters (const StringPairArray& parametersToAdd) const
873 {
874  auto u = *this;
875 
876  for (int i = 0; i < parametersToAdd.size(); ++i)
877  u.addParameter (parametersToAdd.getAllKeys()[i],
878  parametersToAdd.getAllValues()[i]);
879 
880  return u;
881 }
882 
883 URL URL::withAnchor (const String& anchorToAdd) const
884 {
885  auto u = *this;
886 
887  u.anchor = anchorToAdd;
888  return u;
889 }
890 
891 URL URL::withPOSTData (const String& newPostData) const
892 {
893  return withPOSTData (MemoryBlock (newPostData.toRawUTF8(), newPostData.getNumBytesAsUTF8()));
894 }
895 
896 URL URL::withPOSTData (const MemoryBlock& newPostData) const
897 {
898  auto u = *this;
899  u.postData = newPostData;
900  return u;
901 }
902 
903 URL::Upload::Upload (const String& param, const String& name,
904  const String& mime, const File& f, MemoryBlock* mb)
905  : parameterName (param), filename (name), mimeType (mime), file (f), data (mb)
906 {
907  jassert (mimeType.isNotEmpty()); // You need to supply a mime type!
908 }
909 
910 URL URL::withUpload (Upload* const f) const
911 {
912  auto u = *this;
913 
914  for (int i = u.filesToUpload.size(); --i >= 0;)
915  if (u.filesToUpload.getObjectPointerUnchecked (i)->parameterName == f->parameterName)
916  u.filesToUpload.remove (i);
917 
918  u.filesToUpload.add (f);
919  return u;
920 }
921 
922 URL URL::withFileToUpload (const String& parameterName, const File& fileToUpload,
923  const String& mimeType) const
924 {
925  return withUpload (new Upload (parameterName, fileToUpload.getFileName(),
926  mimeType, fileToUpload, nullptr));
927 }
928 
929 URL URL::withDataToUpload (const String& parameterName, const String& filename,
930  const MemoryBlock& fileContentToUpload, const String& mimeType) const
931 {
932  return withUpload (new Upload (parameterName, filename, mimeType, File(),
933  new MemoryBlock (fileContentToUpload)));
934 }
935 
936 //==============================================================================
938 {
939  auto result = s.replaceCharacter ('+', ' ');
940 
941  if (! result.containsChar ('%'))
942  return result;
943 
944  // We need to operate on the string as raw UTF8 chars, and then recombine them into unicode
945  // after all the replacements have been made, so that multi-byte chars are handled.
946  Array<char> utf8 (result.toRawUTF8(), (int) result.getNumBytesAsUTF8());
947 
948  for (int i = 0; i < utf8.size(); ++i)
949  {
950  if (utf8.getUnchecked (i) == '%')
951  {
952  auto hexDigit1 = CharacterFunctions::getHexDigitValue ((juce_wchar) (uint8) utf8 [i + 1]);
953  auto hexDigit2 = CharacterFunctions::getHexDigitValue ((juce_wchar) (uint8) utf8 [i + 2]);
954 
955  if (hexDigit1 >= 0 && hexDigit2 >= 0)
956  {
957  utf8.set (i, (char) ((hexDigit1 << 4) + hexDigit2));
958  utf8.removeRange (i + 1, 2);
959  }
960  }
961  }
962 
963  return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size());
964 }
965 
966 String URL::addEscapeChars (const String& s, bool isParameter, bool roundBracketsAreLegal)
967 {
968  String legalChars (isParameter ? "_-.~"
969  : ",$_-.*!'");
970 
971  if (roundBracketsAreLegal)
972  legalChars += "()";
973 
974  Array<char> utf8 (s.toRawUTF8(), (int) s.getNumBytesAsUTF8());
975 
976  for (int i = 0; i < utf8.size(); ++i)
977  {
978  auto c = utf8.getUnchecked (i);
979 
981  || legalChars.containsChar ((juce_wchar) c)))
982  {
983  utf8.set (i, '%');
984  utf8.insert (++i, "0123456789ABCDEF" [((uint8) c) >> 4]);
985  utf8.insert (++i, "0123456789ABCDEF" [c & 15]);
986  }
987  }
988 
989  return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size());
990 }
991 
992 //==============================================================================
994 {
995  auto u = toString (true);
996 
997  if (u.containsChar ('@') && ! u.containsChar (':'))
998  u = "mailto:" + u;
999 
1000  return Process::openDocument (u, {});
1001 }
1002 
1003 //==============================================================================
1004 std::unique_ptr<InputStream> URL::createInputStream (bool usePostCommand,
1005  OpenStreamProgressCallback* cb,
1006  void* context,
1007  String headers,
1008  int timeOutMs,
1009  StringPairArray* responseHeaders,
1010  int* statusCode,
1011  int numRedirectsToFollow,
1012  String httpRequestCmd) const
1013 {
1014  std::function<bool (int, int)> callback;
1015 
1016  if (cb != nullptr)
1017  callback = [context, cb] (int sent, int total) { return cb (context, sent, total); };
1018 
1019  return createInputStream (InputStreamOptions (toHandling (usePostCommand))
1020  .withProgressCallback (std::move (callback))
1021  .withExtraHeaders (headers)
1022  .withConnectionTimeoutMs (timeOutMs)
1023  .withResponseHeaders (responseHeaders)
1024  .withStatusCode (statusCode)
1025  .withNumRedirectsToFollow (numRedirectsToFollow)
1026  .withHttpRequestCmd (httpRequestCmd));
1027 }
1028 
1029 std::unique_ptr<URL::DownloadTask> URL::downloadToFile (const File& targetLocation,
1030  String extraHeaders,
1031  DownloadTask::Listener* listener,
1032  bool usePostCommand)
1033 {
1034  auto options = DownloadTaskOptions().withExtraHeaders (std::move (extraHeaders))
1035  .withListener (listener)
1036  .withUsePost (usePostCommand);
1037  return downloadToFile (targetLocation, std::move (options));
1038 }
1039 
1040 } // namespace juce
std::unique_ptr< OutputStream > createOutputStream() const
static AndroidDocument fromDocument(const URL &documentUrl)
ElementType getUnchecked(int index) const
Definition: juce_Array.h:252
int size() const noexcept
Definition: juce_Array.h:215
void removeRange(int startIndex, int numberToRemove)
Definition: juce_Array.h:894
void insert(int indexToInsertAt, ParameterType newElement)
Definition: juce_Array.h:462
void set(int indexToChange, ParameterType newValue)
Definition: juce_Array.h:542
ElementType * getRawDataPointer() noexcept
Definition: juce_Array.h:310
static int getHexDigitValue(juce_wchar digit) noexcept
static bool isLetterOrDigit(char character) noexcept
std::unique_ptr< FileOutputStream > createOutputStream(size_t bufferSize=0x8000) const
Definition: juce_File.cpp:742
const String & getFullPathName() const noexcept
Definition: juce_File.h:153
String getFileName() const
Definition: juce_File.cpp:372
bool isRoot() const
Definition: juce_File.cpp:125
File getParentDirectory() const
Definition: juce_File.cpp:358
std::unique_ptr< FileInputStream > createInputStream() const
Definition: juce_File.cpp:732
bool deleteFile() const
static StringRef getSeparatorString()
bool isEmpty() const noexcept
static bool JUCE_CALLTYPE openDocument(const String &documentURL, const String &parameters)
static Random & getSystemRandom() noexcept
Definition: juce_Random.cpp:67
static StringArray fromTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
int size() const noexcept
void add(String stringToAdd)
const StringArray & getAllKeys() const noexcept
const StringArray & getAllValues() const noexcept
int size() 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)
Definition: juce_String.h:1097
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
Definition: juce_String.h:316
String fromFirstOccurrenceOf(StringRef substringToStartFrom, bool includeSubStringInResult, bool ignoreCase) const
bool waitForThreadToExit(int timeOutMilliseconds) const
Thread(const String &threadName, size_t threadStackSize=osDefaultStackSize)
Definition: juce_Thread.cpp:26
bool startThread()
bool threadShouldExit() const
void signalThreadShouldExit()
auto withExtraHeaders(String value) const
Definition: juce_URL.h:468
InputStreamOptions withExtraHeaders(const String &extraHeaders) const
Definition: juce_URL.cpp:704
InputStreamOptions withResponseHeaders(StringPairArray *responseHeaders) const
Definition: juce_URL.cpp:714
InputStreamOptions withStatusCode(int *statusCode) const
Definition: juce_URL.cpp:719
InputStreamOptions withNumRedirectsToFollow(int numRedirectsToFollow) const
Definition: juce_URL.cpp:724
InputStreamOptions withConnectionTimeoutMs(int connectionTimeoutMs) const
Definition: juce_URL.cpp:709
InputStreamOptions(ParameterHandling parameterHandling)
Definition: juce_URL.cpp:697
InputStreamOptions withProgressCallback(std::function< bool(int bytesSent, int totalBytes)> progressCallback) const
Definition: juce_URL.cpp:699
InputStreamOptions withHttpRequestCmd(const String &httpRequestCmd) const
Definition: juce_URL.cpp:729
URL withParameter(const String &parameterName, const String &parameterValue) const
Definition: juce_URL.cpp:864
static URL createWithoutParsing(const String &url)
Definition: juce_URL.cpp:219
File getLocalFile() const
Definition: juce_URL.cpp:387
bool isWellFormed() const
Definition: juce_URL.cpp:332
bool readEntireBinaryStream(MemoryBlock &destData, bool usePostCommand=false) const
Definition: juce_URL.cpp:833
int getPort() const
Definition: juce_URL.cpp:432
URL withDataToUpload(const String &parameterName, const String &filename, const MemoryBlock &fileContentToUpload, const String &mimeType) const
Definition: juce_URL.cpp:929
String getFileName() const
Definition: juce_URL.cpp:392
URL getChildURL(const String &subPath) const
Definition: juce_URL.cpp:466
static String removeEscapeChars(const String &stringToRemoveEscapeCharsFrom)
Definition: juce_URL.cpp:937
String getAnchorString() const
Definition: juce_URL.cpp:368
static String addEscapeChars(const String &stringToAddEscapeCharsTo, bool isParameter, bool roundBracketsAreLegal=true)
Definition: juce_URL.cpp:966
URL withAnchor(const String &anchor) const
Definition: juce_URL.cpp:883
String toString(bool includeGetParameters) const
Definition: juce_URL.cpp:319
std::unique_ptr< OutputStream > createOutputStream() const
Definition: juce_URL.cpp:812
String getSubPath(bool includeGetParameters=false) const
Definition: juce_URL.cpp:343
URL getParentURL() const
Definition: juce_URL.cpp:459
String getQueryString() const
Definition: juce_URL.cpp:355
URL withNewSubPath(const String &newPath) const
Definition: juce_URL.cpp:446
static bool isProbablyAnEmailAddress(const String &possibleEmailAddress)
Definition: juce_URL.cpp:553
URL withNewDomainAndPath(const String &newFullPath) const
Definition: juce_URL.cpp:439
String readEntireTextStream(bool usePostCommand=false) const
Definition: juce_URL.cpp:847
bool isEmpty() const noexcept
Definition: juce_URL.cpp:327
static bool isProbablyAWebsiteURL(const String &possibleURL)
Definition: juce_URL.cpp:538
std::unique_ptr< InputStream > createInputStream(const InputStreamOptions &options) const
Definition: juce_URL.cpp:735
std::unique_ptr< DownloadTask > downloadToFile(const File &targetLocation, String extraHeaders=String(), DownloadTaskListener *listener=nullptr, bool usePostCommand=false)
Definition: juce_URL.cpp:1029
String getDomain() const
Definition: juce_URL.cpp:338
bool isLocalFile() const
Definition: juce_URL.cpp:382
URL withFileToUpload(const String &parameterName, const File &fileToUpload, const String &mimeType) const
Definition: juce_URL.cpp:922
URL withPOSTData(const String &postData) const
Definition: juce_URL.cpp:891
URL withParameters(const StringPairArray &parametersToAdd) const
Definition: juce_URL.cpp:872
std::unique_ptr< XmlElement > readEntireXmlStream(bool usePostCommand=false) const
Definition: juce_URL.cpp:858
String getScheme() const
Definition: juce_URL.cpp:376
bool operator==(const URL &) const
Definition: juce_URL.cpp:224
bool launchInDefaultBrowser() const
Definition: juce_URL.cpp:993
virtual void progress(DownloadTask *task, int64 bytesDownloaded, int64 totalLength)
Definition: juce_URL.cpp:109