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 
findAllAddresses(Array<MACAddress> & result)26 void MACAddress::findAllAddresses (Array<MACAddress>& result)
27 {
28     auto s = socket (AF_INET, SOCK_DGRAM, 0);
29 
30     if (s != -1)
31     {
32         struct ifaddrs* addrs = nullptr;
33 
34         if (getifaddrs (&addrs) != -1)
35         {
36             for (auto* i = addrs; i != nullptr; i = i->ifa_next)
37             {
38 #if JUCE_BSD
39                 if (i->ifa_addr->sa_family != AF_LINK) {
40                     continue;
41                 }
42                 struct sockaddr_dl *dl = (struct sockaddr_dl *)i->ifa_addr;
43                 {
44                     MACAddress ma ((const uint8*) LLADDR(dl));
45 #else
46                 struct ifreq ifr;
47                 strcpy (ifr.ifr_name, i->ifa_name);
48                 ifr.ifr_addr.sa_family = AF_INET;
49 
50                 if (ioctl (s, SIOCGIFHWADDR, &ifr) == 0)
51                 {
52                     MACAddress ma ((const uint8*) ifr.ifr_hwaddr.sa_data);
53 #endif
54 
55                     if (! ma.isNull())
56                         result.addIfNotAlreadyThere (ma);
57                 }
58             }
59 
60             freeifaddrs (addrs);
61         }
62 
63         ::close (s);
64     }
65 }
66 
67 
68 bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& /* targetEmailAddress */,
69                                                       const String& /* emailSubject */,
70                                                       const String& /* bodyText */,
71                                                       const StringArray& /* filesToAttach */)
72 {
73     jassertfalse;    // xxx todo
74     return false;
75 }
76 
77 //==============================================================================
78 #if ! JUCE_USE_CURL
79 class WebInputStream::Pimpl
80 {
81 public:
82     Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool shouldUsePost)
83         : owner (pimplOwner), url (urlToCopy),
84           isPost (shouldUsePost), httpRequestCmd (shouldUsePost ? "POST" : "GET")
85     {}
86 
87     ~Pimpl()
88     {
89         closeSocket();
90     }
91 
92     //==============================================================================
93     // WebInputStream methods
94     void withExtraHeaders (const String& extraHeaders)
95     {
96         if (! headers.endsWithChar ('\n') && headers.isNotEmpty())
97             headers << "\r\n";
98 
99         headers << extraHeaders;
100 
101         if (! headers.endsWithChar ('\n') && headers.isNotEmpty())
102             headers << "\r\n";
103     }
104 
105     void withCustomRequestCommand (const String& customRequestCommand)    { httpRequestCmd = customRequestCommand; }
106     void withConnectionTimeout (int timeoutInMs)                          { timeOutMs = timeoutInMs; }
107     void withNumRedirectsToFollow (int maxRedirectsToFollow)              { numRedirectsToFollow = maxRedirectsToFollow; }
108     int getStatusCode() const                                             { return statusCode; }
109     StringPairArray getRequestHeaders() const                             { return WebInputStream::parseHttpHeaders (headers); }
110 
111     StringPairArray getResponseHeaders() const
112     {
113         StringPairArray responseHeaders;
114 
115         if (! isError())
116         {
117             for (int i = 0; i < headerLines.size(); ++i)
118             {
119                 auto& headersEntry = headerLines[i];
120                 auto key   = headersEntry.upToFirstOccurrenceOf (": ", false, false);
121                 auto value = headersEntry.fromFirstOccurrenceOf (": ", false, false);
122                 auto previousValue = responseHeaders[key];
123                 responseHeaders.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value));
124             }
125         }
126 
127         return responseHeaders;
128     }
129 
130     bool connect (WebInputStream::Listener* listener)
131     {
132         {
133             const ScopedLock lock (createSocketLock);
134 
135             if (hasBeenCancelled)
136                 return false;
137         }
138 
139         address = url.toString (! isPost);
140         statusCode = createConnection (listener, numRedirectsToFollow);
141 
142         return statusCode != 0;
143     }
144 
145     void cancel()
146     {
147         const ScopedLock lock (createSocketLock);
148 
149         hasBeenCancelled = true;
150         statusCode = -1;
151         finished = true;
152 
153         closeSocket();
154     }
155 
156     //==============================================================================
157     bool isError() const                 { return socketHandle < 0; }
158     bool isExhausted()                   { return finished; }
159     int64 getPosition()                  { return position; }
160     int64 getTotalLength()               { return contentLength; }
161 
162     int read (void* buffer, int bytesToRead)
163     {
164         if (finished || isError())
165             return 0;
166 
167         if (isChunked && ! readingChunk)
168         {
169             if (position >= chunkEnd)
170             {
171                 const ScopedValueSetter<bool> setter (readingChunk, true, false);
172                 MemoryOutputStream chunkLengthBuffer;
173                 char c = 0;
174 
175                 if (chunkEnd > 0)
176                 {
177                     if (read (&c, 1) != 1 || c != '\r'
178                          || read (&c, 1) != 1 || c != '\n')
179                     {
180                         finished = true;
181                         return 0;
182                     }
183                 }
184 
185                 while (chunkLengthBuffer.getDataSize() < 512 && ! (finished || isError()))
186                 {
187                     if (read (&c, 1) != 1)
188                     {
189                         finished = true;
190                         return 0;
191                     }
192 
193                     if (c == '\r')
194                         continue;
195 
196                     if (c == '\n')
197                         break;
198 
199                     chunkLengthBuffer.writeByte (c);
200                 }
201 
202                 auto chunkSize = chunkLengthBuffer.toString().trimStart().getHexValue64();
203 
204                 if (chunkSize == 0)
205                 {
206                     finished = true;
207                     return 0;
208                 }
209 
210                 chunkEnd += chunkSize;
211             }
212 
213             if (bytesToRead > chunkEnd - position)
214                 bytesToRead = static_cast<int> (chunkEnd - position);
215         }
216 
217         pollfd pfd { socketHandle, POLLIN, 0 };
218 
219         if (poll (&pfd, 1, timeOutMs) <= 0)
220             return 0; // (timeout)
221 
222         auto bytesRead = jmax (0, (int) recv (socketHandle, buffer, (size_t) bytesToRead, MSG_WAITALL));
223 
224         if (bytesRead == 0)
225             finished = true;
226 
227         if (! readingChunk)
228             position += bytesRead;
229 
230         return bytesRead;
231     }
232 
233     bool setPosition (int64 wantedPos)
234     {
235         if (isError())
236             return false;
237 
238         if (wantedPos != position)
239         {
240             finished = false;
241 
242             if (wantedPos < position)
243                 return false;
244 
245             auto numBytesToSkip = wantedPos - position;
246             auto skipBufferSize = (int) jmin (numBytesToSkip, (int64) 16384);
247             HeapBlock<char> temp (skipBufferSize);
248 
249             while (numBytesToSkip > 0 && ! isExhausted())
250                 numBytesToSkip -= read (temp, (int) jmin (numBytesToSkip, (int64) skipBufferSize));
251         }
252 
253         return true;
254     }
255 
256     //==============================================================================
257     int statusCode = 0;
258 
259 private:
260     WebInputStream& owner;
261     URL url;
262     int socketHandle = -1, levelsOfRedirection = 0;
263     StringArray headerLines;
264     String address, headers;
265     MemoryBlock postData;
266     int64 contentLength = -1, position = 0;
267     bool finished = false;
268     const bool isPost;
269     int timeOutMs = 0;
270     int numRedirectsToFollow = 5;
271     String httpRequestCmd;
272     int64 chunkEnd = 0;
273     bool isChunked = false, readingChunk = false;
274     CriticalSection closeSocketLock, createSocketLock;
275     bool hasBeenCancelled = false;
276 
277     void closeSocket (bool resetLevelsOfRedirection = true)
278     {
279         const ScopedLock lock (closeSocketLock);
280 
281         if (socketHandle >= 0)
282         {
283             ::shutdown (socketHandle, SHUT_RDWR);
284             ::close (socketHandle);
285         }
286 
287         socketHandle = -1;
288 
289         if (resetLevelsOfRedirection)
290             levelsOfRedirection = 0;
291     }
292 
293     int createConnection (WebInputStream::Listener* listener, int numRedirects)
294     {
295         closeSocket (false);
296 
297         if (isPost)
298             WebInputStream::createHeadersAndPostData (url, headers, postData);
299 
300         auto timeOutTime = Time::getMillisecondCounter();
301 
302         if (timeOutMs == 0)
303             timeOutMs = 30000;
304 
305         if (timeOutMs < 0)
306             timeOutTime = 0xffffffff;
307         else
308             timeOutTime += (uint32) timeOutMs;
309 
310         String hostName, hostPath;
311         int hostPort;
312 
313         if (! decomposeURL (address, hostName, hostPath, hostPort))
314             return 0;
315 
316         String serverName, proxyName, proxyPath;
317         int proxyPort = 0;
318         int port = 0;
319 
320         auto proxyURL = String::fromUTF8 (getenv ("http_proxy"));
321 
322         if (proxyURL.startsWithIgnoreCase ("http://"))
323         {
324             if (! decomposeURL (proxyURL, proxyName, proxyPath, proxyPort))
325                 return 0;
326 
327             serverName = proxyName;
328             port = proxyPort;
329         }
330         else
331         {
332             serverName = hostName;
333             port = hostPort;
334         }
335 
336         struct addrinfo hints;
337         zerostruct (hints);
338 
339         hints.ai_family = AF_UNSPEC;
340         hints.ai_socktype = SOCK_STREAM;
341         hints.ai_flags = AI_NUMERICSERV;
342 
343         struct addrinfo* result = nullptr;
344 
345         if (getaddrinfo (serverName.toUTF8(), String (port).toUTF8(), &hints, &result) != 0 || result == 0)
346             return 0;
347 
348         {
349             const ScopedLock lock (createSocketLock);
350 
351             socketHandle = hasBeenCancelled ? -1
352                                             : socket (result->ai_family, result->ai_socktype, 0);
353         }
354 
355         if (socketHandle == -1)
356         {
357             freeaddrinfo (result);
358             return 0;
359         }
360 
361         int receiveBufferSize = 16384;
362         setsockopt (socketHandle, SOL_SOCKET, SO_RCVBUF, (char*) &receiveBufferSize, sizeof (receiveBufferSize));
363         setsockopt (socketHandle, SOL_SOCKET, SO_KEEPALIVE, 0, 0);
364 
365       #if JUCE_MAC
366         setsockopt (socketHandle, SOL_SOCKET, SO_NOSIGPIPE, 0, 0);
367       #endif
368 
369         if (::connect (socketHandle, result->ai_addr, result->ai_addrlen) == -1)
370         {
371             closeSocket();
372             freeaddrinfo (result);
373             return 0;
374         }
375 
376         freeaddrinfo (result);
377 
378         {
379             const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, proxyName, proxyPort, hostPath,
380                                                                   address, headers, postData, isPost, httpRequestCmd));
381 
382             if (! sendHeader (socketHandle, requestHeader, timeOutTime, owner, listener))
383             {
384                 closeSocket();
385                 return 0;
386             }
387         }
388 
389         auto responseHeader = readResponse (timeOutTime);
390         position = 0;
391 
392         if (responseHeader.isNotEmpty())
393         {
394             headerLines = StringArray::fromLines (responseHeader);
395 
396             auto status = responseHeader.fromFirstOccurrenceOf (" ", false, false)
397                                         .substring (0, 3).getIntValue();
398 
399             auto location = findHeaderItem (headerLines, "Location:");
400 
401             if (++levelsOfRedirection <= numRedirects
402                  && status >= 300 && status < 400
403                  && location.isNotEmpty() && location != address)
404             {
405                 if (! (location.startsWithIgnoreCase ("http://")
406                         || location.startsWithIgnoreCase ("https://")
407                         || location.startsWithIgnoreCase ("ftp://")))
408                 {
409                     // The following is a bit dodgy. Ideally, we should do a proper transform of the relative URI to a target URI
410                     if (location.startsWithChar ('/'))
411                         location = URL (address).withNewSubPath (location).toString (true);
412                     else
413                         location = address + "/" + location;
414                 }
415 
416                 address = location;
417                 return createConnection (listener, numRedirects);
418             }
419 
420             auto contentLengthString = findHeaderItem (headerLines, "Content-Length:");
421 
422             if (contentLengthString.isNotEmpty())
423                 contentLength = contentLengthString.getLargeIntValue();
424 
425             isChunked = (findHeaderItem (headerLines, "Transfer-Encoding:") == "chunked");
426 
427             return status;
428         }
429 
430         closeSocket();
431         return 0;
432     }
433 
434     //==============================================================================
435     String readResponse (uint32 timeOutTime)
436     {
437         int numConsecutiveLFs  = 0;
438         MemoryOutputStream buffer;
439 
440         while (numConsecutiveLFs < 2
441                 && buffer.getDataSize() < 32768
442                 && Time::getMillisecondCounter() <= timeOutTime
443                 && ! (finished || isError()))
444         {
445             char c = 0;
446 
447             if (read (&c, 1) != 1)
448                 return {};
449 
450             buffer.writeByte (c);
451 
452             if (c == '\n')
453                 ++numConsecutiveLFs;
454             else if (c != '\r')
455                 numConsecutiveLFs = 0;
456         }
457 
458         auto header = buffer.toString().trimEnd();
459 
460         if (header.startsWithIgnoreCase ("HTTP/"))
461             return header;
462 
463         return {};
464     }
465 
466     static void writeValueIfNotPresent (MemoryOutputStream& dest, const String& headers, const String& key, const String& value)
467     {
468         if (! headers.containsIgnoreCase (key))
469             dest << "\r\n" << key << ' ' << value;
470     }
471 
472     static void writeHost (MemoryOutputStream& dest, const String& httpRequestCmd,
473                            const String& path, const String& host, int port)
474     {
475         dest << httpRequestCmd << ' ' << path << " HTTP/1.1\r\nHost: " << host;
476 
477         /* HTTP spec 14.23 says that the port number must be included in the header if it is not 80 */
478         if (port != 80)
479             dest << ':' << port;
480     }
481 
482     static MemoryBlock createRequestHeader (const String& hostName, int hostPort,
483                                             const String& proxyName, int proxyPort,
484                                             const String& hostPath, const String& originalURL,
485                                             const String& userHeaders, const MemoryBlock& postData,
486                                             bool isPost, const String& httpRequestCmd)
487     {
488         MemoryOutputStream header;
489 
490         if (proxyName.isEmpty())
491             writeHost (header, httpRequestCmd, hostPath, hostName, hostPort);
492         else
493             writeHost (header, httpRequestCmd, originalURL, proxyName, proxyPort);
494 
495         writeValueIfNotPresent (header, userHeaders, "User-Agent:", "JUCE/" JUCE_STRINGIFY(JUCE_MAJOR_VERSION)
496                                                                         "." JUCE_STRINGIFY(JUCE_MINOR_VERSION)
497                                                                         "." JUCE_STRINGIFY(JUCE_BUILDNUMBER));
498         writeValueIfNotPresent (header, userHeaders, "Connection:", "close");
499 
500         if (isPost)
501             writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postData.getSize()));
502 
503         if (userHeaders.isNotEmpty())
504             header << "\r\n" << userHeaders;
505 
506         header << "\r\n\r\n";
507 
508         if (isPost)
509             header << postData;
510 
511         return header.getMemoryBlock();
512     }
513 
514     static bool sendHeader (int socketHandle, const MemoryBlock& requestHeader, uint32 timeOutTime,
515                             WebInputStream& pimplOwner, WebInputStream::Listener* listener)
516     {
517         size_t totalHeaderSent = 0;
518 
519         while (totalHeaderSent < requestHeader.getSize())
520         {
521             if (Time::getMillisecondCounter() > timeOutTime)
522                 return false;
523 
524             auto numToSend = jmin (1024, (int) (requestHeader.getSize() - totalHeaderSent));
525 
526             if (send (socketHandle, static_cast<const char*> (requestHeader.getData()) + totalHeaderSent, (size_t) numToSend, 0) != numToSend)
527                 return false;
528 
529             totalHeaderSent += (size_t) numToSend;
530 
531             if (listener != nullptr && ! listener->postDataSendProgress (pimplOwner, (int) totalHeaderSent, (int) requestHeader.getSize()))
532                 return false;
533         }
534 
535         return true;
536     }
537 
538     static bool decomposeURL (const String& url, String& host, String& path, int& port)
539     {
540         if (! url.startsWithIgnoreCase ("http://"))
541             return false;
542 
543         auto nextSlash = url.indexOfChar (7, '/');
544         auto nextColon = url.indexOfChar (7, ':');
545 
546         if (nextColon > nextSlash && nextSlash > 0)
547             nextColon = -1;
548 
549         if (nextColon >= 0)
550         {
551             host = url.substring (7, nextColon);
552 
553             if (nextSlash >= 0)
554                 port = url.substring (nextColon + 1, nextSlash).getIntValue();
555             else
556                 port = url.substring (nextColon + 1).getIntValue();
557         }
558         else
559         {
560             port = 80;
561 
562             if (nextSlash >= 0)
563                 host = url.substring (7, nextSlash);
564             else
565                 host = url.substring (7);
566         }
567 
568         if (nextSlash >= 0)
569             path = url.substring (nextSlash);
570         else
571             path = "/";
572 
573         return true;
574     }
575 
576     static String findHeaderItem (const StringArray& lines, const String& itemName)
577     {
578         for (int i = 0; i < lines.size(); ++i)
579             if (lines[i].startsWithIgnoreCase (itemName))
580                 return lines[i].substring (itemName.length()).trim();
581 
582         return {};
583     }
584 
585     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
586 };
587 
588 URL::DownloadTask* URL::downloadToFile (const File& targetLocation, String extraHeaders, DownloadTask::Listener* listener, bool shouldUsePost)
589 {
590     return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, extraHeaders, listener, shouldUsePost);
591 }
592 #endif
593 
594 } // namespace juce
595