OpenShot Audio Library | OpenShotAudio  0.3.0
juce_URL.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2017 - ROLI Ltd.
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 : public URL::DownloadTask,
27  public Thread
28 {
29  FallbackDownloadTask (FileOutputStream* outputStreamToUse,
30  size_t bufferSizeToUse,
31  WebInputStream* streamToUse,
32  URL::DownloadTask::Listener* listenerToUse)
33  : Thread ("DownloadTask thread"),
34  fileStream (outputStreamToUse),
35  stream (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 URL::DownloadTask::Listener::~Listener() {}
111 
112 //==============================================================================
113 URL::DownloadTask* URL::DownloadTask::createFallbackDownloader (const URL& urlToUse,
114  const File& targetFileToUse,
115  const String& extraHeadersToUse,
116  Listener* listenerToUse,
117  bool usePostRequest)
118 {
119  const size_t bufferSize = 0x8000;
120  targetFileToUse.deleteFile();
121 
122  if (auto outputStream = std::unique_ptr<FileOutputStream> (targetFileToUse.createOutputStream (bufferSize)))
123  {
124  std::unique_ptr<WebInputStream> stream (new WebInputStream (urlToUse, usePostRequest));
125  stream->withExtraHeaders (extraHeadersToUse);
126 
127  if (stream->connect (nullptr))
128  return new FallbackDownloadTask (outputStream.release(), bufferSize, stream.release(), listenerToUse);
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  do
186  {
187  auto nextAmp = url.indexOfChar (i + 1, '&');
188  auto equalsPos = url.indexOfChar (i + 1, '=');
189 
190  if (nextAmp < 0)
191  {
192  addParameter (removeEscapeChars (equalsPos < 0 ? url.substring (i + 1) : url.substring (i + 1, equalsPos)),
193  equalsPos < 0 ? String() : removeEscapeChars (url.substring (equalsPos + 1)));
194  }
195  else if (nextAmp > 0 && equalsPos < nextAmp)
196  {
197  addParameter (removeEscapeChars (equalsPos < 0 ? url.substring (i + 1, nextAmp) : url.substring (i + 1, equalsPos)),
198  equalsPos < 0 ? String() : removeEscapeChars (url.substring (equalsPos + 1, nextAmp)));
199  }
200 
201  i = nextAmp;
202  }
203  while (i >= 0);
204 
205  url = url.upToFirstOccurrenceOf ("?", false, false);
206  }
207 }
208 
209 URL::URL (const String& u, int) : url (u) {}
210 
212 {
213  return URL (u, 0);
214 }
215 
216 bool URL::operator== (const URL& other) const
217 {
218  return url == other.url
219  && postData == other.postData
220  && parameterNames == other.parameterNames
221  && parameterValues == other.parameterValues
222  && filesToUpload == other.filesToUpload;
223 }
224 
225 bool URL::operator!= (const URL& other) const
226 {
227  return ! operator== (other);
228 }
229 
230 namespace URLHelpers
231 {
232  static String getMangledParameters (const URL& url)
233  {
234  jassert (url.getParameterNames().size() == url.getParameterValues().size());
235  String p;
236 
237  for (int i = 0; i < url.getParameterNames().size(); ++i)
238  {
239  if (i > 0)
240  p << '&';
241 
242  auto val = url.getParameterValues()[i];
243 
244  p << URL::addEscapeChars (url.getParameterNames()[i], true);
245 
246  if (val.isNotEmpty())
247  p << '=' << URL::addEscapeChars (val, true);
248  }
249 
250  return p;
251  }
252 
253  static int findEndOfScheme (const String& url)
254  {
255  int i = 0;
256 
258  || url[i] == '+' || url[i] == '-' || url[i] == '.')
259  ++i;
260 
261  return url.substring (i).startsWith ("://") ? i + 1 : 0;
262  }
263 
264  static int findStartOfNetLocation (const String& url)
265  {
266  int start = findEndOfScheme (url);
267 
268  while (url[start] == '/')
269  ++start;
270 
271  return start;
272  }
273 
274  static int findStartOfPath (const String& url)
275  {
276  return url.indexOfChar (findStartOfNetLocation (url), '/') + 1;
277  }
278 
279  static void concatenatePaths (String& path, const String& suffix)
280  {
281  if (! path.endsWithChar ('/'))
282  path << '/';
283 
284  if (suffix.startsWithChar ('/'))
285  path += suffix.substring (1);
286  else
287  path += suffix;
288  }
289 
290  static String removeLastPathSection (const String& url)
291  {
292  auto startOfPath = findStartOfPath (url);
293  auto lastSlash = url.lastIndexOfChar ('/');
294 
295  if (lastSlash > startOfPath && lastSlash == url.length() - 1)
296  return removeLastPathSection (url.dropLastCharacters (1));
297 
298  if (lastSlash < 0)
299  return url;
300 
301  return url.substring (0, std::max (startOfPath, lastSlash));
302  }
303 }
304 
305 void URL::addParameter (const String& name, const String& value)
306 {
307  parameterNames.add (name);
308  parameterValues.add (value);
309 }
310 
311 String URL::toString (bool includeGetParameters) const
312 {
313  if (includeGetParameters)
314  return url + getQueryString();
315 
316  return url;
317 }
318 
319 bool URL::isEmpty() const noexcept
320 {
321  return url.isEmpty();
322 }
323 
324 bool URL::isWellFormed() const
325 {
326  //xxx TODO
327  return url.isNotEmpty();
328 }
329 
331 {
332  return getDomainInternal (false);
333 }
334 
335 String URL::getSubPath (bool includeGetParameters) const
336 {
337  auto startOfPath = URLHelpers::findStartOfPath (url);
338  auto subPath = startOfPath <= 0 ? String()
339  : url.substring (startOfPath);
340 
341  if (includeGetParameters)
342  subPath += getQueryString();
343 
344  return subPath;
345 }
346 
348 {
349  if (parameterNames.size() > 0)
350  return "?" + URLHelpers::getMangledParameters (*this);
351 
352  return {};
353 }
354 
356 {
357  return url.substring (0, URLHelpers::findEndOfScheme (url) - 1);
358 }
359 
360 #if ! JUCE_ANDROID
361 bool URL::isLocalFile() const
362 {
363  return getScheme() == "file";
364 }
365 
367 {
368  return fileFromFileSchemeURL (*this);
369 }
370 
372 {
373  return toString (false).fromLastOccurrenceOf ("/", false, true);
374 }
375 #endif
376 
377 File URL::fileFromFileSchemeURL (const URL& fileURL)
378 {
379  if (! fileURL.isLocalFile())
380  {
381  jassertfalse;
382  return {};
383  }
384 
385  auto path = removeEscapeChars (fileURL.getDomainInternal (true)).replace ("+", "%2B");
386 
387  #if JUCE_WINDOWS
388  bool isUncPath = (! fileURL.url.startsWith ("file:///"));
389  #else
390  path = File::getSeparatorString() + path;
391  #endif
392 
393  auto urlElements = StringArray::fromTokens (fileURL.getSubPath(), "/", "");
394 
395  for (auto urlElement : urlElements)
396  path += File::getSeparatorString() + removeEscapeChars (urlElement.replace ("+", "%2B"));
397 
398  #if JUCE_WINDOWS
399  if (isUncPath)
400  path = "\\\\" + path;
401  #endif
402 
403  return path;
404 }
405 
406 int URL::getPort() const
407 {
408  auto colonPos = url.indexOfChar (URLHelpers::findStartOfNetLocation (url), ':');
409 
410  return colonPos > 0 ? url.substring (colonPos + 1).getIntValue() : 0;
411 }
412 
413 URL URL::withNewDomainAndPath (const String& newURL) const
414 {
415  URL u (*this);
416  u.url = newURL;
417  return u;
418 }
419 
420 URL URL::withNewSubPath (const String& newPath) const
421 {
422  URL u (*this);
423 
424  auto startOfPath = URLHelpers::findStartOfPath (url);
425 
426  if (startOfPath > 0)
427  u.url = url.substring (0, startOfPath);
428 
429  URLHelpers::concatenatePaths (u.url, newPath);
430  return u;
431 }
432 
434 {
435  URL u (*this);
436  u.url = URLHelpers::removeLastPathSection (u.url);
437  return u;
438 }
439 
440 URL URL::getChildURL (const String& subPath) const
441 {
442  URL u (*this);
443  URLHelpers::concatenatePaths (u.url, subPath);
444  return u;
445 }
446 
447 void URL::createHeadersAndPostData (String& headers, MemoryBlock& postDataToWrite) const
448 {
449  MemoryOutputStream data (postDataToWrite, false);
450 
451  if (filesToUpload.size() > 0)
452  {
453  // (this doesn't currently support mixing custom post-data with uploads..)
454  jassert (postData.getSize() == 0);
455 
456  auto boundary = String::toHexString (Random::getSystemRandom().nextInt64());
457 
458  headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n";
459 
460  data << "--" << boundary;
461 
462  for (int i = 0; i < parameterNames.size(); ++i)
463  {
464  data << "\r\nContent-Disposition: form-data; name=\"" << parameterNames[i]
465  << "\"\r\n\r\n" << parameterValues[i]
466  << "\r\n--" << boundary;
467  }
468 
469  for (auto* f : filesToUpload)
470  {
471  data << "\r\nContent-Disposition: form-data; name=\"" << f->parameterName
472  << "\"; filename=\"" << f->filename << "\"\r\n";
473 
474  if (f->mimeType.isNotEmpty())
475  data << "Content-Type: " << f->mimeType << "\r\n";
476 
477  data << "Content-Transfer-Encoding: binary\r\n\r\n";
478 
479  if (f->data != nullptr)
480  data << *f->data;
481  else
482  data << f->file;
483 
484  data << "\r\n--" << boundary;
485  }
486 
487  data << "--\r\n";
488  }
489  else
490  {
491  data << URLHelpers::getMangledParameters (*this)
492  << postData;
493 
494  // if the user-supplied headers didn't contain a content-type, add one now..
495  if (! headers.containsIgnoreCase ("Content-Type"))
496  headers << "Content-Type: application/x-www-form-urlencoded\r\n";
497 
498  headers << "Content-length: " << (int) data.getDataSize() << "\r\n";
499  }
500 }
501 
502 //==============================================================================
503 bool URL::isProbablyAWebsiteURL (const String& possibleURL)
504 {
505  for (auto* protocol : { "http:", "https:", "ftp:" })
506  if (possibleURL.startsWithIgnoreCase (protocol))
507  return true;
508 
509  if (possibleURL.containsChar ('@') || possibleURL.containsChar (' '))
510  return false;
511 
512  auto topLevelDomain = possibleURL.upToFirstOccurrenceOf ("/", false, false)
513  .fromLastOccurrenceOf (".", false, false);
514 
515  return topLevelDomain.isNotEmpty() && topLevelDomain.length() <= 3;
516 }
517 
518 bool URL::isProbablyAnEmailAddress (const String& possibleEmailAddress)
519 {
520  auto atSign = possibleEmailAddress.indexOfChar ('@');
521 
522  return atSign > 0
523  && possibleEmailAddress.lastIndexOfChar ('.') > (atSign + 1)
524  && ! possibleEmailAddress.endsWithChar ('.');
525 }
526 
527 String URL::getDomainInternal (bool ignorePort) const
528 {
529  auto start = URLHelpers::findStartOfNetLocation (url);
530  auto end1 = url.indexOfChar (start, '/');
531  auto end2 = ignorePort ? -1 : url.indexOfChar (start, ':');
532 
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);
537 }
538 
539 #if JUCE_IOS
540 URL::Bookmark::Bookmark (void* bookmarkToUse) : data (bookmarkToUse)
541 {
542 }
543 
544 URL::Bookmark::~Bookmark()
545 {
546  [(NSData*) data release];
547 }
548 
549 void setURLBookmark (URL& u, void* bookmark)
550 {
551  u.bookmark = new URL::Bookmark (bookmark);
552 }
553 
554 void* getURLBookmark (URL& u)
555 {
556  if (u.bookmark.get() == nullptr)
557  return nullptr;
558 
559  return u.bookmark.get()->data;
560 }
561 
562 template <typename Stream> struct iOSFileStreamWrapperFlush { static void flush (Stream*) {} };
563 template <> struct iOSFileStreamWrapperFlush<FileOutputStream> { static void flush (OutputStream* o) { o->flush(); } };
564 
565 template <typename Stream>
566 class iOSFileStreamWrapper : public Stream
567 {
568 public:
569  iOSFileStreamWrapper (URL& urlToUse)
570  : Stream (getLocalFileAccess (urlToUse)),
571  url (urlToUse)
572  {}
573 
574  ~iOSFileStreamWrapper()
575  {
576  iOSFileStreamWrapperFlush<Stream>::flush (this);
577 
578  if (NSData* bookmark = (NSData*) getURLBookmark (url))
579  {
580  BOOL isBookmarkStale = false;
581  NSError* error = nil;
582 
583  auto nsURL = [NSURL URLByResolvingBookmarkData: bookmark
584  options: 0
585  relativeToURL: nil
586  bookmarkDataIsStale: &isBookmarkStale
587  error: &error];
588 
589  if (error == nil)
590  {
591  if (isBookmarkStale)
592  updateStaleBookmark (nsURL, url);
593 
594  [nsURL stopAccessingSecurityScopedResource];
595  }
596  else
597  {
598  auto desc = [error localizedDescription];
599  ignoreUnused (desc);
600  jassertfalse;
601  }
602  }
603  }
604 
605 private:
606  URL url;
607  bool securityAccessSucceeded = false;
608 
609  File getLocalFileAccess (URL& urlToUse)
610  {
611  if (NSData* bookmark = (NSData*) getURLBookmark (urlToUse))
612  {
613  BOOL isBookmarkStale = false;
614  NSError* error = nil;
615 
616  auto nsURL = [NSURL URLByResolvingBookmarkData: bookmark
617  options: 0
618  relativeToURL: nil
619  bookmarkDataIsStale: &isBookmarkStale
620  error: &error];
621 
622  if (error == nil)
623  {
624  securityAccessSucceeded = [nsURL startAccessingSecurityScopedResource];
625 
626  if (isBookmarkStale)
627  updateStaleBookmark (nsURL, urlToUse);
628 
629  return urlToUse.getLocalFile();
630  }
631 
632  auto desc = [error localizedDescription];
633  ignoreUnused (desc);
634  jassertfalse;
635  }
636 
637  return urlToUse.getLocalFile();
638  }
639 
640  void updateStaleBookmark (NSURL* nsURL, URL& juceUrl)
641  {
642  NSError* error = nil;
643 
644  NSData* bookmark = [nsURL bookmarkDataWithOptions: NSURLBookmarkCreationSuitableForBookmarkFile
645  includingResourceValuesForKeys: nil
646  relativeToURL: nil
647  error: &error];
648 
649  if (error == nil)
650  setURLBookmark (juceUrl, (void*) bookmark);
651  else
652  jassertfalse;
653  }
654 };
655 #endif
656 
657 //==============================================================================
658 InputStream* URL::createInputStream (bool usePostCommand,
659  OpenStreamProgressCallback* progressCallback,
660  void* progressCallbackContext,
661  String headers,
662  int timeOutMs,
663  StringPairArray* responseHeaders,
664  int* statusCode,
665  int numRedirectsToFollow,
666  String httpRequestCmd) const
667 {
668  if (isLocalFile())
669  {
670  #if JUCE_IOS
671  // We may need to refresh the embedded bookmark.
672  return new iOSFileStreamWrapper<FileInputStream> (const_cast<URL&>(*this));
673  #else
674  return getLocalFile().createInputStream();
675  #endif
676  }
677 
678  auto wi = std::make_unique<WebInputStream> (*this, usePostCommand);
679 
680  struct ProgressCallbackCaller : public WebInputStream::Listener
681  {
682  ProgressCallbackCaller (OpenStreamProgressCallback* progressCallbackToUse, void* progressCallbackContextToUse)
683  : callback (progressCallbackToUse), data (progressCallbackContextToUse)
684  {}
685 
686  bool postDataSendProgress (WebInputStream&, int bytesSent, int totalBytes) override
687  {
688  return callback (data, bytesSent, totalBytes);
689  }
690 
691  OpenStreamProgressCallback* callback;
692  void* const data;
693  };
694 
695  std::unique_ptr<ProgressCallbackCaller> callbackCaller
696  (progressCallback != nullptr ? new ProgressCallbackCaller (progressCallback, progressCallbackContext) : nullptr);
697 
698  if (headers.isNotEmpty())
699  wi->withExtraHeaders (headers);
700 
701  if (timeOutMs != 0)
702  wi->withConnectionTimeout (timeOutMs);
703 
704  if (httpRequestCmd.isNotEmpty())
705  wi->withCustomRequestCommand (httpRequestCmd);
706 
707  wi->withNumRedirectsToFollow (numRedirectsToFollow);
708 
709  bool success = wi->connect (callbackCaller.get());
710 
711  if (statusCode != nullptr)
712  *statusCode = wi->getStatusCode();
713 
714  if (responseHeaders != nullptr)
715  *responseHeaders = wi->getResponseHeaders();
716 
717  if (! success || wi->isError())
718  return nullptr;
719 
720  return wi.release();
721 }
722 
723 #if JUCE_ANDROID
724 OutputStream* juce_CreateContentURIOutputStream (const URL&);
725 #endif
726 
728 {
729  if (isLocalFile())
730  {
731  #if JUCE_IOS
732  // We may need to refresh the embedded bookmark.
733  return new iOSFileStreamWrapper<FileOutputStream> (const_cast<URL&> (*this));
734  #else
735  return new FileOutputStream (getLocalFile());
736  #endif
737  }
738 
739  #if JUCE_ANDROID
740  return juce_CreateContentURIOutputStream (*this);
741  #else
742  return nullptr;
743  #endif
744 }
745 
746 //==============================================================================
747 bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) const
748 {
749  const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
750  : createInputStream (usePostCommand));
751 
752  if (in != nullptr)
753  {
754  in->readIntoMemoryBlock (destData);
755  return true;
756  }
757 
758  return false;
759 }
760 
761 String URL::readEntireTextStream (bool usePostCommand) const
762 {
763  const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
764  : createInputStream (usePostCommand));
765 
766  if (in != nullptr)
767  return in->readEntireStreamAsString();
768 
769  return {};
770 }
771 
772 std::unique_ptr<XmlElement> URL::readEntireXmlStream (bool usePostCommand) const
773 {
774  return parseXML (readEntireTextStream (usePostCommand));
775 }
776 
777 //==============================================================================
778 URL URL::withParameter (const String& parameterName,
779  const String& parameterValue) const
780 {
781  auto u = *this;
782  u.addParameter (parameterName, parameterValue);
783  return u;
784 }
785 
786 URL URL::withParameters (const StringPairArray& parametersToAdd) const
787 {
788  auto u = *this;
789 
790  for (int i = 0; i < parametersToAdd.size(); ++i)
791  u.addParameter (parametersToAdd.getAllKeys()[i],
792  parametersToAdd.getAllValues()[i]);
793 
794  return u;
795 }
796 
797 URL URL::withPOSTData (const String& newPostData) const
798 {
799  return withPOSTData (MemoryBlock (newPostData.toRawUTF8(), newPostData.getNumBytesAsUTF8()));
800 }
801 
802 URL URL::withPOSTData (const MemoryBlock& newPostData) const
803 {
804  auto u = *this;
805  u.postData = newPostData;
806  return u;
807 }
808 
809 URL::Upload::Upload (const String& param, const String& name,
810  const String& mime, const File& f, MemoryBlock* mb)
811  : parameterName (param), filename (name), mimeType (mime), file (f), data (mb)
812 {
813  jassert (mimeType.isNotEmpty()); // You need to supply a mime type!
814 }
815 
816 URL URL::withUpload (Upload* const f) const
817 {
818  auto u = *this;
819 
820  for (int i = u.filesToUpload.size(); --i >= 0;)
821  if (u.filesToUpload.getObjectPointerUnchecked(i)->parameterName == f->parameterName)
822  u.filesToUpload.remove (i);
823 
824  u.filesToUpload.add (f);
825  return u;
826 }
827 
828 URL URL::withFileToUpload (const String& parameterName, const File& fileToUpload,
829  const String& mimeType) const
830 {
831  return withUpload (new Upload (parameterName, fileToUpload.getFileName(),
832  mimeType, fileToUpload, nullptr));
833 }
834 
835 URL URL::withDataToUpload (const String& parameterName, const String& filename,
836  const MemoryBlock& fileContentToUpload, const String& mimeType) const
837 {
838  return withUpload (new Upload (parameterName, filename, mimeType, File(),
839  new MemoryBlock (fileContentToUpload)));
840 }
841 
842 //==============================================================================
844 {
845  auto result = s.replaceCharacter ('+', ' ');
846 
847  if (! result.containsChar ('%'))
848  return result;
849 
850  // We need to operate on the string as raw UTF8 chars, and then recombine them into unicode
851  // after all the replacements have been made, so that multi-byte chars are handled.
852  Array<char> utf8 (result.toRawUTF8(), (int) result.getNumBytesAsUTF8());
853 
854  for (int i = 0; i < utf8.size(); ++i)
855  {
856  if (utf8.getUnchecked(i) == '%')
857  {
858  auto hexDigit1 = CharacterFunctions::getHexDigitValue ((juce_wchar) (uint8) utf8 [i + 1]);
859  auto hexDigit2 = CharacterFunctions::getHexDigitValue ((juce_wchar) (uint8) utf8 [i + 2]);
860 
861  if (hexDigit1 >= 0 && hexDigit2 >= 0)
862  {
863  utf8.set (i, (char) ((hexDigit1 << 4) + hexDigit2));
864  utf8.removeRange (i + 1, 2);
865  }
866  }
867  }
868 
869  return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size());
870 }
871 
872 String URL::addEscapeChars (const String& s, bool isParameter, bool roundBracketsAreLegal)
873 {
874  String legalChars (isParameter ? "_-.~"
875  : ",$_-.*!'");
876 
877  if (roundBracketsAreLegal)
878  legalChars += "()";
879 
880  Array<char> utf8 (s.toRawUTF8(), (int) s.getNumBytesAsUTF8());
881 
882  for (int i = 0; i < utf8.size(); ++i)
883  {
884  auto c = utf8.getUnchecked(i);
885 
887  || legalChars.containsChar ((juce_wchar) c)))
888  {
889  utf8.set (i, '%');
890  utf8.insert (++i, "0123456789ABCDEF" [((uint8) c) >> 4]);
891  utf8.insert (++i, "0123456789ABCDEF" [c & 15]);
892  }
893  }
894 
895  return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size());
896 }
897 
898 //==============================================================================
900 {
901  auto u = toString (true);
902 
903  if (u.containsChar ('@') && ! u.containsChar (':'))
904  u = "mailto:" + u;
905 
906  return Process::openDocument (u, {});
907 }
908 
909 } // namespace juce
bool isWellFormed() const
Definition: juce_URL.cpp:324
const StringArray & getAllValues() const noexcept
String getFileName() const
Definition: juce_File.cpp:366
static Random & getSystemRandom() noexcept
Definition: juce_Random.cpp:71
URL withParameters(const StringPairArray &parametersToAdd) const
Definition: juce_URL.cpp:786
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
Definition: juce_String.h:302
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
Definition: juce_URL.cpp:658
URL getChildURL(const String &subPath) const
Definition: juce_URL.cpp:440
const StringArray & getParameterValues() const noexcept
Definition: juce_URL.h:240
File getParentDirectory() const
Definition: juce_File.cpp:360
Thread(const String &threadName, size_t threadStackSize=0)
Definition: juce_Thread.cpp:26
const StringArray & getAllKeys() const noexcept
void signalThreadShouldExit()
ObjectClass * add(ObjectClass *newObject)
bool deleteFile() const
int getPort() const
Definition: juce_URL.cpp:406
bool operator==(const URL &) const
Definition: juce_URL.cpp:216
bool containsChar(juce_wchar character) const noexcept
String toString(bool includeGetParameters) const
Definition: juce_URL.cpp:311
bool isEmpty() const noexcept
Definition: juce_URL.cpp:319
String dropLastCharacters(int numberToDrop) const
bool isRoot() const
Definition: juce_File.cpp:127
URL withDataToUpload(const String &parameterName, const String &filename, const MemoryBlock &fileContentToUpload, const String &mimeType) const
Definition: juce_URL.cpp:835
std::unique_ptr< XmlElement > readEntireXmlStream(bool usePostCommand=false) const
Definition: juce_URL.cpp:772
FileInputStream * createInputStream() const
Definition: juce_File.cpp:729
String getScheme() const
Definition: juce_URL.cpp:355
File getLocalFile() const
Definition: juce_URL.cpp:366
static bool JUCE_CALLTYPE openDocument(const String &documentURL, const String &parameters)
bool startsWithIgnoreCase(StringRef text) const noexcept
int size() const noexcept
URL withParameter(const String &parameterName, const String &parameterValue) const
Definition: juce_URL.cpp:778
ElementType getUnchecked(int index) const
Definition: juce_Array.h:252
virtual void flush()=0
String getQueryString() const
Definition: juce_URL.cpp:347
String fromLastOccurrenceOf(StringRef substringToFind, bool includeSubStringInResult, bool ignoreCase) const
String substring(int startIndex, int endIndex) const
URL withNewDomainAndPath(const String &newFullPath) const
Definition: juce_URL.cpp:413
URL withNewSubPath(const String &newPath) const
Definition: juce_URL.cpp:420
static String removeEscapeChars(const String &stringToRemoveEscapeCharsFrom)
Definition: juce_URL.cpp:843
bool isLocalFile() const
Definition: juce_URL.cpp:361
static URL createWithoutParsing(const String &url)
Definition: juce_URL.cpp:211
URL withPOSTData(const String &postData) const
Definition: juce_URL.cpp:797
bool containsIgnoreCase(StringRef text) const noexcept
static int getHexDigitValue(juce_wchar digit) noexcept
static bool isProbablyAnEmailAddress(const String &possibleEmailAddress)
Definition: juce_URL.cpp:518
bool threadShouldExit() const
static String toHexString(IntegerType number)
Definition: juce_String.h:1053
const StringArray & getParameterNames() const noexcept
Definition: juce_URL.h:226
bool launchInDefaultBrowser() const
Definition: juce_URL.cpp:899
static StringArray fromTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
bool readEntireBinaryStream(MemoryBlock &destData, bool usePostCommand=false) const
Definition: juce_URL.cpp:747
String replaceCharacter(juce_wchar characterToReplace, juce_wchar characterToInsertInstead) const
String getDomain() const
Definition: juce_URL.cpp:330
static String addEscapeChars(const String &stringToAddEscapeCharsTo, bool isParameter, bool roundBracketsAreLegal=true)
Definition: juce_URL.cpp:872
URL withFileToUpload(const String &parameterName, const File &fileToUpload, const String &mimeType) const
Definition: juce_URL.cpp:828
FileOutputStream * createOutputStream(size_t bufferSize=0x8000) const
Definition: juce_File.cpp:739
size_t getDataSize() const noexcept
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) const
virtual void progress(URL::DownloadTask *task, int64 bytesDownloaded, int64 totalLength)
Definition: juce_URL.cpp:109
bool startsWithChar(juce_wchar character) const noexcept
URL getParentURL() const
Definition: juce_URL.cpp:433
bool waitForThreadToExit(int timeOutMilliseconds) const
static StringRef getSeparatorString()
String readEntireTextStream(bool usePostCommand=false) const
Definition: juce_URL.cpp:761
String getSubPath(bool includeGetParameters=false) const
Definition: juce_URL.cpp:335
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)
Definition: juce_URL.cpp:503
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
Definition: juce_File.h:149
String getFileName() const
Definition: juce_URL.cpp:371
OutputStream * createOutputStream() const
Definition: juce_URL.cpp:727
bool(void *context, int bytesSent, int totalBytes) OpenStreamProgressCallback
Definition: juce_URL.h:299
bool startsWith(StringRef text) const noexcept
void startThread()