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 #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, 0))
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, 0))
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 != 0);
155     }
156 
isError() const157     bool isError() const        { return request == 0; }
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, 0, 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 = 0, request = 0;
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 = 0;
253 
254         if (requestCopy != 0)
255             InternetCloseHandle (requestCopy);
256 
257         if (connection != 0)
258         {
259             InternetCloseHandle (connection);
260             connection = 0;
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, 0, 0, 0);
267 
268         closeConnection();
269 
270         if (sessionHandle != 0)
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 = { 0 };
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 ? 0
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 != 0)
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, 0, 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 += 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 ? 0
390                                        : HttpOpenRequest (connection, httpRequestCmd.toWideCharPointer(),
391                                                           uc.lpszUrlPath, 0, 0, mimeTypes, flags, 0);
392         }
393 
394         if (request != 0)
395         {
396             INTERNET_BUFFERS buffers = { 0 };
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, 0, 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, NULL, adaptersAddresses, &len) == ERROR_BUFFER_OVERFLOW)
444             adaptersAddresses.malloc (len, 1);
445 
446         return getAdaptersAddresses (AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, 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 != 0)
480         {
481             LANA_ENUM enums = { 0 };
482 
483             {
484                 NCB ncb = { 0 };
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 = { 0 };
494                 ncb2.ncb_command = NCBRESET;
495                 ncb2.ncb_lana_num = enums.lana[i];
496 
497                 if (NetbiosCall (&ncb2) == 0)
498                 {
499                     NCB ncb = { 0 };
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 = { 0 };
612     message.lpszSubject = (LPSTR) emailSubject.toRawUTF8();
613     message.lpszNoteText = (LPSTR) bodyText.toRawUTF8();
614 
615     MapiRecipDesc recip = { 0 };
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 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