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