/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ // (This file gets included by juce_linux_NativeCode.cpp, rather than being // compiled on its own). #if JUCE_INCLUDED_FILE //============================================================================== void MACAddress::findAllAddresses (Array& result) { const int s = socket (AF_INET, SOCK_DGRAM, 0); if (s != -1) { char buf [1024]; struct ifconf ifc; ifc.ifc_len = sizeof (buf); ifc.ifc_buf = buf; ioctl (s, SIOCGIFCONF, &ifc); for (unsigned int i = 0; i < ifc.ifc_len / sizeof (struct ifreq); ++i) { struct ifreq ifr; strcpy (ifr.ifr_name, ifc.ifc_req[i].ifr_name); if (ioctl (s, SIOCGIFFLAGS, &ifr) == 0 && (ifr.ifr_flags & IFF_LOOPBACK) == 0 && ioctl (s, SIOCGIFHWADDR, &ifr) == 0) { result.addIfNotAlreadyThere (MACAddress ((const uint8*) ifr.ifr_hwaddr.sa_data)); } } close (s); } } bool PlatformUtilities::launchEmailWithAttachments (const String& targetEmailAddress, const String& emailSubject, const String& bodyText, const StringArray& filesToAttach) { jassertfalse; // xxx todo return false; } //============================================================================== class WebInputStream : public InputStream { public: //============================================================================== WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) : socketHandle (-1), levelsOfRedirection (0), address (address_), headers (headers_), postData (postData_), position (0), finished (false), isPost (isPost_), timeOutMs (timeOutMs_) { createConnection (progressCallback, progressCallbackContext); if (responseHeaders != nullptr && ! isError()) { for (int i = 0; i < headerLines.size(); ++i) { const String& headersEntry = headerLines[i]; const String key (headersEntry.upToFirstOccurrenceOf (": ", false, false)); const String value (headersEntry.fromFirstOccurrenceOf (": ", false, false)); const String previousValue ((*responseHeaders) [key]); responseHeaders->set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); } } } ~WebInputStream() { closeSocket(); } //============================================================================== bool isError() const { return socketHandle < 0; } bool isExhausted() { return finished; } int64 getPosition() { return position; } int64 getTotalLength() { jassertfalse; //xxx to do return -1; } int read (void* buffer, int bytesToRead) { if (finished || isError()) return 0; fd_set readbits; FD_ZERO (&readbits); FD_SET (socketHandle, &readbits); struct timeval tv; tv.tv_sec = jmax (1, timeOutMs / 1000); tv.tv_usec = 0; if (select (socketHandle + 1, &readbits, 0, 0, &tv) <= 0) return 0; // (timeout) const int bytesRead = jmax (0, (int) recv (socketHandle, buffer, bytesToRead, MSG_WAITALL)); if (bytesRead == 0) finished = true; position += bytesRead; return bytesRead; } bool setPosition (int64 wantedPos) { if (isError()) return false; if (wantedPos != position) { finished = false; if (wantedPos < position) { closeSocket(); position = 0; createConnection (0, 0); } skipNextBytes (wantedPos - position); } return true; } //============================================================================== private: int socketHandle, levelsOfRedirection; StringArray headerLines; String address, headers; MemoryBlock postData; int64 position; bool finished; const bool isPost; const int timeOutMs; void closeSocket() { if (socketHandle >= 0) close (socketHandle); socketHandle = -1; levelsOfRedirection = 0; } void createConnection (URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) { closeSocket(); uint32 timeOutTime = Time::getMillisecondCounter(); if (timeOutMs == 0) timeOutTime += 60000; else if (timeOutMs < 0) timeOutTime = 0xffffffff; else timeOutTime += timeOutMs; String hostName, hostPath; int hostPort; if (! decomposeURL (address, hostName, hostPath, hostPort)) return; String serverName, proxyName, proxyPath; int proxyPort = 0; int port = 0; const String proxyURL (getenv ("http_proxy")); if (proxyURL.startsWithIgnoreCase ("http://")) { if (! decomposeURL (proxyURL, proxyName, proxyPath, proxyPort)) return; serverName = proxyName; port = proxyPort; } else { serverName = hostName; port = hostPort; } struct addrinfo hints = { 0 }; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICSERV; struct addrinfo* result = nullptr; if (getaddrinfo (serverName.toUTF8(), String (port).toUTF8(), &hints, &result) != 0 || result == 0) return; socketHandle = socket (result->ai_family, result->ai_socktype, 0); if (socketHandle == -1) { freeaddrinfo (result); return; } int receiveBufferSize = 16384; setsockopt (socketHandle, SOL_SOCKET, SO_RCVBUF, (char*) &receiveBufferSize, sizeof (receiveBufferSize)); setsockopt (socketHandle, SOL_SOCKET, SO_KEEPALIVE, 0, 0); #if JUCE_MAC setsockopt (socketHandle, SOL_SOCKET, SO_NOSIGPIPE, 0, 0); #endif if (connect (socketHandle, result->ai_addr, result->ai_addrlen) == -1) { closeSocket(); freeaddrinfo (result); return; } freeaddrinfo (result); { const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, proxyName, proxyPort, hostPath, address, headers, postData, isPost)); if (! sendHeader (socketHandle, requestHeader, timeOutTime, progressCallback, progressCallbackContext)) { closeSocket(); return; } } const String responseHeader (readResponse (socketHandle, timeOutTime)); if (responseHeader.isNotEmpty()) { headerLines.clear(); headerLines.addLines (responseHeader); const int statusCode = responseHeader.fromFirstOccurrenceOf (" ", false, false) .substring (0, 3).getIntValue(); //int contentLength = findHeaderItem (lines, "Content-Length:").getIntValue(); //bool isChunked = findHeaderItem (lines, "Transfer-Encoding:").equalsIgnoreCase ("chunked"); String location (findHeaderItem (headerLines, "Location:")); if (statusCode >= 300 && statusCode < 400 && location.isNotEmpty()) { if (! location.startsWithIgnoreCase ("http://")) location = "http://" + location; if (++levelsOfRedirection <= 3) { address = location; createConnection (progressCallback, progressCallbackContext); return; } } else { levelsOfRedirection = 0; return; } } closeSocket(); } //============================================================================== static const String readResponse (const int socketHandle, const uint32 timeOutTime) { int bytesRead = 0, numConsecutiveLFs = 0; MemoryBlock buffer (1024, true); while (numConsecutiveLFs < 2 && bytesRead < 32768 && Time::getMillisecondCounter() <= timeOutTime) { fd_set readbits; FD_ZERO (&readbits); FD_SET (socketHandle, &readbits); struct timeval tv; tv.tv_sec = jmax (1, (int) (timeOutTime - Time::getMillisecondCounter()) / 1000); tv.tv_usec = 0; if (select (socketHandle + 1, &readbits, 0, 0, &tv) <= 0) return String::empty; // (timeout) buffer.ensureSize (bytesRead + 8, true); char* const dest = (char*) buffer.getData() + bytesRead; if (recv (socketHandle, dest, 1, 0) == -1) return String::empty; const char lastByte = *dest; ++bytesRead; if (lastByte == '\n') ++numConsecutiveLFs; else if (lastByte != '\r') numConsecutiveLFs = 0; } const String header (CharPointer_UTF8 ((const char*) buffer.getData())); if (header.startsWithIgnoreCase ("HTTP/")) return header.trimEnd(); return String::empty; } static const MemoryBlock createRequestHeader (const String& hostName, const int hostPort, const String& proxyName, const int proxyPort, const String& hostPath, const String& originalURL, const String& headers, const MemoryBlock& postData, const bool isPost) { String header (isPost ? "POST " : "GET "); if (proxyName.isEmpty()) { header << hostPath << " HTTP/1.0\r\nHost: " << hostName << ':' << hostPort; } else { header << originalURL << " HTTP/1.0\r\nHost: " << proxyName << ':' << proxyPort; } header << "\r\nUser-Agent: JUCE/" << JUCE_MAJOR_VERSION << '.' << JUCE_MINOR_VERSION << "\r\nConnection: Close\r\nContent-Length: " << (int) postData.getSize() << "\r\n" << headers << "\r\n"; MemoryBlock mb; mb.append (header.toUTF8(), (int) strlen (header.toUTF8())); mb.append (postData.getData(), postData.getSize()); return mb; } static bool sendHeader (int socketHandle, const MemoryBlock& requestHeader, const uint32 timeOutTime, URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) { size_t totalHeaderSent = 0; while (totalHeaderSent < requestHeader.getSize()) { if (Time::getMillisecondCounter() > timeOutTime) return false; const int numToSend = jmin (1024, (int) (requestHeader.getSize() - totalHeaderSent)); if (send (socketHandle, static_cast (requestHeader.getData()) + totalHeaderSent, numToSend, 0) != numToSend) return false; totalHeaderSent += numToSend; if (progressCallback != nullptr && ! progressCallback (progressCallbackContext, totalHeaderSent, requestHeader.getSize())) return false; } return true; } static bool decomposeURL (const String& url, String& host, String& path, int& port) { if (! url.startsWithIgnoreCase ("http://")) return false; const int nextSlash = url.indexOfChar (7, '/'); int nextColon = url.indexOfChar (7, ':'); if (nextColon > nextSlash && nextSlash > 0) nextColon = -1; if (nextColon >= 0) { host = url.substring (7, nextColon); if (nextSlash >= 0) port = url.substring (nextColon + 1, nextSlash).getIntValue(); else port = url.substring (nextColon + 1).getIntValue(); } else { port = 80; if (nextSlash >= 0) host = url.substring (7, nextSlash); else host = url.substring (7); } if (nextSlash >= 0) path = url.substring (nextSlash); else path = "/"; return true; } static const String findHeaderItem (const StringArray& lines, const String& itemName) { for (int i = 0; i < lines.size(); ++i) if (lines[i].startsWithIgnoreCase (itemName)) return lines[i].substring (itemName.length()).trim(); return String::empty; } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream); }; InputStream* URL::createNativeStream (const String& address, bool isPost, const MemoryBlock& postData, OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, const String& headers, const int timeOutMs, StringPairArray* responseHeaders) { ScopedPointer wi (new WebInputStream (address, isPost, postData, progressCallback, progressCallbackContext, headers, timeOutMs, responseHeaders)); return wi->isError() ? nullptr : wi.release(); } #endif