1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - 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 CURLSymbols
27 {
28     CURL* (*curl_easy_init) (void);
29     CURLcode (*curl_easy_setopt) (CURL *curl, CURLoption option, ...);
30     void (*curl_easy_cleanup) (CURL *curl);
31     CURLcode (*curl_easy_getinfo) (CURL *curl, CURLINFO info, ...);
32     CURLMcode (*curl_multi_add_handle) (CURLM *multi_handle, CURL *curl_handle);
33     CURLMcode (*curl_multi_cleanup) (CURLM *multi_handle);
34     CURLMcode (*curl_multi_fdset) (CURLM *multi_handle, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd);
35     CURLMsg* (*curl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue);
36     CURLM* (*curl_multi_init) (void);
37     CURLMcode (*curl_multi_perform) (CURLM *multi_handle, int *running_handles);
38     CURLMcode (*curl_multi_remove_handle) (CURLM *multi_handle, CURL *curl_handle);
39     CURLMcode (*curl_multi_timeout) (CURLM *multi_handle, long *milliseconds);
40     struct curl_slist* (*curl_slist_append) (struct curl_slist *, const char *);
41     void (*curl_slist_free_all) (struct curl_slist *);
42     curl_version_info_data* (*curl_version_info) (CURLversion);
43 
createjuce::CURLSymbols44     static std::unique_ptr<CURLSymbols> create()
45     {
46         std::unique_ptr<CURLSymbols> symbols (new CURLSymbols);
47 
48        #if JUCE_LOAD_CURL_SYMBOLS_LAZILY
49         const ScopedLock sl (getLibcurlLock());
50         #define JUCE_INIT_CURL_SYMBOL(name)  if (! symbols->loadSymbol (symbols->name, #name)) return nullptr;
51        #else
52         #define JUCE_INIT_CURL_SYMBOL(name)  symbols->name = ::name;
53        #endif
54 
55         JUCE_INIT_CURL_SYMBOL (curl_easy_init)
56         JUCE_INIT_CURL_SYMBOL (curl_easy_setopt)
57         JUCE_INIT_CURL_SYMBOL (curl_easy_cleanup)
58         JUCE_INIT_CURL_SYMBOL (curl_easy_getinfo)
59         JUCE_INIT_CURL_SYMBOL (curl_multi_add_handle)
60         JUCE_INIT_CURL_SYMBOL (curl_multi_cleanup)
61         JUCE_INIT_CURL_SYMBOL (curl_multi_fdset)
62         JUCE_INIT_CURL_SYMBOL (curl_multi_info_read)
63         JUCE_INIT_CURL_SYMBOL (curl_multi_init)
64         JUCE_INIT_CURL_SYMBOL (curl_multi_perform)
65         JUCE_INIT_CURL_SYMBOL (curl_multi_remove_handle)
66         JUCE_INIT_CURL_SYMBOL (curl_multi_timeout)
67         JUCE_INIT_CURL_SYMBOL (curl_slist_append)
68         JUCE_INIT_CURL_SYMBOL (curl_slist_free_all)
69         JUCE_INIT_CURL_SYMBOL (curl_version_info)
70 
71         return symbols;
72     }
73 
74     // liburl's curl_multi_init calls curl_global_init which is not thread safe
75     // so we need to get a lock during calls to curl_multi_init and curl_multi_cleanup
getLibcurlLockjuce::CURLSymbols76     static CriticalSection& getLibcurlLock() noexcept
77     {
78         static CriticalSection cs;
79         return cs;
80     }
81 
82 private:
83     CURLSymbols() = default;
84 
85    #if JUCE_LOAD_CURL_SYMBOLS_LAZILY
getLibcurljuce::CURLSymbols86     static DynamicLibrary& getLibcurl()
87     {
88         const ScopedLock sl (getLibcurlLock());
89         static DynamicLibrary libcurl;
90 
91         if (libcurl.getNativeHandle() == nullptr)
92             for (auto libName : { "libcurl.so", "libcurl.so.4", "libcurl.so.3" })
93                 if (libcurl.open (libName))
94                     break;
95 
96         return libcurl;
97     }
98 
99     template <typename FuncPtr>
loadSymboljuce::CURLSymbols100     bool loadSymbol (FuncPtr& dst, const char* name)
101     {
102         dst = reinterpret_cast<FuncPtr> (getLibcurl().getFunction (name));
103         return (dst != nullptr);
104     }
105    #endif
106 };
107 
108 
109 //==============================================================================
110 class WebInputStream::Pimpl
111 {
112 public:
Pimpl(WebInputStream & ownerStream,const URL & urlToCopy,bool shouldUsePost)113     Pimpl (WebInputStream& ownerStream, const URL& urlToCopy, bool shouldUsePost)
114         : owner (ownerStream), url (urlToCopy), isPost (shouldUsePost),
115           httpRequest (isPost ? "POST" : "GET")
116     {
117         jassert (symbols); // Unable to load libcurl!
118 
119         {
120             const ScopedLock sl (CURLSymbols::getLibcurlLock());
121             multi = symbols->curl_multi_init();
122         }
123 
124         if (multi != nullptr)
125         {
126             curl = symbols->curl_easy_init();
127 
128             if (curl != nullptr)
129                 if (symbols->curl_multi_add_handle (multi, curl) == CURLM_OK)
130                     return;
131         }
132 
133         cleanup();
134     }
135 
~Pimpl()136     ~Pimpl()
137     {
138         cleanup();
139     }
140 
141     //==============================================================================
142     // Input Stream overrides
isError() const143     bool isError() const                 { return curl == nullptr || lastError != CURLE_OK; }
isExhausted()144     bool isExhausted()                   { return (isError() || finished) && curlBuffer.getSize() == 0; }
getPosition()145     int64 getPosition()                  { return streamPos; }
getTotalLength()146     int64 getTotalLength()               { return contentLength; }
147 
read(void * buffer,int bytesToRead)148     int read (void* buffer, int bytesToRead)
149     {
150         return readOrSkip (buffer, bytesToRead, false);
151     }
152 
setPosition(int64 wantedPos)153     bool setPosition (int64 wantedPos)
154     {
155         const int amountToSkip = static_cast<int> (wantedPos - getPosition());
156 
157         if (amountToSkip < 0)
158             return false;
159 
160         if (amountToSkip == 0)
161             return true;
162 
163         const int actuallySkipped = readOrSkip (nullptr, amountToSkip, true);
164 
165         return actuallySkipped == amountToSkip;
166     }
167 
168     //==============================================================================
169     // WebInputStream methods
withExtraHeaders(const String & extraHeaders)170     void withExtraHeaders (const String& extraHeaders)
171     {
172         if (! requestHeaders.endsWithChar ('\n') && requestHeaders.isNotEmpty())
173             requestHeaders << "\r\n";
174 
175         requestHeaders << extraHeaders;
176 
177         if (! requestHeaders.endsWithChar ('\n') && requestHeaders.isNotEmpty())
178             requestHeaders << "\r\n";
179     }
180 
withCustomRequestCommand(const String & customRequestCommand)181     void withCustomRequestCommand (const String& customRequestCommand)    { httpRequest = customRequestCommand; }
withConnectionTimeout(int timeoutInMs)182     void withConnectionTimeout (int timeoutInMs)                          { timeOutMs = timeoutInMs; }
withNumRedirectsToFollow(int maxRedirectsToFollow)183     void withNumRedirectsToFollow (int maxRedirectsToFollow)              { maxRedirects = maxRedirectsToFollow; }
getRequestHeaders() const184     StringPairArray getRequestHeaders() const                             { return WebInputStream::parseHttpHeaders (requestHeaders); }
getResponseHeaders() const185     StringPairArray getResponseHeaders() const                            { return WebInputStream::parseHttpHeaders (responseHeaders); }
getStatusCode() const186     int getStatusCode() const                                             { return statusCode; }
187 
188     //==============================================================================
cleanup()189     void cleanup()
190     {
191         const ScopedLock lock (cleanupLock);
192         const ScopedLock sl (CURLSymbols::getLibcurlLock());
193 
194         if (curl != nullptr)
195         {
196             symbols->curl_multi_remove_handle (multi, curl);
197 
198             if (headerList != nullptr)
199             {
200                 symbols->curl_slist_free_all (headerList);
201                 headerList = nullptr;
202             }
203 
204             symbols->curl_easy_cleanup (curl);
205             curl = nullptr;
206         }
207 
208         if (multi != nullptr)
209         {
210             symbols->curl_multi_cleanup (multi);
211             multi = nullptr;
212         }
213     }
214 
cancel()215     void cancel()
216     {
217         cleanup();
218     }
219 
220     //==============================================================================
setOptions()221     bool setOptions()
222     {
223         auto address = url.toString (! isPost);
224 
225         curl_version_info_data* data = symbols->curl_version_info (CURLVERSION_NOW);
226         jassert (data != nullptr);
227 
228         if (! requestHeaders.endsWithChar ('\n'))
229             requestHeaders << "\r\n";
230 
231         if (isPost)
232             WebInputStream::createHeadersAndPostData (url, requestHeaders, headersAndPostData);
233 
234         if (! requestHeaders.endsWithChar ('\n'))
235             requestHeaders << "\r\n";
236 
237         auto userAgent = String ("curl/") + data->version;
238 
239         if (symbols->curl_easy_setopt (curl, CURLOPT_URL, address.toRawUTF8()) == CURLE_OK
240             && symbols->curl_easy_setopt (curl, CURLOPT_WRITEDATA, this) == CURLE_OK
241             && symbols->curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, StaticCurlWrite) == CURLE_OK
242             && symbols->curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1) == CURLE_OK
243             && symbols->curl_easy_setopt (curl, CURLOPT_MAXREDIRS, static_cast<long> (maxRedirects)) == CURLE_OK
244             && symbols->curl_easy_setopt (curl, CURLOPT_USERAGENT, userAgent.toRawUTF8()) == CURLE_OK
245             && symbols->curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, (maxRedirects > 0 ? 1 : 0)) == CURLE_OK)
246         {
247             if (isPost)
248             {
249                 if (symbols->curl_easy_setopt (curl, CURLOPT_READDATA, this) != CURLE_OK
250                     || symbols->curl_easy_setopt (curl, CURLOPT_READFUNCTION, StaticCurlRead) != CURLE_OK)
251                     return false;
252 
253                 if (symbols->curl_easy_setopt (curl, CURLOPT_POST, 1) != CURLE_OK
254                     || symbols->curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t> (headersAndPostData.getSize())) != CURLE_OK)
255                     return false;
256             }
257 
258             // handle special http request commands
259             bool hasSpecialRequestCmd = isPost ? (httpRequest != "POST") : (httpRequest != "GET");
260 
261             if (hasSpecialRequestCmd)
262                 if (symbols->curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, httpRequest.toRawUTF8()) != CURLE_OK)
263                     return false;
264 
265             if (symbols->curl_easy_setopt (curl, CURLOPT_HEADERDATA, this) != CURLE_OK
266                 || symbols->curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, StaticCurlHeader) != CURLE_OK)
267                 return false;
268 
269             if (timeOutMs > 0)
270             {
271                 auto timeOutSecs = ((long) timeOutMs + 999) / 1000;
272 
273                 if (symbols->curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, timeOutSecs) != CURLE_OK
274                     || symbols->curl_easy_setopt (curl, CURLOPT_LOW_SPEED_LIMIT, 100) != CURLE_OK
275                     || symbols->curl_easy_setopt (curl, CURLOPT_LOW_SPEED_TIME, timeOutSecs) != CURLE_OK)
276                     return false;
277             }
278 
279             return true;
280         }
281 
282         return false;
283     }
284 
connect(WebInputStream::Listener * webInputListener)285     bool connect (WebInputStream::Listener* webInputListener)
286     {
287         {
288             const ScopedLock lock (cleanupLock);
289 
290             if (curl == nullptr)
291                 return false;
292 
293             if (! setOptions())
294             {
295                 cleanup();
296                 return false;
297             }
298 
299             if (requestHeaders.isNotEmpty())
300             {
301                 const StringArray headerLines = StringArray::fromLines (requestHeaders);
302 
303                 // fromLines will always return at least one line if the string is not empty
304                 jassert (headerLines.size() > 0);
305                 headerList = symbols->curl_slist_append (headerList, headerLines [0].toRawUTF8());
306 
307                 for (int i = 1; (i < headerLines.size() && headerList != nullptr); ++i)
308                     headerList = symbols->curl_slist_append (headerList, headerLines [i].toRawUTF8());
309 
310                 if (headerList == nullptr)
311                 {
312                     cleanup();
313                     return false;
314                 }
315 
316                 if (symbols->curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headerList) != CURLE_OK)
317                 {
318                     cleanup();
319                     return false;
320                 }
321             }
322         }
323 
324         listener = webInputListener;
325 
326         if (isPost)
327             postBuffer = &headersAndPostData;
328 
329         size_t lastPos = static_cast<size_t> (-1);
330 
331         // step until either: 1) there is an error 2) the transaction is complete
332         // or 3) data is in the in buffer
333         while ((! finished) && curlBuffer.getSize() == 0)
334         {
335             {
336                 const ScopedLock lock (cleanupLock);
337 
338                 if (curl == nullptr)
339                     return false;
340             }
341 
342             singleStep();
343 
344             // call callbacks if this is a post request
345             if (isPost && listener != nullptr && lastPos != postPosition)
346             {
347                 lastPos = postPosition;
348 
349                 if (! listener->postDataSendProgress (owner, static_cast<int> (lastPos), static_cast<int> (headersAndPostData.getSize())))
350                 {
351                     // user has decided to abort the transaction
352                     cleanup();
353                     return false;
354                 }
355             }
356         }
357 
358         {
359             const ScopedLock lock (cleanupLock);
360 
361             if (curl == nullptr)
362                 return false;
363 
364             long responseCode;
365             if (symbols->curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &responseCode) == CURLE_OK)
366                 statusCode = static_cast<int> (responseCode);
367 
368             // get content length size
369             double curlLength;
370             if (symbols->curl_easy_getinfo (curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &curlLength) == CURLE_OK)
371                 contentLength = static_cast<int64> (curlLength);
372         }
373 
374         return true;
375     }
376 
finish()377     void finish()
378     {
379         const ScopedLock lock (cleanupLock);
380 
381         if (curl == nullptr)
382             return;
383 
384         for (;;)
385         {
386             int cnt = 0;
387 
388             if (CURLMsg* msg = symbols->curl_multi_info_read (multi, &cnt))
389             {
390                 if (msg->msg == CURLMSG_DONE && msg->easy_handle == curl)
391                 {
392                     lastError = msg->data.result; // this is the error that stopped our process from continuing
393                     break;
394                 }
395             }
396             else
397             {
398                 break;
399             }
400         }
401 
402         finished = true;
403     }
404 
405     //==============================================================================
singleStep()406     void singleStep()
407     {
408         if (lastError != CURLE_OK)
409             return;
410 
411         fd_set fdread, fdwrite, fdexcep;
412         int maxfd = -1;
413         long curl_timeo;
414 
415         {
416             const ScopedLock lock (cleanupLock);
417 
418             if (multi == nullptr)
419                 return;
420 
421             if ((lastError = (int) symbols->curl_multi_timeout (multi, &curl_timeo)) != CURLM_OK)
422                 return;
423         }
424 
425         // why 980? see http://curl.haxx.se/libcurl/c/curl_multi_timeout.html
426         if (curl_timeo < 0)
427             curl_timeo = 980;
428 
429         struct timeval tv;
430         tv.tv_sec = curl_timeo / 1000;
431         tv.tv_usec = (curl_timeo % 1000) * 1000;
432 
433         FD_ZERO (&fdread);
434         FD_ZERO (&fdwrite);
435         FD_ZERO (&fdexcep);
436 
437         {
438             const ScopedLock lock (cleanupLock);
439 
440             if (multi == nullptr)
441                 return;
442 
443             if ((lastError = (int) symbols->curl_multi_fdset (multi, &fdread, &fdwrite, &fdexcep, &maxfd)) != CURLM_OK)
444                 return;
445         }
446 
447         if (maxfd != -1)
448         {
449             if (select (maxfd + 1, &fdread, &fdwrite, &fdexcep, &tv) < 0)
450             {
451                 lastError = -1;
452                 return;
453             }
454         }
455         else
456         {
457             // if curl does not return any sockets for to wait on, then the doc says to wait 100 ms
458             Thread::sleep (100);
459         }
460 
461         int still_running = 0;
462         int curlRet;
463 
464         {
465             const ScopedLock lock (cleanupLock);
466 
467             while ((curlRet = (int) symbols->curl_multi_perform (multi, &still_running)) == CURLM_CALL_MULTI_PERFORM)
468             {}
469         }
470 
471         if ((lastError = curlRet) != CURLM_OK)
472             return;
473 
474         if (still_running <= 0)
475             finish();
476     }
477 
readOrSkip(void * buffer,int bytesToRead,bool skip)478     int readOrSkip (void* buffer, int bytesToRead, bool skip)
479     {
480         if (bytesToRead <= 0)
481             return 0;
482 
483         size_t pos = 0;
484         size_t len = static_cast<size_t> (bytesToRead);
485 
486         while (len > 0)
487         {
488             size_t bufferBytes = curlBuffer.getSize();
489             bool removeSection = true;
490 
491             if (bufferBytes == 0)
492             {
493                 // do not call curl again if we are finished
494                 {
495                     const ScopedLock lock (cleanupLock);
496 
497                     if (finished || curl == nullptr)
498                         return static_cast<int> (pos);
499                 }
500 
501                 skipBytes = skip ? len : 0;
502                 singleStep();
503 
504                 // update the amount that was read/skipped from curl
505                 bufferBytes = skip ? len - skipBytes : curlBuffer.getSize();
506                 removeSection = ! skip;
507             }
508 
509             // can we copy data from the internal buffer?
510             if (bufferBytes > 0)
511             {
512                 size_t max = jmin (len, bufferBytes);
513 
514                 if (! skip)
515                     memcpy (addBytesToPointer (buffer, pos), curlBuffer.getData(), max);
516 
517                 pos += max;
518                 streamPos += static_cast<int64> (max);
519                 len -= max;
520 
521                 if (removeSection)
522                     curlBuffer.removeSection (0, max);
523             }
524         }
525 
526         return static_cast<int> (pos);
527     }
528 
529     //==============================================================================
530     // CURL callbacks
curlWriteCallback(char * ptr,size_t size,size_t nmemb)531     size_t curlWriteCallback (char* ptr, size_t size, size_t nmemb)
532     {
533         if (curl == nullptr || lastError != CURLE_OK)
534             return 0;
535 
536         const size_t len = size * nmemb;
537 
538         // skip bytes if necessary
539         size_t max = jmin (skipBytes, len);
540         skipBytes -= max;
541 
542         if (len > max)
543             curlBuffer.append (ptr + max, len - max);
544 
545         return len;
546     }
547 
curlReadCallback(char * ptr,size_t size,size_t nmemb)548     size_t curlReadCallback (char* ptr, size_t size, size_t nmemb)
549     {
550         if (curl == nullptr || postBuffer == nullptr || lastError != CURLE_OK)
551             return 0;
552 
553         const size_t len = size * nmemb;
554 
555         size_t max = jmin (postBuffer->getSize() - postPosition, len);
556         memcpy (ptr, (char*)postBuffer->getData() + postPosition, max);
557         postPosition += max;
558 
559         return max;
560     }
561 
curlHeaderCallback(char * ptr,size_t size,size_t nmemb)562     size_t curlHeaderCallback (char* ptr, size_t size, size_t nmemb)
563     {
564         if (curl == nullptr || lastError != CURLE_OK)
565             return 0;
566 
567         size_t len = size * nmemb;
568 
569         String header (ptr, len);
570 
571         if (! header.contains (":") && header.startsWithIgnoreCase ("HTTP/"))
572             responseHeaders.clear();
573         else
574             responseHeaders += header;
575 
576         return len;
577     }
578 
579 
580     //==============================================================================
581     // Static method wrappers
StaticCurlWrite(char * ptr,size_t size,size_t nmemb,void * userdata)582     static size_t StaticCurlWrite (char* ptr, size_t size, size_t nmemb, void* userdata)
583     {
584         WebInputStream::Pimpl* wi = reinterpret_cast<WebInputStream::Pimpl*> (userdata);
585         return wi->curlWriteCallback (ptr, size, nmemb);
586     }
587 
StaticCurlRead(char * ptr,size_t size,size_t nmemb,void * userdata)588     static size_t StaticCurlRead (char* ptr, size_t size, size_t nmemb, void* userdata)
589     {
590         WebInputStream::Pimpl* wi = reinterpret_cast<WebInputStream::Pimpl*> (userdata);
591         return wi->curlReadCallback (ptr, size, nmemb);
592     }
593 
StaticCurlHeader(char * ptr,size_t size,size_t nmemb,void * userdata)594     static size_t StaticCurlHeader (char* ptr, size_t size, size_t nmemb, void* userdata)
595     {
596         WebInputStream::Pimpl* wi = reinterpret_cast<WebInputStream::Pimpl*> (userdata);
597         return wi->curlHeaderCallback (ptr, size, nmemb);
598     }
599 
600     //==============================================================================
601     WebInputStream& owner;
602     const URL url;
603     std::unique_ptr<CURLSymbols> symbols { CURLSymbols::create() };
604 
605     //==============================================================================
606     // curl stuff
607     CURLM* multi = nullptr;
608     CURL* curl = nullptr;
609     struct curl_slist* headerList = nullptr;
610     int lastError = CURLE_OK;
611 
612     //==============================================================================
613     // Options
614     int timeOutMs = 0;
615     int maxRedirects = 5;
616     const bool isPost;
617     String httpRequest;
618 
619     //==============================================================================
620     // internal buffers and buffer positions
621     int64 contentLength = -1, streamPos = 0;
622     MemoryBlock curlBuffer;
623     MemoryBlock headersAndPostData;
624     String responseHeaders, requestHeaders;
625     int statusCode = -1;
626 
627     //==============================================================================
628     bool finished = false;
629     size_t skipBytes = 0;
630 
631     //==============================================================================
632     // Http POST variables
633     const MemoryBlock* postBuffer = nullptr;
634     size_t postPosition = 0;
635 
636     //==============================================================================
637     WebInputStream::Listener* listener = nullptr;
638 
639     //==============================================================================
640     CriticalSection cleanupLock;
641 
642     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
643 };
644 
downloadToFile(const File & targetLocation,String extraHeaders,DownloadTask::Listener * listener,bool shouldUsePost)645 std::unique_ptr<URL::DownloadTask> URL::downloadToFile (const File& targetLocation, String extraHeaders, DownloadTask::Listener* listener, bool shouldUsePost)
646 {
647     return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, extraHeaders, listener, shouldUsePost);
648 }
649 
650 } // namespace juce
651