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 #ifndef INTERNET_FLAG_NEED_FILE
27 #define INTERNET_FLAG_NEED_FILE 0x00000010
28 #endif
29
30 #ifndef INTERNET_OPTION_DISABLE_AUTODIAL
31 #define INTERNET_OPTION_DISABLE_AUTODIAL 70
32 #endif
33
34 //==============================================================================
35 class WebInputStream::Pimpl
36 {
37 public:
Pimpl(WebInputStream & pimplOwner,const URL & urlToCopy,bool shouldBePost)38 Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool shouldBePost)
39 : statusCode (0), owner (pimplOwner), url (urlToCopy), isPost (shouldBePost),
40 httpRequestCmd (isPost ? "POST" : "GET")
41 {}
42
~Pimpl()43 ~Pimpl()
44 {
45 closeConnection();
46 }
47
48 //==============================================================================
49 // WebInputStream methods
withExtraHeaders(const String & extraHeaders)50 void withExtraHeaders (const String& extraHeaders)
51 {
52 if (! headers.endsWithChar ('\n') && headers.isNotEmpty())
53 headers << "\r\n";
54
55 headers << extraHeaders;
56
57 if (! headers.endsWithChar ('\n') && headers.isNotEmpty())
58 headers << "\r\n";
59 }
60
withCustomRequestCommand(const String & customRequestCommand)61 void withCustomRequestCommand (const String& customRequestCommand) { httpRequestCmd = customRequestCommand; }
withConnectionTimeout(int timeoutInMs)62 void withConnectionTimeout (int timeoutInMs) { timeOutMs = timeoutInMs; }
withNumRedirectsToFollow(int maxRedirectsToFollow)63 void withNumRedirectsToFollow (int maxRedirectsToFollow) { numRedirectsToFollow = maxRedirectsToFollow; }
getRequestHeaders() const64 StringPairArray getRequestHeaders() const { return WebInputStream::parseHttpHeaders (headers); }
getResponseHeaders() const65 StringPairArray getResponseHeaders() const { return responseHeaders; }
getStatusCode() const66 int getStatusCode() const { return statusCode; }
67
68 //==============================================================================
connect(WebInputStream::Listener * listener)69 bool connect (WebInputStream::Listener* listener)
70 {
71 {
72 const ScopedLock lock (createConnectionLock);
73
74 if (hasBeenCancelled)
75 return false;
76 }
77
78 String address = url.toString (! isPost);
79
80 while (numRedirectsToFollow-- >= 0)
81 {
82 createConnection (address, listener);
83
84 if (! isError())
85 {
86 DWORD bufferSizeBytes = 4096;
87 StringPairArray dataHeaders;
88
89 for (;;)
90 {
91 HeapBlock<char> buffer (bufferSizeBytes);
92
93 if (HttpQueryInfo (request, HTTP_QUERY_RAW_HEADERS_CRLF, buffer.getData(), &bufferSizeBytes, nullptr))
94 {
95 StringArray headersArray;
96 headersArray.addLines (String (reinterpret_cast<const WCHAR*> (buffer.getData())));
97
98 for (int i = 0; i < headersArray.size(); ++i)
99 {
100 const String& header = headersArray[i];
101 const String key (header.upToFirstOccurrenceOf (": ", false, false));
102 const String value (header.fromFirstOccurrenceOf (": ", false, false));
103 const String previousValue (dataHeaders[key]);
104 dataHeaders.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value));
105 }
106
107 break;
108 }
109
110 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
111 return false;
112
113 bufferSizeBytes += 4096;
114 }
115
116 DWORD status = 0;
117 DWORD statusSize = sizeof (status);
118
119 if (HttpQueryInfo (request, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &statusSize, nullptr))
120 {
121 statusCode = (int) status;
122
123 if (numRedirectsToFollow >= 0
124 && (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307))
125 {
126 String newLocation (dataHeaders["Location"]);
127
128 // Check whether location is a relative URI - this is an incomplete test for relative path,
129 // but we'll use it for now (valid protocols for this implementation are http, https & ftp)
130 if (! (newLocation.startsWithIgnoreCase ("http://")
131 || newLocation.startsWithIgnoreCase ("https://")
132 || newLocation.startsWithIgnoreCase ("ftp://")))
133 {
134 if (newLocation.startsWithChar ('/'))
135 newLocation = URL (address).withNewSubPath (newLocation).toString (true);
136 else
137 newLocation = address + "/" + newLocation;
138 }
139
140 if (newLocation.isNotEmpty() && newLocation != address)
141 {
142 address = newLocation;
143 continue;
144 }
145 }
146 }
147
148 responseHeaders.addArray (dataHeaders);
149 }
150
151 break;
152 }
153
154 return (request != nullptr);
155 }
156
isError() const157 bool isError() const { return request == nullptr; }
isExhausted()158 bool isExhausted() { return finished; }
getPosition()159 int64 getPosition() { return position; }
160
getTotalLength()161 int64 getTotalLength()
162 {
163 if (! isError())
164 {
165 DWORD index = 0, result = 0, size = sizeof (result);
166
167 if (HttpQueryInfo (request, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &result, &size, &index))
168 return (int64) result;
169 }
170
171 return -1;
172 }
173
read(void * buffer,int bytesToRead)174 int read (void* buffer, int bytesToRead)
175 {
176 jassert (buffer != nullptr && bytesToRead >= 0);
177 DWORD bytesRead = 0;
178
179 if (! (finished || isError()))
180 {
181 InternetReadFile (request, buffer, (DWORD) bytesToRead, &bytesRead);
182 position += bytesRead;
183
184 if (bytesRead == 0)
185 finished = true;
186 }
187
188 return (int) bytesRead;
189 }
190
cancel()191 void cancel()
192 {
193 {
194 const ScopedLock lock (createConnectionLock);
195
196 hasBeenCancelled = true;
197
198 closeConnection();
199 }
200 }
201
setPosition(int64 wantedPos)202 bool setPosition (int64 wantedPos)
203 {
204 if (isError())
205 return false;
206
207 if (wantedPos != position)
208 {
209 finished = false;
210 position = (int64) InternetSetFilePointer (request, (LONG) wantedPos, nullptr, FILE_BEGIN, 0);
211
212 if (position == wantedPos)
213 return true;
214
215 if (wantedPos < position)
216 return false;
217
218 int64 numBytesToSkip = wantedPos - position;
219 auto skipBufferSize = (int) jmin (numBytesToSkip, (int64) 16384);
220 HeapBlock<char> temp (skipBufferSize);
221
222 while (numBytesToSkip > 0 && ! isExhausted())
223 numBytesToSkip -= read (temp, (int) jmin (numBytesToSkip, (int64) skipBufferSize));
224 }
225
226 return true;
227 }
228
229 int statusCode;
230
231 private:
232 //==============================================================================
233 WebInputStream& owner;
234 const URL url;
235 HINTERNET connection = nullptr, request = nullptr;
236 String headers;
237 MemoryBlock postData;
238 int64 position = 0;
239 bool finished = false;
240 const bool isPost;
241 int timeOutMs = 0;
242 String httpRequestCmd;
243 int numRedirectsToFollow = 5;
244 StringPairArray responseHeaders;
245 CriticalSection createConnectionLock;
246 bool hasBeenCancelled = false;
247
closeConnection()248 void closeConnection()
249 {
250 HINTERNET requestCopy = request;
251
252 request = nullptr;
253
254 if (requestCopy != nullptr)
255 InternetCloseHandle (requestCopy);
256
257 if (connection != nullptr)
258 {
259 InternetCloseHandle (connection);
260 connection = nullptr;
261 }
262 }
263
createConnection(const String & address,WebInputStream::Listener * listener)264 void createConnection (const String& address, WebInputStream::Listener* listener)
265 {
266 static HINTERNET sessionHandle = InternetOpen (_T("juce"), INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0);
267
268 closeConnection();
269
270 if (sessionHandle != nullptr)
271 {
272 // break up the url..
273 const int fileNumChars = 65536;
274 const int serverNumChars = 2048;
275 const int usernameNumChars = 1024;
276 const int passwordNumChars = 1024;
277 HeapBlock<TCHAR> file (fileNumChars), server (serverNumChars),
278 username (usernameNumChars), password (passwordNumChars);
279
280 URL_COMPONENTS uc = {};
281 uc.dwStructSize = sizeof (uc);
282 uc.lpszUrlPath = file;
283 uc.dwUrlPathLength = fileNumChars;
284 uc.lpszHostName = server;
285 uc.dwHostNameLength = serverNumChars;
286 uc.lpszUserName = username;
287 uc.dwUserNameLength = usernameNumChars;
288 uc.lpszPassword = password;
289 uc.dwPasswordLength = passwordNumChars;
290
291 if (isPost)
292 WebInputStream::createHeadersAndPostData (url, headers, postData);
293
294 if (InternetCrackUrl (address.toWideCharPointer(), 0, 0, &uc))
295 openConnection (uc, sessionHandle, address, listener);
296 }
297 }
298
openConnection(URL_COMPONENTS & uc,HINTERNET sessionHandle,const String & address,WebInputStream::Listener * listener)299 void openConnection (URL_COMPONENTS& uc, HINTERNET sessionHandle,
300 const String& address,
301 WebInputStream::Listener* listener)
302 {
303 int disable = 1;
304 InternetSetOption (sessionHandle, INTERNET_OPTION_DISABLE_AUTODIAL, &disable, sizeof (disable));
305
306 if (timeOutMs == 0)
307 timeOutMs = 30000;
308 else if (timeOutMs < 0)
309 timeOutMs = -1;
310
311 applyTimeout (sessionHandle, INTERNET_OPTION_CONNECT_TIMEOUT);
312 applyTimeout (sessionHandle, INTERNET_OPTION_RECEIVE_TIMEOUT);
313 applyTimeout (sessionHandle, INTERNET_OPTION_SEND_TIMEOUT);
314 applyTimeout (sessionHandle, INTERNET_OPTION_DATA_RECEIVE_TIMEOUT);
315 applyTimeout (sessionHandle, INTERNET_OPTION_DATA_SEND_TIMEOUT);
316
317 const bool isFtp = address.startsWithIgnoreCase ("ftp:");
318
319 {
320 const ScopedLock lock (createConnectionLock);
321
322 connection = hasBeenCancelled ? nullptr
323 : InternetConnect (sessionHandle,
324 uc.lpszHostName, uc.nPort,
325 uc.lpszUserName, uc.lpszPassword,
326 isFtp ? (DWORD) INTERNET_SERVICE_FTP
327 : (DWORD) INTERNET_SERVICE_HTTP,
328 0, 0);
329 }
330
331 if (connection != nullptr)
332 {
333 if (isFtp)
334 request = FtpOpenFile (connection, uc.lpszUrlPath, GENERIC_READ,
335 FTP_TRANSFER_TYPE_BINARY | INTERNET_FLAG_NEED_FILE, 0);
336 else
337 openHTTPConnection (uc, address, listener);
338 }
339 }
340
applyTimeout(HINTERNET sessionHandle,const DWORD option)341 void applyTimeout (HINTERNET sessionHandle, const DWORD option)
342 {
343 InternetSetOption (sessionHandle, option, &timeOutMs, sizeof (timeOutMs));
344 }
345
sendHTTPRequest(INTERNET_BUFFERS & buffers,WebInputStream::Listener * listener)346 void sendHTTPRequest (INTERNET_BUFFERS& buffers, WebInputStream::Listener* listener)
347 {
348 if (! HttpSendRequestEx (request, &buffers, nullptr, HSR_INITIATE, 0))
349 return;
350
351 int totalBytesSent = 0;
352
353 while (totalBytesSent < (int) postData.getSize())
354 {
355 auto bytesToSend = jmin (1024, (int) postData.getSize() - totalBytesSent);
356 DWORD bytesSent = 0;
357
358 if (bytesToSend == 0
359 || ! InternetWriteFile (request, static_cast<const char*> (postData.getData()) + totalBytesSent,
360 (DWORD) bytesToSend, &bytesSent))
361 {
362 return;
363 }
364
365 totalBytesSent += (int) bytesSent;
366
367 if (listener != nullptr
368 && ! listener->postDataSendProgress (owner, totalBytesSent, (int) postData.getSize()))
369 {
370 return;
371 }
372 }
373 }
374
openHTTPConnection(URL_COMPONENTS & uc,const String & address,WebInputStream::Listener * listener)375 void openHTTPConnection (URL_COMPONENTS& uc, const String& address, WebInputStream::Listener* listener)
376 {
377 const TCHAR* mimeTypes[] = { _T("*/*"), nullptr };
378
379 DWORD flags = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES
380 | INTERNET_FLAG_NO_AUTO_REDIRECT;
381
382 if (address.startsWithIgnoreCase ("https:"))
383 flags |= INTERNET_FLAG_SECURE; // (this flag only seems necessary if the OS is running IE6 -
384 // IE7 seems to automatically work out when it's https)
385
386 {
387 const ScopedLock lock (createConnectionLock);
388
389 request = hasBeenCancelled ? nullptr
390 : HttpOpenRequest (connection, httpRequestCmd.toWideCharPointer(),
391 uc.lpszUrlPath, nullptr, nullptr, mimeTypes, flags, 0);
392 }
393
394 if (request != nullptr)
395 {
396 INTERNET_BUFFERS buffers = {};
397 buffers.dwStructSize = sizeof (INTERNET_BUFFERS);
398 buffers.lpcszHeader = headers.toWideCharPointer();
399 buffers.dwHeadersLength = (DWORD) headers.length();
400 buffers.dwBufferTotal = (DWORD) postData.getSize();
401
402 auto sendRequestAndTryEnd = [this, &buffers, &listener]() -> bool
403 {
404 sendHTTPRequest (buffers, listener);
405
406 if (HttpEndRequest (request, nullptr, 0, 0))
407 return true;
408
409 return false;
410 };
411
412 auto closed = sendRequestAndTryEnd();
413
414 // N.B. this is needed for some authenticated HTTP connections
415 if (! closed && GetLastError() == ERROR_INTERNET_FORCE_RETRY)
416 closed = sendRequestAndTryEnd();
417
418 if (closed)
419 return;
420 }
421
422 closeConnection();
423 }
424
425 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
426 };
427
428
429 //==============================================================================
430 struct GetAdaptersAddressesHelper
431 {
callGetAdaptersAddressesjuce::GetAdaptersAddressesHelper432 bool callGetAdaptersAddresses()
433 {
434 DynamicLibrary dll ("iphlpapi.dll");
435 JUCE_LOAD_WINAPI_FUNCTION (dll, GetAdaptersAddresses, getAdaptersAddresses, DWORD, (ULONG, ULONG, PVOID, PIP_ADAPTER_ADDRESSES, PULONG))
436
437 if (getAdaptersAddresses == nullptr)
438 return false;
439
440 adaptersAddresses.malloc (1);
441 ULONG len = sizeof (IP_ADAPTER_ADDRESSES);
442
443 if (getAdaptersAddresses (AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, adaptersAddresses, &len) == ERROR_BUFFER_OVERFLOW)
444 adaptersAddresses.malloc (len, 1);
445
446 return getAdaptersAddresses (AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, adaptersAddresses, &len) == NO_ERROR;
447 }
448
449 HeapBlock<IP_ADAPTER_ADDRESSES> adaptersAddresses;
450 };
451
452 namespace MACAddressHelpers
453 {
addAddress(Array<MACAddress> & result,const MACAddress & ma)454 static void addAddress (Array<MACAddress>& result, const MACAddress& ma)
455 {
456 if (! ma.isNull())
457 result.addIfNotAlreadyThere (ma);
458 }
459
getViaGetAdaptersAddresses(Array<MACAddress> & result)460 static void getViaGetAdaptersAddresses (Array<MACAddress>& result)
461 {
462 GetAdaptersAddressesHelper addressesHelper;
463
464 if (addressesHelper.callGetAdaptersAddresses())
465 {
466 for (PIP_ADAPTER_ADDRESSES adapter = addressesHelper.adaptersAddresses; adapter != nullptr; adapter = adapter->Next)
467 {
468 if (adapter->PhysicalAddressLength >= 6)
469 addAddress (result, MACAddress (adapter->PhysicalAddress));
470 }
471 }
472 }
473
getViaNetBios(Array<MACAddress> & result)474 static void getViaNetBios (Array<MACAddress>& result)
475 {
476 DynamicLibrary dll ("netapi32.dll");
477 JUCE_LOAD_WINAPI_FUNCTION (dll, Netbios, NetbiosCall, UCHAR, (PNCB))
478
479 if (NetbiosCall != nullptr)
480 {
481 LANA_ENUM enums = {};
482
483 {
484 NCB ncb = {};
485 ncb.ncb_command = NCBENUM;
486 ncb.ncb_buffer = (unsigned char*) &enums;
487 ncb.ncb_length = sizeof (LANA_ENUM);
488 NetbiosCall (&ncb);
489 }
490
491 for (int i = 0; i < enums.length; ++i)
492 {
493 NCB ncb2 = {};
494 ncb2.ncb_command = NCBRESET;
495 ncb2.ncb_lana_num = enums.lana[i];
496
497 if (NetbiosCall (&ncb2) == 0)
498 {
499 NCB ncb = {};
500 memcpy (ncb.ncb_callname, "* ", NCBNAMSZ);
501 ncb.ncb_command = NCBASTAT;
502 ncb.ncb_lana_num = enums.lana[i];
503
504 struct ASTAT
505 {
506 ADAPTER_STATUS adapt;
507 NAME_BUFFER NameBuff [30];
508 };
509
510 ASTAT astat;
511 zerostruct (astat);
512 ncb.ncb_buffer = (unsigned char*) &astat;
513 ncb.ncb_length = sizeof (ASTAT);
514
515 if (NetbiosCall (&ncb) == 0 && astat.adapt.adapter_type == 0xfe)
516 addAddress (result, MACAddress (astat.adapt.adapter_address));
517 }
518 }
519 }
520 }
521
split(const sockaddr_in6 * sa_in6,int off,uint8 * split)522 static void split (const sockaddr_in6* sa_in6, int off, uint8* split)
523 {
524 #if JUCE_MINGW
525 split[0] = sa_in6->sin6_addr._S6_un._S6_u8[off + 1];
526 split[1] = sa_in6->sin6_addr._S6_un._S6_u8[off];
527 #else
528 split[0] = sa_in6->sin6_addr.u.Byte[off + 1];
529 split[1] = sa_in6->sin6_addr.u.Byte[off];
530 #endif
531 }
532
createAddress(const sockaddr_in6 * sa_in6)533 static IPAddress createAddress (const sockaddr_in6* sa_in6)
534 {
535 IPAddressByteUnion temp;
536 uint16 arr[8];
537
538 for (int i = 0; i < 8; ++i)
539 {
540 split (sa_in6, i * 2, temp.split);
541 arr[i] = temp.combined;
542 }
543
544 return IPAddress (arr);
545 }
546
createAddress(const sockaddr_in * sa_in)547 static IPAddress createAddress (const sockaddr_in* sa_in)
548 {
549 return IPAddress ((uint8*) &sa_in->sin_addr.s_addr, false);
550 }
551
552 template <typename Type>
findAddresses(Array<IPAddress> & result,bool includeIPv6,Type start)553 static void findAddresses (Array<IPAddress>& result, bool includeIPv6, Type start)
554 {
555 for (auto addr = start; addr != nullptr; addr = addr->Next)
556 {
557 if (addr->Address.lpSockaddr->sa_family == AF_INET)
558 result.addIfNotAlreadyThere (createAddress ((sockaddr_in*) addr->Address.lpSockaddr));
559 else if (addr->Address.lpSockaddr->sa_family == AF_INET6 && includeIPv6)
560 result.addIfNotAlreadyThere (createAddress ((sockaddr_in6*) addr->Address.lpSockaddr));
561 }
562 }
563 }
564
findAllAddresses(Array<MACAddress> & result)565 void MACAddress::findAllAddresses (Array<MACAddress>& result)
566 {
567 MACAddressHelpers::getViaGetAdaptersAddresses (result);
568 MACAddressHelpers::getViaNetBios (result);
569 }
570
findAllAddresses(Array<IPAddress> & result,bool includeIPv6)571 void IPAddress::findAllAddresses (Array<IPAddress>& result, bool includeIPv6)
572 {
573 result.addIfNotAlreadyThere (IPAddress::local());
574
575 if (includeIPv6)
576 result.addIfNotAlreadyThere (IPAddress::local (true));
577
578 GetAdaptersAddressesHelper addressesHelper;
579
580 if (addressesHelper.callGetAdaptersAddresses())
581 {
582 for (PIP_ADAPTER_ADDRESSES adapter = addressesHelper.adaptersAddresses; adapter != nullptr; adapter = adapter->Next)
583 {
584 MACAddressHelpers::findAddresses (result, includeIPv6, adapter->FirstUnicastAddress);
585 MACAddressHelpers::findAddresses (result, includeIPv6, adapter->FirstAnycastAddress);
586 MACAddressHelpers::findAddresses (result, includeIPv6, adapter->FirstMulticastAddress);
587 }
588 }
589 }
590
getInterfaceBroadcastAddress(const IPAddress &)591 IPAddress IPAddress::getInterfaceBroadcastAddress (const IPAddress&)
592 {
593 // TODO
594 return {};
595 }
596
597
598 //==============================================================================
openEmailWithAttachments(const String & targetEmailAddress,const String & emailSubject,const String & bodyText,const StringArray & filesToAttach)599 bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& targetEmailAddress,
600 const String& emailSubject,
601 const String& bodyText,
602 const StringArray& filesToAttach)
603 {
604 DynamicLibrary dll ("MAPI32.dll");
605 JUCE_LOAD_WINAPI_FUNCTION (dll, MAPISendMail, mapiSendMail,
606 ULONG, (LHANDLE, ULONG, lpMapiMessage, ::FLAGS, ULONG))
607
608 if (mapiSendMail == nullptr)
609 return false;
610
611 MapiMessage message = {};
612 message.lpszSubject = (LPSTR) emailSubject.toRawUTF8();
613 message.lpszNoteText = (LPSTR) bodyText.toRawUTF8();
614
615 MapiRecipDesc recip = {};
616 recip.ulRecipClass = MAPI_TO;
617 String targetEmailAddress_ (targetEmailAddress);
618 if (targetEmailAddress_.isEmpty())
619 targetEmailAddress_ = " "; // (Windows Mail can't deal with a blank address)
620 recip.lpszName = (LPSTR) targetEmailAddress_.toRawUTF8();
621 message.nRecipCount = 1;
622 message.lpRecips = &recip;
623
624 HeapBlock<MapiFileDesc> files;
625 files.calloc (filesToAttach.size());
626
627 message.nFileCount = (ULONG) filesToAttach.size();
628 message.lpFiles = files;
629
630 for (int i = 0; i < filesToAttach.size(); ++i)
631 {
632 files[i].nPosition = (ULONG) -1;
633 files[i].lpszPathName = (LPSTR) filesToAttach[i].toRawUTF8();
634 }
635
636 return mapiSendMail (0, 0, &message, MAPI_DIALOG | MAPI_LOGON_UI, 0) == SUCCESS_SUCCESS;
637 }
638
downloadToFile(const File & targetLocation,String extraHeaders,DownloadTask::Listener * listener,bool shouldUsePost)639 std::unique_ptr<URL::DownloadTask> URL::downloadToFile (const File& targetLocation, String extraHeaders, DownloadTask::Listener* listener, bool shouldUsePost)
640 {
641 return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, extraHeaders, listener, shouldUsePost);
642 }
643
644 } // namespace juce
645