fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
304
modules/juce_core/network/juce_IPAddress.cpp
Normal file
304
modules/juce_core/network/juce_IPAddress.cpp
Normal file
@ -0,0 +1,304 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
IPAddress::IPAddress (bool IPv6) noexcept : isIPv6 (IPv6)
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
address[i] = 0;
|
||||
}
|
||||
|
||||
IPAddress::IPAddress (const uint8 bytes[], bool IPv6) noexcept : isIPv6 (IPv6)
|
||||
{
|
||||
for (int i = 0; i < (isIPv6 ? 16 : 4); ++i)
|
||||
address[i] = bytes[i];
|
||||
|
||||
if (! isIPv6)
|
||||
zeroUnusedBytes();
|
||||
}
|
||||
|
||||
IPAddress::IPAddress (const uint16 bytes[8]) noexcept : isIPv6 (true)
|
||||
{
|
||||
ByteUnion temp;
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
temp.combined = bytes[i];
|
||||
|
||||
address[i * 2] = temp.split[0];
|
||||
address[i * 2 + 1] = temp.split[1];
|
||||
}
|
||||
}
|
||||
|
||||
IPAddress::IPAddress (uint8 a0, uint8 a1, uint8 a2, uint8 a3) noexcept : isIPv6 (false)
|
||||
{
|
||||
address[0] = a0; address[1] = a1;
|
||||
address[2] = a2; address[3] = a3;
|
||||
|
||||
zeroUnusedBytes();
|
||||
}
|
||||
|
||||
IPAddress::IPAddress (uint16 a1, uint16 a2, uint16 a3, uint16 a4,
|
||||
uint16 a5, uint16 a6, uint16 a7, uint16 a8) noexcept : isIPv6 (true)
|
||||
|
||||
{
|
||||
uint16 array[8] = { a1, a2, a3, a4, a5, a6, a7, a8 };
|
||||
|
||||
ByteUnion temp;
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
temp.combined = array[i];
|
||||
address[i * 2] = temp.split[0];
|
||||
address[i * 2 + 1] = temp.split[1];
|
||||
}
|
||||
}
|
||||
|
||||
IPAddress::IPAddress (uint32 n) noexcept : isIPv6 (false)
|
||||
{
|
||||
address[0] = (n >> 24);
|
||||
address[1] = (n >> 16) & 255;
|
||||
address[2] = (n >> 8) & 255;
|
||||
address[3] = (n & 255);
|
||||
|
||||
zeroUnusedBytes();
|
||||
}
|
||||
|
||||
IPAddress::IPAddress (const String& adr)
|
||||
{
|
||||
isIPv6 = adr.contains (":");
|
||||
|
||||
if (! isIPv6)
|
||||
{
|
||||
StringArray tokens;
|
||||
tokens.addTokens (adr, ".", String());
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
address[i] = (uint8) tokens[i].getIntValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
StringArray tokens;
|
||||
tokens.addTokens (adr.removeCharacters ("[]"), ":", String());
|
||||
|
||||
if (tokens.contains (StringRef())) // if :: shorthand has been used
|
||||
{
|
||||
int idx = tokens.indexOf (StringRef());
|
||||
tokens.set (idx, "0");
|
||||
|
||||
while (tokens.size() < 8)
|
||||
tokens.insert (idx, "0");
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
ByteUnion temp;
|
||||
temp.combined = (uint16) CharacterFunctions::HexParser<int>::parse (tokens[i].getCharPointer());
|
||||
|
||||
address[i * 2] = temp.split[0];
|
||||
address[i * 2 + 1] = temp.split[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String IPAddress::toString() const
|
||||
{
|
||||
if (! isIPv6)
|
||||
{
|
||||
String s ((int) address[0]);
|
||||
|
||||
for (int i = 1; i < 4; ++i)
|
||||
s << '.' << (int) address[i];
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
String addressString;
|
||||
ByteUnion temp;
|
||||
|
||||
temp.split[0] = address[0];
|
||||
temp.split[1] = address[1];
|
||||
|
||||
addressString = String (String::toHexString (temp.combined));
|
||||
|
||||
for (int i = 1; i < 8; ++i)
|
||||
{
|
||||
temp.split[0] = address[i * 2];
|
||||
temp.split[1] = address[i * 2 + 1];
|
||||
|
||||
addressString << ':' << String (String::toHexString (temp.combined));
|
||||
}
|
||||
|
||||
return getFormattedAddress (addressString);
|
||||
}
|
||||
|
||||
IPAddress IPAddress::any (bool IPv6) noexcept { return IPAddress (IPv6); }
|
||||
IPAddress IPAddress::broadcast() noexcept { return IPAddress (255, 255, 255, 255); }
|
||||
IPAddress IPAddress::local (bool IPv6) noexcept { return IPv6 ? IPAddress (0, 0, 0, 0, 0, 0, 0, 1)
|
||||
: IPAddress (127, 0, 0, 1); }
|
||||
|
||||
String IPAddress::getFormattedAddress (const String& unformattedAddress)
|
||||
{
|
||||
jassert (unformattedAddress.contains (":") && ! unformattedAddress.contains ("::")); // needs to be an unformatted IPv6 address!
|
||||
|
||||
auto portString = unformattedAddress.fromFirstOccurrenceOf ("]", false, true);
|
||||
auto addressString = unformattedAddress.dropLastCharacters (portString.length()).removeCharacters ("[]");
|
||||
|
||||
auto tokens = StringArray::fromTokens (addressString, ":", {});
|
||||
|
||||
int numZeros = 0;
|
||||
int numZerosTemp = 0;
|
||||
bool isFirst = false;
|
||||
bool isLast = false;
|
||||
|
||||
for (int i = 0; i < tokens.size(); ++i)
|
||||
{
|
||||
const auto& t = tokens.getReference (i);
|
||||
|
||||
if (t.getHexValue32() == 0x0000)
|
||||
{
|
||||
++numZeros;
|
||||
|
||||
if (i == 0)
|
||||
isFirst = true;
|
||||
else if (i == tokens.size() - 1 && numZeros > numZerosTemp)
|
||||
isLast = true;
|
||||
|
||||
if (t.length() > 1)
|
||||
addressString = addressString.replace (String::repeatedString ("0", t.length()), "0");
|
||||
|
||||
if (isFirst && numZerosTemp != 0 && numZeros > numZerosTemp)
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
addressString = addressString.replace (t, t.trimCharactersAtStart ("0").toLowerCase());
|
||||
|
||||
if (numZeros > 0)
|
||||
{
|
||||
if (numZeros > numZerosTemp)
|
||||
numZerosTemp = numZeros;
|
||||
|
||||
numZeros = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numZerosTemp > numZeros)
|
||||
numZeros = numZerosTemp;
|
||||
|
||||
if (numZeros > 1)
|
||||
{
|
||||
if (numZeros == tokens.size())
|
||||
{
|
||||
addressString = "::,";
|
||||
}
|
||||
else
|
||||
{
|
||||
auto zeroString = isFirst ? "0" + String::repeatedString (":0", numZeros - 1)
|
||||
: String::repeatedString (":0", numZeros);
|
||||
|
||||
addressString = addressString.replaceFirstOccurrenceOf (zeroString, ":");
|
||||
|
||||
if (isLast)
|
||||
addressString << ':';
|
||||
}
|
||||
}
|
||||
|
||||
if (portString.isNotEmpty())
|
||||
addressString = "[" + addressString + "]" + portString;
|
||||
|
||||
return addressString;
|
||||
}
|
||||
|
||||
bool IPAddress::operator== (const IPAddress& other) const noexcept
|
||||
{
|
||||
for (int i = 0; i < (isIPv6 ? 16 : 4); ++i)
|
||||
if (address[i] != other.address[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool IPAddress::operator!= (const IPAddress& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
#if (! JUCE_WINDOWS) && (! JUCE_ANDROID)
|
||||
static void addAddress (const sockaddr_in* addr_in, Array<IPAddress>& result)
|
||||
{
|
||||
auto addr = addr_in->sin_addr.s_addr;
|
||||
|
||||
if (addr != INADDR_NONE)
|
||||
result.addIfNotAlreadyThere (IPAddress (ntohl (addr)));
|
||||
}
|
||||
|
||||
static void addAddress (const sockaddr_in6* addr_in, Array<IPAddress>& result)
|
||||
{
|
||||
in6_addr addr = addr_in->sin6_addr;
|
||||
|
||||
typedef union
|
||||
{
|
||||
uint16 combined;
|
||||
uint8 split[2];
|
||||
} ByteUnion;
|
||||
|
||||
ByteUnion temp;
|
||||
uint16 arr[8];
|
||||
|
||||
for (int i = 0; i < 8; ++i) // Swap bytes from network to host order
|
||||
{
|
||||
temp.split[0] = addr.s6_addr[i * 2 + 1];
|
||||
temp.split[1] = addr.s6_addr[i * 2];
|
||||
|
||||
arr[i] = temp.combined;
|
||||
}
|
||||
|
||||
IPAddress ip (arr);
|
||||
result.addIfNotAlreadyThere (ip);
|
||||
}
|
||||
|
||||
void IPAddress::findAllAddresses (Array<IPAddress>& result, bool includeIPv6)
|
||||
{
|
||||
struct ifaddrs *ifaddr, *ifa;
|
||||
|
||||
if (getifaddrs (&ifaddr) == -1)
|
||||
return;
|
||||
|
||||
for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next)
|
||||
{
|
||||
if (ifa->ifa_addr == nullptr)
|
||||
continue;
|
||||
|
||||
if (ifa->ifa_addr->sa_family == AF_INET) addAddress ((const sockaddr_in*) ifa->ifa_addr, result);
|
||||
else if (ifa->ifa_addr->sa_family == AF_INET6 && includeIPv6) addAddress ((const sockaddr_in6*) ifa->ifa_addr, result);
|
||||
}
|
||||
|
||||
freeifaddrs (ifaddr);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
114
modules/juce_core/network/juce_IPAddress.h
Normal file
114
modules/juce_core/network/juce_IPAddress.h
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents an IP address.
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API IPAddress final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Populates a list of all the IP addresses that this machine is using. */
|
||||
static void findAllAddresses (Array<IPAddress>& results, bool includeIPv6 = false);
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a null address - 0.0.0.0 (IPv4) or ::, (IPv6)
|
||||
@param IPv6 if true indicates that this is an IPv6 address
|
||||
*/
|
||||
IPAddress (bool IPv6 = false) noexcept;
|
||||
|
||||
/** Creates an IPv4 or IPv6 address by reading 4 or 16 bytes from an array.
|
||||
@param bytes The array containing the bytes to read.
|
||||
@param IPv6 if true indicates that 16 bytes should be read instead of 4.
|
||||
*/
|
||||
explicit IPAddress (const uint8 bytes[], bool IPv6 = false) noexcept;
|
||||
|
||||
/** Creates an IPv6 address from an array of 8 16-bit integers
|
||||
@param bytes The array containing the bytes to read.
|
||||
*/
|
||||
explicit IPAddress (const uint16 bytes[8]) noexcept;
|
||||
|
||||
/** Creates an IPv4 address from 4 bytes. */
|
||||
IPAddress (uint8 address1, uint8 address2, uint8 address3, uint8 address4) noexcept;
|
||||
|
||||
/** Creates an IPv6 address from 8 16-bit integers */
|
||||
IPAddress (uint16 address1, uint16 address2, uint16 address3, uint16 address4,
|
||||
uint16 address5, uint16 address6, uint16 address7, uint16 address8) noexcept;
|
||||
|
||||
/** Creates an IPv4 address from a packed 32-bit integer, where the
|
||||
MSB is the first number in the address, and the LSB is the last.
|
||||
*/
|
||||
explicit IPAddress (uint32 asNativeEndian32Bit) noexcept;
|
||||
|
||||
/** Parses a string IP address of the form "1.2.3.4" (IPv4) or "1:2:3:4:5:6:7:8" (IPv6). */
|
||||
explicit IPAddress (const String& address);
|
||||
|
||||
/** Returns a dot- or colon-separated string in the form "1.2.3.4" (IPv4) or "1:2:3:4:5:6:7:8" (IPv6). */
|
||||
String toString() const;
|
||||
|
||||
/** Returns an IPv4 or IPv6 address meaning "any", equivalent to 0.0.0.0 (IPv4) or ::, (IPv6) */
|
||||
static IPAddress any (bool IPv6 = false) noexcept;
|
||||
|
||||
/** Returns an IPv4 address meaning "broadcast" (255.255.255.255) */
|
||||
static IPAddress broadcast() noexcept;
|
||||
|
||||
/** Returns an IPv4 or IPv6 address meaning "localhost", equivalent to 127.0.0.1 (IPv4) or ::1 (IPv6) */
|
||||
static IPAddress local (bool IPv6 = false) noexcept;
|
||||
|
||||
/** Returns a formatted version of the provided IPv6 address conforming to RFC 5952 with leading zeros suppressed,
|
||||
lower case characters, and double-colon notation used to represent contiguous 16-bit fields of zeros.
|
||||
|
||||
@param unformattedAddress the IPv6 address to be formatted
|
||||
*/
|
||||
static String getFormattedAddress (const String& unformattedAddress);
|
||||
|
||||
bool operator== (const IPAddress& other) const noexcept;
|
||||
bool operator!= (const IPAddress& other) const noexcept;
|
||||
|
||||
/** The elements of the IP address. */
|
||||
uint8 address[16];
|
||||
|
||||
bool isIPv6;
|
||||
|
||||
private:
|
||||
/** Union used to split a 16-bit unsigned integer into 2 8-bit unsigned integers or vice-versa */
|
||||
typedef union
|
||||
{
|
||||
uint16 combined;
|
||||
uint8 split[2];
|
||||
} ByteUnion;
|
||||
|
||||
/** Method used to zero the remaining bytes of the address array when creating IPv4 addresses */
|
||||
void zeroUnusedBytes()
|
||||
{
|
||||
for (int i = 4; i < 16; ++i)
|
||||
address[i] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
93
modules/juce_core/network/juce_MACAddress.cpp
Normal file
93
modules/juce_core/network/juce_MACAddress.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
MACAddress::MACAddress() noexcept
|
||||
{
|
||||
zeromem (address, sizeof (address));
|
||||
}
|
||||
|
||||
MACAddress::MACAddress (const MACAddress& other) noexcept
|
||||
{
|
||||
memcpy (address, other.address, sizeof (address));
|
||||
}
|
||||
|
||||
MACAddress& MACAddress::operator= (const MACAddress& other) noexcept
|
||||
{
|
||||
memcpy (address, other.address, sizeof (address));
|
||||
return *this;
|
||||
}
|
||||
|
||||
MACAddress::MACAddress (const uint8 bytes[6]) noexcept
|
||||
{
|
||||
memcpy (address, bytes, sizeof (address));
|
||||
}
|
||||
|
||||
MACAddress::MACAddress (StringRef addressString)
|
||||
{
|
||||
MemoryBlock hex;
|
||||
hex.loadFromHexString (addressString);
|
||||
|
||||
if (hex.getSize() == sizeof (address))
|
||||
memcpy (address, hex.getData(), sizeof (address));
|
||||
else
|
||||
zeromem (address, sizeof (address));
|
||||
}
|
||||
|
||||
String MACAddress::toString() const
|
||||
{
|
||||
return toString ("-");
|
||||
}
|
||||
|
||||
String MACAddress::toString (StringRef separator) const
|
||||
{
|
||||
String s;
|
||||
|
||||
for (size_t i = 0; i < sizeof (address); ++i)
|
||||
{
|
||||
s << String::toHexString ((int) address[i]).paddedLeft ('0', 2);
|
||||
|
||||
if (i < sizeof (address) - 1)
|
||||
s << separator;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int64 MACAddress::toInt64() const noexcept
|
||||
{
|
||||
int64 n = 0;
|
||||
|
||||
for (int i = (int) sizeof (address); --i >= 0;)
|
||||
n = (n << 8) | address[i];
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
bool MACAddress::isNull() const noexcept { return toInt64() == 0; }
|
||||
|
||||
bool MACAddress::operator== (const MACAddress& other) const noexcept { return memcmp (address, other.address, sizeof (address)) == 0; }
|
||||
bool MACAddress::operator!= (const MACAddress& other) const noexcept { return ! operator== (other); }
|
||||
|
||||
} // namespace juce
|
85
modules/juce_core/network/juce_MACAddress.h
Normal file
85
modules/juce_core/network/juce_MACAddress.h
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a MAC network card adapter address ID.
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API MACAddress final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Populates a list of the MAC addresses of all the available network cards. */
|
||||
static void findAllAddresses (Array<MACAddress>& results);
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a null address (00-00-00-00-00-00). */
|
||||
MACAddress() noexcept;
|
||||
|
||||
/** Creates a copy of another address. */
|
||||
MACAddress (const MACAddress&) noexcept;
|
||||
|
||||
/** Creates a copy of another address. */
|
||||
MACAddress& operator= (const MACAddress&) noexcept;
|
||||
|
||||
/** Creates an address from 6 bytes. */
|
||||
explicit MACAddress (const uint8 bytes[6]) noexcept;
|
||||
|
||||
/** Creates an address from a hex string.
|
||||
If the string isn't a 6-byte hex value, this will just default-initialise
|
||||
the object.
|
||||
*/
|
||||
explicit MACAddress (StringRef address);
|
||||
|
||||
/** Returns a pointer to the 6 bytes that make up this address. */
|
||||
const uint8* getBytes() const noexcept { return address; }
|
||||
|
||||
/** Returns a dash-separated string in the form "11-22-33-44-55-66" */
|
||||
String toString() const;
|
||||
|
||||
/** Returns a hex string of this address, using a custom separator between each byte. */
|
||||
String toString (StringRef separator) const;
|
||||
|
||||
/** Returns the address in the lower 6 bytes of an int64.
|
||||
|
||||
This uses a little-endian arrangement, with the first byte of the address being
|
||||
stored in the least-significant byte of the result value.
|
||||
*/
|
||||
int64 toInt64() const noexcept;
|
||||
|
||||
/** Returns true if this address is null (00-00-00-00-00-00). */
|
||||
bool isNull() const noexcept;
|
||||
|
||||
bool operator== (const MACAddress&) const noexcept;
|
||||
bool operator!= (const MACAddress&) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
private:
|
||||
uint8 address[6];
|
||||
};
|
||||
|
||||
} // namespace juce
|
63
modules/juce_core/network/juce_NamedPipe.cpp
Normal file
63
modules/juce_core/network/juce_NamedPipe.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
NamedPipe::NamedPipe() {}
|
||||
|
||||
NamedPipe::~NamedPipe()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool NamedPipe::openExisting (const String& pipeName)
|
||||
{
|
||||
close();
|
||||
|
||||
ScopedWriteLock sl (lock);
|
||||
currentPipeName = pipeName;
|
||||
return openInternal (pipeName, false, false);
|
||||
}
|
||||
|
||||
bool NamedPipe::isOpen() const
|
||||
{
|
||||
return pimpl != nullptr;
|
||||
}
|
||||
|
||||
bool NamedPipe::createNewPipe (const String& pipeName, bool mustNotExist)
|
||||
{
|
||||
close();
|
||||
|
||||
ScopedWriteLock sl (lock);
|
||||
currentPipeName = pipeName;
|
||||
return openInternal (pipeName, true, mustNotExist);
|
||||
}
|
||||
|
||||
String NamedPipe::getName() const
|
||||
{
|
||||
return currentPipeName;
|
||||
}
|
||||
|
||||
// other methods for this class are implemented in the platform-specific files
|
||||
|
||||
} // namespace juce
|
100
modules/juce_core/network/juce_NamedPipe.h
Normal file
100
modules/juce_core/network/juce_NamedPipe.h
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A cross-process pipe that can have data written to and read from it.
|
||||
|
||||
Two processes can use NamedPipe objects to exchange blocks of data.
|
||||
|
||||
@see InterprocessConnection
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API NamedPipe final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a NamedPipe. */
|
||||
NamedPipe();
|
||||
|
||||
/** Destructor. */
|
||||
~NamedPipe();
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to open a pipe that already exists.
|
||||
Returns true if it succeeds.
|
||||
*/
|
||||
bool openExisting (const String& pipeName);
|
||||
|
||||
/** Tries to create a new pipe.
|
||||
Returns true if it succeeds.
|
||||
If mustNotExist is true then it will fail if a pipe is already
|
||||
open with the same name.
|
||||
*/
|
||||
bool createNewPipe (const String& pipeName, bool mustNotExist = false);
|
||||
|
||||
/** Closes the pipe, if it's open. */
|
||||
void close();
|
||||
|
||||
/** True if the pipe is currently open. */
|
||||
bool isOpen() const;
|
||||
|
||||
/** Returns the last name that was used to try to open this pipe. */
|
||||
String getName() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Reads data from the pipe.
|
||||
|
||||
This will block until another thread has written enough data into the pipe to fill
|
||||
the number of bytes specified, or until another thread calls the cancelPendingReads()
|
||||
method.
|
||||
|
||||
If the operation fails, it returns -1, otherwise, it will return the number of
|
||||
bytes read.
|
||||
|
||||
If timeOutMilliseconds is less than zero, it will wait indefinitely, otherwise
|
||||
this is a maximum timeout for reading from the pipe.
|
||||
*/
|
||||
int read (void* destBuffer, int maxBytesToRead, int timeOutMilliseconds);
|
||||
|
||||
/** Writes some data to the pipe.
|
||||
@returns the number of bytes written, or -1 on failure.
|
||||
*/
|
||||
int write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_PUBLIC_IN_DLL_BUILD (class Pimpl)
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
String currentPipeName;
|
||||
ReadWriteLock lock;
|
||||
|
||||
bool openInternal (const String& pipeName, bool createPipe, bool mustNotExist);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NamedPipe)
|
||||
};
|
||||
|
||||
} // namespace juce
|
770
modules/juce_core/network/juce_Socket.cpp
Normal file
770
modules/juce_core/network/juce_Socket.cpp
Normal file
@ -0,0 +1,770 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_MSVC
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable : 4127 4389 4018)
|
||||
#endif
|
||||
|
||||
#ifndef AI_NUMERICSERV // (missing in older Mac SDKs)
|
||||
#define AI_NUMERICSERV 0x1000
|
||||
#endif
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
typedef int juce_socklen_t;
|
||||
typedef int juce_recvsend_size_t;
|
||||
typedef SOCKET SocketHandle;
|
||||
static const SocketHandle invalidSocket = INVALID_SOCKET;
|
||||
#elif JUCE_ANDROID
|
||||
typedef socklen_t juce_socklen_t;
|
||||
typedef size_t juce_recvsend_size_t;
|
||||
typedef int SocketHandle;
|
||||
static const SocketHandle invalidSocket = -1;
|
||||
#else
|
||||
typedef socklen_t juce_socklen_t;
|
||||
typedef socklen_t juce_recvsend_size_t;
|
||||
typedef int SocketHandle;
|
||||
static const SocketHandle invalidSocket = -1;
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
namespace SocketHelpers
|
||||
{
|
||||
static void initSockets()
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
static bool socketsStarted = false;
|
||||
|
||||
if (! socketsStarted)
|
||||
{
|
||||
socketsStarted = true;
|
||||
|
||||
WSADATA wsaData;
|
||||
const WORD wVersionRequested = MAKEWORD (1, 1);
|
||||
WSAStartup (wVersionRequested, &wsaData);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool isValidPortNumber (int port) noexcept
|
||||
{
|
||||
return isPositiveAndBelow (port, 65536);
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
static bool setOption (SocketHandle handle, int mode, int property, Type value) noexcept
|
||||
{
|
||||
return setsockopt (handle, mode, property, reinterpret_cast<const char*> (&value), sizeof (value)) == 0;
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
static bool setOption (SocketHandle handle, int property, Type value) noexcept
|
||||
{
|
||||
return setOption (handle, SOL_SOCKET, property, value);
|
||||
}
|
||||
|
||||
static bool resetSocketOptions (SocketHandle handle, bool isDatagram, bool allowBroadcast) noexcept
|
||||
{
|
||||
return handle > 0
|
||||
&& setOption (handle, SO_RCVBUF, (int) 65536)
|
||||
&& setOption (handle, SO_SNDBUF, (int) 65536)
|
||||
&& (isDatagram ? ((! allowBroadcast) || setOption (handle, SO_BROADCAST, (int) 1))
|
||||
: setOption (handle, IPPROTO_TCP, TCP_NODELAY, (int) 1));
|
||||
}
|
||||
|
||||
static void closeSocket (std::atomic<int>& handle, CriticalSection& readLock,
|
||||
bool isListener, int portNumber, std::atomic<bool>& connected) noexcept
|
||||
{
|
||||
const SocketHandle h = handle.load();
|
||||
handle = -1;
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
ignoreUnused (portNumber, isListener, readLock);
|
||||
|
||||
if (h != (unsigned) SOCKET_ERROR || connected)
|
||||
closesocket (h);
|
||||
|
||||
// make sure any read process finishes before we delete the socket
|
||||
CriticalSection::ScopedLockType lock (readLock);
|
||||
connected = false;
|
||||
#else
|
||||
if (connected)
|
||||
{
|
||||
connected = false;
|
||||
|
||||
if (isListener)
|
||||
{
|
||||
// need to do this to interrupt the accept() function..
|
||||
StreamingSocket temp;
|
||||
temp.connect (IPAddress::local().toString(), portNumber, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (h != -1)
|
||||
{
|
||||
// unblock any pending read requests
|
||||
::shutdown (h, SHUT_RDWR);
|
||||
|
||||
{
|
||||
// see man-page of recv on linux about a race condition where the
|
||||
// shutdown command is lost if the receiving thread does not have
|
||||
// a chance to process before close is called. On Mac OS X shutdown
|
||||
// does not unblock a select call, so using a lock here will dead-lock
|
||||
// both threads.
|
||||
#if JUCE_LINUX || JUCE_ANDROID
|
||||
CriticalSection::ScopedLockType lock (readLock);
|
||||
::close (h);
|
||||
#else
|
||||
::close (h);
|
||||
CriticalSection::ScopedLockType lock (readLock);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool bindSocket (SocketHandle handle, int port, const String& address) noexcept
|
||||
{
|
||||
if (handle <= 0 || ! isValidPortNumber (port))
|
||||
return false;
|
||||
|
||||
struct sockaddr_in addr;
|
||||
zerostruct (addr); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct)
|
||||
|
||||
addr.sin_family = PF_INET;
|
||||
addr.sin_port = htons ((uint16) port);
|
||||
addr.sin_addr.s_addr = address.isNotEmpty() ? ::inet_addr (address.toRawUTF8())
|
||||
: htonl (INADDR_ANY);
|
||||
|
||||
return ::bind (handle, (struct sockaddr*) &addr, sizeof (addr)) >= 0;
|
||||
}
|
||||
|
||||
static int getBoundPort (SocketHandle handle) noexcept
|
||||
{
|
||||
if (handle > 0)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
socklen_t len = sizeof (addr);
|
||||
|
||||
if (getsockname (handle, (struct sockaddr*) &addr, &len) == 0)
|
||||
return ntohs (addr.sin_port);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static String getConnectedAddress (SocketHandle handle) noexcept
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
socklen_t len = sizeof (addr);
|
||||
|
||||
if (getpeername (handle, (struct sockaddr*) &addr, &len) >= 0)
|
||||
return inet_ntoa (addr.sin_addr);
|
||||
|
||||
return String ("0.0.0.0");
|
||||
}
|
||||
|
||||
static int readSocket (SocketHandle handle,
|
||||
void* destBuffer, int maxBytesToRead,
|
||||
std::atomic<bool>& connected,
|
||||
bool blockUntilSpecifiedAmountHasArrived,
|
||||
CriticalSection& readLock,
|
||||
String* senderIP = nullptr,
|
||||
int* senderPort = nullptr) noexcept
|
||||
{
|
||||
int bytesRead = 0;
|
||||
|
||||
while (bytesRead < maxBytesToRead)
|
||||
{
|
||||
long bytesThisTime = -1;
|
||||
auto buffer = static_cast<char*> (destBuffer) + bytesRead;
|
||||
auto numToRead = (juce_recvsend_size_t) (maxBytesToRead - bytesRead);
|
||||
|
||||
{
|
||||
// avoid race-condition
|
||||
CriticalSection::ScopedTryLockType lock (readLock);
|
||||
|
||||
if (lock.isLocked())
|
||||
{
|
||||
if (senderIP == nullptr || senderPort == nullptr)
|
||||
{
|
||||
bytesThisTime = ::recv (handle, buffer, numToRead, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
sockaddr_in client;
|
||||
socklen_t clientLen = sizeof (sockaddr);
|
||||
|
||||
bytesThisTime = ::recvfrom (handle, buffer, numToRead, 0, (sockaddr*) &client, &clientLen);
|
||||
|
||||
*senderIP = String::fromUTF8 (inet_ntoa (client.sin_addr), 16);
|
||||
*senderPort = ntohs (client.sin_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bytesThisTime <= 0 || ! connected)
|
||||
{
|
||||
if (bytesRead == 0 && blockUntilSpecifiedAmountHasArrived)
|
||||
bytesRead = -1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
bytesRead += bytesThisTime;
|
||||
|
||||
if (! blockUntilSpecifiedAmountHasArrived)
|
||||
break;
|
||||
}
|
||||
|
||||
return (int) bytesRead;
|
||||
}
|
||||
|
||||
static int waitForReadiness (std::atomic<int>& handle, CriticalSection& readLock,
|
||||
bool forReading, int timeoutMsecs) noexcept
|
||||
{
|
||||
// avoid race-condition
|
||||
CriticalSection::ScopedTryLockType lock (readLock);
|
||||
|
||||
if (! lock.isLocked())
|
||||
return -1;
|
||||
|
||||
int h = handle.load();
|
||||
|
||||
struct timeval timeout;
|
||||
struct timeval* timeoutp;
|
||||
|
||||
if (timeoutMsecs >= 0)
|
||||
{
|
||||
timeout.tv_sec = timeoutMsecs / 1000;
|
||||
timeout.tv_usec = (timeoutMsecs % 1000) * 1000;
|
||||
timeoutp = &timeout;
|
||||
}
|
||||
else
|
||||
{
|
||||
timeoutp = 0;
|
||||
}
|
||||
|
||||
fd_set rset, wset;
|
||||
FD_ZERO (&rset);
|
||||
FD_SET (h, &rset);
|
||||
FD_ZERO (&wset);
|
||||
FD_SET (h, &wset);
|
||||
|
||||
fd_set* const prset = forReading ? &rset : nullptr;
|
||||
fd_set* const pwset = forReading ? nullptr : &wset;
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
if (select ((int) h + 1, prset, pwset, 0, timeoutp) < 0)
|
||||
return -1;
|
||||
#else
|
||||
{
|
||||
int result;
|
||||
while ((result = select (h + 1, prset, pwset, 0, timeoutp)) < 0
|
||||
&& errno == EINTR)
|
||||
{
|
||||
}
|
||||
|
||||
if (result < 0)
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// we are closing
|
||||
if (handle.load() < 0)
|
||||
return -1;
|
||||
|
||||
{
|
||||
int opt;
|
||||
juce_socklen_t len = sizeof (opt);
|
||||
|
||||
if (getsockopt (h, SOL_SOCKET, SO_ERROR, (char*) &opt, &len) < 0
|
||||
|| opt != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return FD_ISSET (h, forReading ? &rset : &wset) ? 1 : 0;
|
||||
}
|
||||
|
||||
static bool setSocketBlockingState (SocketHandle handle, bool shouldBlock) noexcept
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
u_long nonBlocking = shouldBlock ? 0 : (u_long) 1;
|
||||
return ioctlsocket (handle, FIONBIO, &nonBlocking) == 0;
|
||||
#else
|
||||
int socketFlags = fcntl (handle, F_GETFL, 0);
|
||||
|
||||
if (socketFlags == -1)
|
||||
return false;
|
||||
|
||||
if (shouldBlock)
|
||||
socketFlags &= ~O_NONBLOCK;
|
||||
else
|
||||
socketFlags |= O_NONBLOCK;
|
||||
|
||||
return fcntl (handle, F_SETFL, socketFlags) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static addrinfo* getAddressInfo (bool isDatagram, const String& hostName, int portNumber)
|
||||
{
|
||||
struct addrinfo hints;
|
||||
zerostruct (hints);
|
||||
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = isDatagram ? SOCK_DGRAM : SOCK_STREAM;
|
||||
hints.ai_flags = AI_NUMERICSERV;
|
||||
|
||||
struct addrinfo* info = nullptr;
|
||||
|
||||
if (getaddrinfo (hostName.toRawUTF8(), String (portNumber).toRawUTF8(), &hints, &info) == 0)
|
||||
return info;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool connectSocket (std::atomic<int>& handle,
|
||||
CriticalSection& readLock,
|
||||
const String& hostName,
|
||||
int portNumber,
|
||||
int timeOutMillisecs) noexcept
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
if (auto* info = getAddressInfo (false, hostName, portNumber))
|
||||
{
|
||||
for (auto* i = info; i != nullptr; i = i->ai_next)
|
||||
{
|
||||
auto newHandle = socket (i->ai_family, i->ai_socktype, 0);
|
||||
|
||||
if (newHandle != invalidSocket)
|
||||
{
|
||||
setSocketBlockingState (newHandle, false);
|
||||
auto result = ::connect (newHandle, i->ai_addr, (socklen_t) i->ai_addrlen);
|
||||
success = (result >= 0);
|
||||
|
||||
if (! success)
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
if (result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
|
||||
#else
|
||||
if (errno == EINPROGRESS)
|
||||
#endif
|
||||
{
|
||||
std::atomic<int> cvHandle { (int) newHandle };
|
||||
|
||||
if (waitForReadiness (cvHandle, readLock, false, timeOutMillisecs) == 1)
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
handle = (int) newHandle;
|
||||
break;
|
||||
}
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
closesocket (newHandle);
|
||||
#else
|
||||
::close (newHandle);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo (info);
|
||||
|
||||
if (success)
|
||||
{
|
||||
setSocketBlockingState (handle, true);
|
||||
resetSocketOptions (handle, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static void makeReusable (int handle) noexcept
|
||||
{
|
||||
setOption (handle, SO_REUSEADDR, (int) 1);
|
||||
}
|
||||
|
||||
static bool multicast (int handle, const String& multicastIPAddress,
|
||||
const String& interfaceIPAddress, bool join) noexcept
|
||||
{
|
||||
struct ip_mreq mreq;
|
||||
|
||||
zerostruct (mreq);
|
||||
mreq.imr_multiaddr.s_addr = inet_addr (multicastIPAddress.toRawUTF8());
|
||||
mreq.imr_interface.s_addr = INADDR_ANY;
|
||||
|
||||
if (interfaceIPAddress.isNotEmpty())
|
||||
mreq.imr_interface.s_addr = inet_addr (interfaceIPAddress.toRawUTF8());
|
||||
|
||||
return setsockopt (handle, IPPROTO_IP,
|
||||
join ? IP_ADD_MEMBERSHIP
|
||||
: IP_DROP_MEMBERSHIP,
|
||||
(const char*) &mreq, sizeof (mreq)) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
StreamingSocket::StreamingSocket()
|
||||
{
|
||||
SocketHelpers::initSockets();
|
||||
}
|
||||
|
||||
StreamingSocket::StreamingSocket (const String& host, int portNum, int h)
|
||||
: hostName (host),
|
||||
portNumber (portNum),
|
||||
handle (h),
|
||||
connected (true)
|
||||
{
|
||||
jassert (SocketHelpers::isValidPortNumber (portNum));
|
||||
|
||||
SocketHelpers::initSockets();
|
||||
SocketHelpers::resetSocketOptions (h, false, false);
|
||||
}
|
||||
|
||||
StreamingSocket::~StreamingSocket()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int StreamingSocket::read (void* destBuffer, int maxBytesToRead, bool shouldBlock)
|
||||
{
|
||||
return (connected && ! isListener) ? SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead,
|
||||
connected, shouldBlock, readLock)
|
||||
: -1;
|
||||
}
|
||||
|
||||
int StreamingSocket::write (const void* sourceBuffer, int numBytesToWrite)
|
||||
{
|
||||
if (isListener || ! connected)
|
||||
return -1;
|
||||
|
||||
return (int) ::send (handle, (const char*) sourceBuffer, (juce_recvsend_size_t) numBytesToWrite, 0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int StreamingSocket::waitUntilReady (bool readyForReading, int timeoutMsecs)
|
||||
{
|
||||
return connected ? SocketHelpers::waitForReadiness (handle, readLock, readyForReading, timeoutMsecs)
|
||||
: -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool StreamingSocket::bindToPort (int port)
|
||||
{
|
||||
return bindToPort (port, String());
|
||||
}
|
||||
|
||||
bool StreamingSocket::bindToPort (int port, const String& addr)
|
||||
{
|
||||
jassert (SocketHelpers::isValidPortNumber (port));
|
||||
|
||||
return SocketHelpers::bindSocket (handle, port, addr);
|
||||
}
|
||||
|
||||
int StreamingSocket::getBoundPort() const noexcept
|
||||
{
|
||||
return SocketHelpers::getBoundPort (handle);
|
||||
}
|
||||
|
||||
bool StreamingSocket::connect (const String& remoteHostName, int remotePortNumber, int timeOutMillisecs)
|
||||
{
|
||||
jassert (SocketHelpers::isValidPortNumber (remotePortNumber));
|
||||
|
||||
if (isListener)
|
||||
{
|
||||
jassertfalse; // a listener socket can't connect to another one!
|
||||
return false;
|
||||
}
|
||||
|
||||
if (connected)
|
||||
close();
|
||||
|
||||
hostName = remoteHostName;
|
||||
portNumber = remotePortNumber;
|
||||
isListener = false;
|
||||
|
||||
connected = SocketHelpers::connectSocket (handle, readLock, remoteHostName,
|
||||
remotePortNumber, timeOutMillisecs);
|
||||
|
||||
if (! (connected && SocketHelpers::resetSocketOptions (handle, false, false)))
|
||||
{
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void StreamingSocket::close()
|
||||
{
|
||||
SocketHelpers::closeSocket (handle, readLock, isListener, portNumber, connected);
|
||||
|
||||
hostName.clear();
|
||||
portNumber = 0;
|
||||
handle = -1;
|
||||
isListener = false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool StreamingSocket::createListener (int newPortNumber, const String& localHostName)
|
||||
{
|
||||
jassert (SocketHelpers::isValidPortNumber (newPortNumber));
|
||||
|
||||
if (connected)
|
||||
close();
|
||||
|
||||
hostName = "listener";
|
||||
portNumber = newPortNumber;
|
||||
isListener = true;
|
||||
|
||||
handle = (int) socket (AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (handle < 0)
|
||||
return false;
|
||||
|
||||
#if ! JUCE_WINDOWS // on windows, adding this option produces behaviour different to posix
|
||||
SocketHelpers::makeReusable (handle);
|
||||
#endif
|
||||
|
||||
if (SocketHelpers::bindSocket (handle, portNumber, localHostName)
|
||||
&& listen (handle, SOMAXCONN) >= 0)
|
||||
{
|
||||
connected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
StreamingSocket* StreamingSocket::waitForNextConnection() const
|
||||
{
|
||||
// To call this method, you first have to use createListener() to
|
||||
// prepare this socket as a listener.
|
||||
jassert (isListener || ! connected);
|
||||
|
||||
if (connected && isListener)
|
||||
{
|
||||
struct sockaddr_storage address;
|
||||
juce_socklen_t len = sizeof (address);
|
||||
auto newSocket = (int) accept (handle, (struct sockaddr*) &address, &len);
|
||||
|
||||
if (newSocket >= 0 && connected)
|
||||
return new StreamingSocket (inet_ntoa (((struct sockaddr_in*) &address)->sin_addr),
|
||||
portNumber, newSocket);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool StreamingSocket::isLocal() const noexcept
|
||||
{
|
||||
if (! isConnected())
|
||||
return false;
|
||||
|
||||
Array<IPAddress> localAddresses;
|
||||
IPAddress::findAllAddresses (localAddresses);
|
||||
IPAddress currentIP (SocketHelpers::getConnectedAddress (handle));
|
||||
|
||||
for (auto& a : localAddresses)
|
||||
if (a == currentIP)
|
||||
return true;
|
||||
|
||||
return hostName == "127.0.0.1";
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
DatagramSocket::DatagramSocket (bool canBroadcast)
|
||||
{
|
||||
SocketHelpers::initSockets();
|
||||
|
||||
handle = (int) socket (AF_INET, SOCK_DGRAM, 0);
|
||||
|
||||
if (handle >= 0)
|
||||
{
|
||||
SocketHelpers::resetSocketOptions (handle, true, canBroadcast);
|
||||
SocketHelpers::makeReusable (handle);
|
||||
}
|
||||
}
|
||||
|
||||
DatagramSocket::~DatagramSocket()
|
||||
{
|
||||
if (lastServerAddress != nullptr)
|
||||
freeaddrinfo (static_cast<struct addrinfo*> (lastServerAddress));
|
||||
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void DatagramSocket::shutdown()
|
||||
{
|
||||
if (handle < 0)
|
||||
return;
|
||||
|
||||
std::atomic<int> handleCopy { handle.load() };
|
||||
handle = -1;
|
||||
std::atomic<bool> connected { false };
|
||||
SocketHelpers::closeSocket (handleCopy, readLock, false, 0, connected);
|
||||
}
|
||||
|
||||
bool DatagramSocket::bindToPort (int port)
|
||||
{
|
||||
return bindToPort (port, String());
|
||||
}
|
||||
|
||||
bool DatagramSocket::bindToPort (int port, const String& addr)
|
||||
{
|
||||
jassert (SocketHelpers::isValidPortNumber (port));
|
||||
|
||||
if (SocketHelpers::bindSocket (handle, port, addr))
|
||||
{
|
||||
isBound = true;
|
||||
lastBindAddress = addr;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int DatagramSocket::getBoundPort() const noexcept
|
||||
{
|
||||
return (handle >= 0 && isBound) ? SocketHelpers::getBoundPort (handle) : -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int DatagramSocket::waitUntilReady (bool readyForReading, int timeoutMsecs)
|
||||
{
|
||||
if (handle < 0)
|
||||
return -1;
|
||||
|
||||
return SocketHelpers::waitForReadiness (handle, readLock, readyForReading, timeoutMsecs);
|
||||
}
|
||||
|
||||
int DatagramSocket::read (void* destBuffer, int maxBytesToRead, bool shouldBlock)
|
||||
{
|
||||
if (handle < 0 || ! isBound)
|
||||
return -1;
|
||||
|
||||
std::atomic<bool> connected { true };
|
||||
|
||||
SocketHelpers::setSocketBlockingState (handle, shouldBlock);
|
||||
return SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead,
|
||||
connected, shouldBlock, readLock);
|
||||
}
|
||||
|
||||
int DatagramSocket::read (void* destBuffer, int maxBytesToRead, bool shouldBlock, String& senderIPAddress, int& senderPort)
|
||||
{
|
||||
if (handle < 0 || ! isBound)
|
||||
return -1;
|
||||
|
||||
std::atomic<bool> connected { true };
|
||||
|
||||
SocketHelpers::setSocketBlockingState (handle, shouldBlock);
|
||||
return SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, connected,
|
||||
shouldBlock, readLock, &senderIPAddress, &senderPort);
|
||||
}
|
||||
|
||||
int DatagramSocket::write (const String& remoteHostname, int remotePortNumber,
|
||||
const void* sourceBuffer, int numBytesToWrite)
|
||||
{
|
||||
jassert (SocketHelpers::isValidPortNumber (remotePortNumber));
|
||||
|
||||
if (handle < 0)
|
||||
return -1;
|
||||
|
||||
struct addrinfo*& info = reinterpret_cast<struct addrinfo*&> (lastServerAddress);
|
||||
|
||||
// getaddrinfo can be quite slow so cache the result of the address lookup
|
||||
if (info == nullptr || remoteHostname != lastServerHost || remotePortNumber != lastServerPort)
|
||||
{
|
||||
if (info != nullptr)
|
||||
freeaddrinfo (info);
|
||||
|
||||
if ((info = SocketHelpers::getAddressInfo (true, remoteHostname, remotePortNumber)) == nullptr)
|
||||
return -1;
|
||||
|
||||
lastServerHost = remoteHostname;
|
||||
lastServerPort = remotePortNumber;
|
||||
}
|
||||
|
||||
return (int) ::sendto (handle, (const char*) sourceBuffer,
|
||||
(juce_recvsend_size_t) numBytesToWrite, 0,
|
||||
info->ai_addr, (socklen_t) info->ai_addrlen);
|
||||
}
|
||||
|
||||
bool DatagramSocket::joinMulticast (const String& multicastIPAddress)
|
||||
{
|
||||
if (! isBound || handle < 0)
|
||||
return false;
|
||||
|
||||
return SocketHelpers::multicast (handle, multicastIPAddress, lastBindAddress, true);
|
||||
}
|
||||
|
||||
bool DatagramSocket::leaveMulticast (const String& multicastIPAddress)
|
||||
{
|
||||
if (! isBound || handle < 0)
|
||||
return false;
|
||||
|
||||
return SocketHelpers::multicast (handle, multicastIPAddress, lastBindAddress, false);
|
||||
}
|
||||
|
||||
bool DatagramSocket::setMulticastLoopbackEnabled (bool enable)
|
||||
{
|
||||
if (! isBound || handle < 0)
|
||||
return false;
|
||||
|
||||
return SocketHelpers::setOption<bool> (handle, IPPROTO_IP, IP_MULTICAST_LOOP, enable);
|
||||
}
|
||||
|
||||
bool DatagramSocket::setEnablePortReuse (bool enabled)
|
||||
{
|
||||
#if JUCE_ANDROID
|
||||
ignoreUnused (enabled);
|
||||
#else
|
||||
if (handle >= 0)
|
||||
return SocketHelpers::setOption (handle,
|
||||
#if JUCE_WINDOWS || JUCE_LINUX
|
||||
SO_REUSEADDR, // port re-use is implied by addr re-use on these platforms
|
||||
#else
|
||||
SO_REUSEPORT,
|
||||
#endif
|
||||
(int) (enabled ? 1 : 0));
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if JUCE_MSVC
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
359
modules/juce_core/network/juce_Socket.h
Normal file
359
modules/juce_core/network/juce_Socket.h
Normal file
@ -0,0 +1,359 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A wrapper for a streaming (TCP) socket.
|
||||
|
||||
This allows low-level use of sockets; for an easier-to-use messaging layer on top of
|
||||
sockets, you could also try the InterprocessConnection class.
|
||||
|
||||
@see DatagramSocket, InterprocessConnection, InterprocessConnectionServer
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API StreamingSocket final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an uninitialised socket.
|
||||
|
||||
To connect it, use the connect() method, after which you can read() or write()
|
||||
to it.
|
||||
|
||||
To wait for other sockets to connect to this one, the createListener() method
|
||||
enters "listener" mode, and can be used to spawn new sockets for each connection
|
||||
that comes along.
|
||||
*/
|
||||
StreamingSocket();
|
||||
|
||||
/** Destructor. */
|
||||
~StreamingSocket();
|
||||
|
||||
//==============================================================================
|
||||
/** Binds the socket to the specified local port.
|
||||
|
||||
@returns true on success; false may indicate that another socket is already bound
|
||||
on the same port
|
||||
*/
|
||||
bool bindToPort (int localPortNumber);
|
||||
|
||||
/** Binds the socket to the specified local port and local address.
|
||||
|
||||
If localAddress is not an empty string then the socket will be bound to localAddress
|
||||
as well. This is useful if you would like to bind your socket to a specific network
|
||||
adapter. Note that localAddress must be an IP address assigned to one of your
|
||||
network address otherwise this function will fail.
|
||||
@returns true on success; false may indicate that another socket is already bound
|
||||
on the same port
|
||||
@see bindToPort(int localPortNumber), IPAddress::findAllAddresses
|
||||
*/
|
||||
bool bindToPort (int localPortNumber, const String& localAddress);
|
||||
|
||||
/** Returns the local port number to which this socket is currently bound.
|
||||
|
||||
This is useful if you need to know to which port the OS has actually bound your
|
||||
socket when calling the constructor or bindToPort with zero as the
|
||||
localPortNumber argument. Returns -1 if the function fails.
|
||||
*/
|
||||
int getBoundPort() const noexcept;
|
||||
|
||||
/** Tries to connect the socket to hostname:port.
|
||||
|
||||
If timeOutMillisecs is 0, then this method will block until the operating system
|
||||
rejects the connection (which could take a long time).
|
||||
|
||||
@returns true if it succeeds.
|
||||
@see isConnected
|
||||
*/
|
||||
bool connect (const String& remoteHostname,
|
||||
int remotePortNumber,
|
||||
int timeOutMillisecs = 3000);
|
||||
|
||||
/** True if the socket is currently connected. */
|
||||
bool isConnected() const noexcept { return connected; }
|
||||
|
||||
/** Closes the connection. */
|
||||
void close();
|
||||
|
||||
/** Returns the name of the currently connected host. */
|
||||
const String& getHostName() const noexcept { return hostName; }
|
||||
|
||||
/** Returns the port number that's currently open. */
|
||||
int getPort() const noexcept { return portNumber; }
|
||||
|
||||
/** True if the socket is connected to this machine rather than over the network. */
|
||||
bool isLocal() const noexcept;
|
||||
|
||||
/** Returns the OS's socket handle that's currently open. */
|
||||
int getRawSocketHandle() const noexcept { return handle; }
|
||||
|
||||
//==============================================================================
|
||||
/** Waits until the socket is ready for reading or writing.
|
||||
|
||||
If readyForReading is true, it will wait until the socket is ready for
|
||||
reading; if false, it will wait until it's ready for writing.
|
||||
|
||||
If the timeout is < 0, it will wait forever, or else will give up after
|
||||
the specified time.
|
||||
|
||||
If the socket is ready on return, this returns 1. If it times-out before
|
||||
the socket becomes ready, it returns 0. If an error occurs, it returns -1.
|
||||
*/
|
||||
int waitUntilReady (bool readyForReading, int timeoutMsecs);
|
||||
|
||||
/** Reads bytes from the socket.
|
||||
|
||||
If blockUntilSpecifiedAmountHasArrived is true, the method will block until
|
||||
maxBytesToRead bytes have been read, (or until an error occurs). If this
|
||||
flag is false, the method will return as much data as is currently available
|
||||
without blocking.
|
||||
|
||||
@returns the number of bytes read, or -1 if there was an error.
|
||||
@see waitUntilReady
|
||||
*/
|
||||
int read (void* destBuffer, int maxBytesToRead,
|
||||
bool blockUntilSpecifiedAmountHasArrived);
|
||||
|
||||
/** Writes bytes to the socket from a buffer.
|
||||
|
||||
Note that this method will block unless you have checked the socket is ready
|
||||
for writing before calling it (see the waitUntilReady() method).
|
||||
|
||||
@returns the number of bytes written, or -1 if there was an error.
|
||||
*/
|
||||
int write (const void* sourceBuffer, int numBytesToWrite);
|
||||
|
||||
//==============================================================================
|
||||
/** Puts this socket into "listener" mode.
|
||||
|
||||
When in this mode, your thread can call waitForNextConnection() repeatedly,
|
||||
which will spawn new sockets for each new connection, so that these can
|
||||
be handled in parallel by other threads.
|
||||
|
||||
@param portNumber the port number to listen on
|
||||
@param localHostName the interface address to listen on - pass an empty
|
||||
string to listen on all addresses
|
||||
@returns true if it manages to open the socket successfully.
|
||||
|
||||
@see waitForNextConnection
|
||||
*/
|
||||
bool createListener (int portNumber, const String& localHostName = String());
|
||||
|
||||
/** When in "listener" mode, this waits for a connection and spawns it as a new
|
||||
socket.
|
||||
|
||||
The object that gets returned will be owned by the caller.
|
||||
|
||||
This method can only be called after using createListener().
|
||||
|
||||
@see createListener
|
||||
*/
|
||||
StreamingSocket* waitForNextConnection() const;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String hostName;
|
||||
std::atomic<int> portNumber { 0 }, handle { -1 };
|
||||
std::atomic<bool> connected { false };
|
||||
bool isListener = false;
|
||||
mutable CriticalSection readLock;
|
||||
|
||||
StreamingSocket (const String& hostname, int portNumber, int handle);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StreamingSocket)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A wrapper for a datagram (UDP) socket.
|
||||
|
||||
This allows low-level use of sockets; for an easier-to-use messaging layer on top of
|
||||
sockets, you could also try the InterprocessConnection class.
|
||||
|
||||
@see StreamingSocket, InterprocessConnection, InterprocessConnectionServer
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API DatagramSocket final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/**
|
||||
Creates a datagram socket.
|
||||
|
||||
You first need to bind this socket to a port with bindToPort if you intend to read
|
||||
from this socket.
|
||||
|
||||
If enableBroadcasting is true, the socket will be allowed to send broadcast messages
|
||||
(may require extra privileges on linux)
|
||||
*/
|
||||
DatagramSocket (bool enableBroadcasting = false);
|
||||
|
||||
|
||||
/** Destructor. */
|
||||
~DatagramSocket();
|
||||
|
||||
//==============================================================================
|
||||
/** Binds the socket to the specified local port.
|
||||
|
||||
The localPortNumber is the port on which to bind this socket. If this value is 0,
|
||||
the port number is assigned by the operating system.
|
||||
|
||||
@returns true on success; false may indicate that another socket is already bound
|
||||
on the same port
|
||||
*/
|
||||
bool bindToPort (int localPortNumber);
|
||||
|
||||
/** Binds the socket to the specified local port and local address.
|
||||
|
||||
If localAddress is not an empty string then the socket will be bound to localAddress
|
||||
as well. This is useful if you would like to bind your socket to a specific network
|
||||
adapter. Note that localAddress must be an IP address assigned to one of your
|
||||
network address otherwise this function will fail.
|
||||
@returns true on success; false may indicate that another socket is already bound
|
||||
on the same port
|
||||
@see bindToPort(int localPortNumber), IPAddress::findAllAddresses
|
||||
*/
|
||||
bool bindToPort (int localPortNumber, const String& localAddress);
|
||||
|
||||
/** Returns the local port number to which this socket is currently bound.
|
||||
|
||||
This is useful if you need to know to which port the OS has actually bound your
|
||||
socket when bindToPort was called with zero.
|
||||
|
||||
Returns -1 if the socket didn't bind to any port yet or an error occurred. */
|
||||
int getBoundPort() const noexcept;
|
||||
|
||||
/** Returns the OS's socket handle that's currently open. */
|
||||
int getRawSocketHandle() const noexcept { return handle; }
|
||||
|
||||
//==============================================================================
|
||||
/** Waits until the socket is ready for reading or writing.
|
||||
|
||||
If readyForReading is true, it will wait until the socket is ready for
|
||||
reading; if false, it will wait until it's ready for writing.
|
||||
|
||||
If the timeout is < 0, it will wait forever, or else will give up after
|
||||
the specified time.
|
||||
|
||||
If the socket is ready on return, this returns 1. If it times-out before
|
||||
the socket becomes ready, it returns 0. If an error occurs, it returns -1.
|
||||
*/
|
||||
int waitUntilReady (bool readyForReading, int timeoutMsecs);
|
||||
|
||||
/** Reads bytes from the socket.
|
||||
|
||||
If blockUntilSpecifiedAmountHasArrived is true, the method will block until
|
||||
maxBytesToRead bytes have been read, (or until an error occurs). If this
|
||||
flag is false, the method will return as much data as is currently available
|
||||
without blocking.
|
||||
|
||||
@returns the number of bytes read, or -1 if there was an error.
|
||||
@see waitUntilReady
|
||||
*/
|
||||
int read (void* destBuffer, int maxBytesToRead,
|
||||
bool blockUntilSpecifiedAmountHasArrived);
|
||||
|
||||
/** Reads bytes from the socket and return the IP address of the sender.
|
||||
|
||||
If blockUntilSpecifiedAmountHasArrived is true, the method will block until
|
||||
maxBytesToRead bytes have been read, (or until an error occurs). If this
|
||||
flag is false, the method will return as much data as is currently available
|
||||
without blocking.
|
||||
|
||||
@returns the number of bytes read, or -1 if there was an error. On a successful
|
||||
result, the senderIPAddress value will be set to the IP of the sender.
|
||||
@see waitUntilReady
|
||||
*/
|
||||
int read (void* destBuffer, int maxBytesToRead,
|
||||
bool blockUntilSpecifiedAmountHasArrived,
|
||||
String& senderIPAddress, int& senderPortNumber);
|
||||
|
||||
/** Writes bytes to the socket from a buffer.
|
||||
|
||||
Note that this method will block unless you have checked the socket is ready
|
||||
for writing before calling it (see the waitUntilReady() method).
|
||||
|
||||
@returns the number of bytes written, or -1 if there was an error.
|
||||
*/
|
||||
int write (const String& remoteHostname, int remotePortNumber,
|
||||
const void* sourceBuffer, int numBytesToWrite);
|
||||
|
||||
/** Closes the underlying socket object.
|
||||
|
||||
Closes the underlying socket object and aborts any read or write operations.
|
||||
Note that all other methods will return an error after this call. This
|
||||
method is useful if another thread is blocking in a read/write call and you
|
||||
would like to abort the read/write thread. Simply deleting the socket
|
||||
object without calling shutdown may cause a race-condition where the read/write
|
||||
returns just before the socket is deleted and the subsequent read/write would
|
||||
try to read from an invalid pointer. By calling shutdown first, the socket
|
||||
object remains valid but all current and subsequent calls to read/write will
|
||||
return immediately.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
//==============================================================================
|
||||
/** Join a multicast group.
|
||||
@returns true if it succeeds.
|
||||
*/
|
||||
bool joinMulticast (const String& multicastIPAddress);
|
||||
|
||||
/** Leave a multicast group.
|
||||
@returns true if it succeeds.
|
||||
*/
|
||||
bool leaveMulticast (const String& multicastIPAddress);
|
||||
|
||||
/** Enables or disables multicast loopback.
|
||||
@returns true if it succeeds.
|
||||
*/
|
||||
bool setMulticastLoopbackEnabled (bool enableLoopback);
|
||||
|
||||
//==============================================================================
|
||||
/** Allow other applications to re-use the port.
|
||||
|
||||
Allow any other application currently running to bind to the same port.
|
||||
Do not use this if your socket handles sensitive data as it could be
|
||||
read by any, possibly malicious, third-party apps.
|
||||
|
||||
Returns true on success.
|
||||
*/
|
||||
bool setEnablePortReuse (bool enabled);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
std::atomic<int> handle { -1 };
|
||||
bool isBound = false;
|
||||
String lastBindAddress, lastServerHost;
|
||||
int lastServerPort = -1;
|
||||
void* lastServerAddress = nullptr;
|
||||
mutable CriticalSection readLock;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DatagramSocket)
|
||||
};
|
||||
|
||||
} // namespace juce
|
912
modules/juce_core/network/juce_URL.cpp
Normal file
912
modules/juce_core/network/juce_URL.cpp
Normal file
@ -0,0 +1,912 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct FallbackDownloadTask : public URL::DownloadTask,
|
||||
public Thread
|
||||
{
|
||||
FallbackDownloadTask (FileOutputStream* outputStreamToUse,
|
||||
size_t bufferSizeToUse,
|
||||
WebInputStream* streamToUse,
|
||||
URL::DownloadTask::Listener* listenerToUse)
|
||||
: Thread ("DownloadTask thread"),
|
||||
fileStream (outputStreamToUse),
|
||||
stream (streamToUse),
|
||||
bufferSize (bufferSizeToUse),
|
||||
buffer (bufferSize),
|
||||
listener (listenerToUse)
|
||||
{
|
||||
jassert (fileStream != nullptr);
|
||||
jassert (stream != nullptr);
|
||||
|
||||
contentLength = stream->getTotalLength();
|
||||
httpCode = stream->getStatusCode();
|
||||
|
||||
startThread();
|
||||
}
|
||||
|
||||
~FallbackDownloadTask()
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
stream->cancel();
|
||||
waitForThreadToExit (-1);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void run() override
|
||||
{
|
||||
while (! (stream->isExhausted() || stream->isError() || threadShouldExit()))
|
||||
{
|
||||
if (listener != nullptr)
|
||||
listener->progress (this, downloaded, contentLength);
|
||||
|
||||
const int max = jmin ((int) bufferSize, contentLength < 0 ? std::numeric_limits<int>::max()
|
||||
: static_cast<int> (contentLength - downloaded));
|
||||
|
||||
const int actual = stream->read (buffer.get(), max);
|
||||
|
||||
if (actual < 0 || threadShouldExit() || stream->isError())
|
||||
break;
|
||||
|
||||
if (! fileStream->write (buffer.get(), static_cast<size_t> (actual)))
|
||||
{
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
downloaded += actual;
|
||||
|
||||
if (downloaded == contentLength)
|
||||
break;
|
||||
}
|
||||
|
||||
fileStream->flush();
|
||||
|
||||
if (threadShouldExit() || stream->isError())
|
||||
error = true;
|
||||
|
||||
if (contentLength > 0 && downloaded < contentLength)
|
||||
error = true;
|
||||
|
||||
finished = true;
|
||||
|
||||
if (listener != nullptr && ! threadShouldExit())
|
||||
listener->finished (this, ! error);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const std::unique_ptr<FileOutputStream> fileStream;
|
||||
const std::unique_ptr<WebInputStream> stream;
|
||||
const size_t bufferSize;
|
||||
HeapBlock<char> buffer;
|
||||
URL::DownloadTask::Listener* const listener;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FallbackDownloadTask)
|
||||
};
|
||||
|
||||
void URL::DownloadTask::Listener::progress (DownloadTask*, int64, int64) {}
|
||||
URL::DownloadTask::Listener::~Listener() {}
|
||||
|
||||
//==============================================================================
|
||||
URL::DownloadTask* URL::DownloadTask::createFallbackDownloader (const URL& urlToUse,
|
||||
const File& targetFileToUse,
|
||||
const String& extraHeadersToUse,
|
||||
Listener* listenerToUse,
|
||||
bool usePostRequest)
|
||||
{
|
||||
const size_t bufferSize = 0x8000;
|
||||
targetFileToUse.deleteFile();
|
||||
|
||||
std::unique_ptr<FileOutputStream> outputStream (targetFileToUse.createOutputStream (bufferSize));
|
||||
|
||||
if (outputStream != nullptr)
|
||||
{
|
||||
std::unique_ptr<WebInputStream> stream (new WebInputStream (urlToUse, usePostRequest));
|
||||
stream->withExtraHeaders (extraHeadersToUse);
|
||||
|
||||
if (stream->connect (nullptr))
|
||||
return new FallbackDownloadTask (outputStream.release(), bufferSize, stream.release(), listenerToUse);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
URL::DownloadTask::DownloadTask() {}
|
||||
URL::DownloadTask::~DownloadTask() {}
|
||||
|
||||
//==============================================================================
|
||||
URL::URL() noexcept {}
|
||||
|
||||
URL::URL (const String& u) : url (u)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
URL::URL (File localFile)
|
||||
{
|
||||
if (localFile == File())
|
||||
return;
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
bool isUncPath = localFile.getFullPathName().startsWith ("\\\\");
|
||||
#endif
|
||||
|
||||
while (! localFile.isRoot())
|
||||
{
|
||||
url = "/" + addEscapeChars (localFile.getFileName(), false) + url;
|
||||
localFile = localFile.getParentDirectory();
|
||||
}
|
||||
|
||||
url = addEscapeChars (localFile.getFileName(), false) + url;
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
if (isUncPath)
|
||||
{
|
||||
url = url.fromFirstOccurrenceOf ("/", false, false);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (! url.startsWithChar (L'/'))
|
||||
url = "/" + url;
|
||||
}
|
||||
|
||||
|
||||
url = "file://" + url;
|
||||
|
||||
jassert (isWellFormed());
|
||||
}
|
||||
|
||||
void URL::init()
|
||||
{
|
||||
auto i = url.indexOfChar ('?');
|
||||
|
||||
if (i >= 0)
|
||||
{
|
||||
do
|
||||
{
|
||||
const int nextAmp = url.indexOfChar (i + 1, '&');
|
||||
const int equalsPos = url.indexOfChar (i + 1, '=');
|
||||
|
||||
if (nextAmp < 0)
|
||||
{
|
||||
addParameter (removeEscapeChars (equalsPos < 0 ? url.substring (i + 1) : url.substring (i + 1, equalsPos)),
|
||||
equalsPos < 0 ? String() : removeEscapeChars (url.substring (equalsPos + 1)));
|
||||
}
|
||||
else if (nextAmp > 0 && equalsPos < nextAmp)
|
||||
{
|
||||
addParameter (removeEscapeChars (equalsPos < 0 ? url.substring (i + 1, nextAmp) : url.substring (i + 1, equalsPos)),
|
||||
equalsPos < 0 ? String() : removeEscapeChars (url.substring (equalsPos + 1, nextAmp)));
|
||||
}
|
||||
|
||||
i = nextAmp;
|
||||
}
|
||||
while (i >= 0);
|
||||
|
||||
url = url.upToFirstOccurrenceOf ("?", false, false);
|
||||
}
|
||||
}
|
||||
|
||||
URL::URL (const String& u, int) : url (u) {}
|
||||
|
||||
URL::URL (URL&& other)
|
||||
: url (static_cast<String&&> (other.url)),
|
||||
postData (static_cast<MemoryBlock&&> (other.postData)),
|
||||
parameterNames (static_cast<StringArray&&> (other.parameterNames)),
|
||||
parameterValues (static_cast<StringArray&&> (other.parameterValues)),
|
||||
filesToUpload (static_cast<ReferenceCountedArray<Upload>&&> (other.filesToUpload))
|
||||
#if JUCE_IOS
|
||||
, bookmark (static_cast<Bookmark::Ptr&&> (other.bookmark))
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
URL& URL::operator= (URL&& other)
|
||||
{
|
||||
url = static_cast<String&&> (other.url);
|
||||
postData = static_cast<MemoryBlock&&> (other.postData);
|
||||
parameterNames = static_cast<StringArray&&> (other.parameterNames);
|
||||
parameterValues = static_cast<StringArray&&> (other.parameterValues);
|
||||
filesToUpload = static_cast<ReferenceCountedArray<Upload>&&> (other.filesToUpload);
|
||||
#if JUCE_IOS
|
||||
bookmark = static_cast<Bookmark::Ptr&&> (other.bookmark);
|
||||
#endif
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
URL::~URL() {}
|
||||
|
||||
URL URL::createWithoutParsing (const String& u)
|
||||
{
|
||||
return URL (u, 0);
|
||||
}
|
||||
|
||||
bool URL::operator== (const URL& other) const
|
||||
{
|
||||
return url == other.url
|
||||
&& postData == other.postData
|
||||
&& parameterNames == other.parameterNames
|
||||
&& parameterValues == other.parameterValues
|
||||
&& filesToUpload == other.filesToUpload;
|
||||
}
|
||||
|
||||
bool URL::operator!= (const URL& other) const
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
namespace URLHelpers
|
||||
{
|
||||
static String getMangledParameters (const URL& url)
|
||||
{
|
||||
jassert (url.getParameterNames().size() == url.getParameterValues().size());
|
||||
String p;
|
||||
|
||||
for (int i = 0; i < url.getParameterNames().size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
p << '&';
|
||||
|
||||
auto val = url.getParameterValues()[i];
|
||||
|
||||
p << URL::addEscapeChars (url.getParameterNames()[i], true);
|
||||
|
||||
if (val.isNotEmpty())
|
||||
p << '=' << URL::addEscapeChars (val, true);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static int findEndOfScheme (const String& url)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (CharacterFunctions::isLetterOrDigit (url[i])
|
||||
|| url[i] == '+' || url[i] == '-' || url[i] == '.')
|
||||
++i;
|
||||
|
||||
return url.substring (i).startsWith ("://") ? i + 1 : 0;
|
||||
}
|
||||
|
||||
static int findStartOfNetLocation (const String& url)
|
||||
{
|
||||
int start = findEndOfScheme (url);
|
||||
|
||||
while (url[start] == '/')
|
||||
++start;
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
static int findStartOfPath (const String& url)
|
||||
{
|
||||
return url.indexOfChar (findStartOfNetLocation (url), '/') + 1;
|
||||
}
|
||||
|
||||
static void concatenatePaths (String& path, const String& suffix)
|
||||
{
|
||||
if (! path.endsWithChar ('/'))
|
||||
path << '/';
|
||||
|
||||
if (suffix.startsWithChar ('/'))
|
||||
path += suffix.substring (1);
|
||||
else
|
||||
path += suffix;
|
||||
}
|
||||
}
|
||||
|
||||
void URL::addParameter (const String& name, const String& value)
|
||||
{
|
||||
parameterNames.add (name);
|
||||
parameterValues.add (value);
|
||||
}
|
||||
|
||||
String URL::toString (const bool includeGetParameters) const
|
||||
{
|
||||
if (includeGetParameters && parameterNames.size() > 0)
|
||||
return url + "?" + URLHelpers::getMangledParameters (*this);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
bool URL::isEmpty() const noexcept
|
||||
{
|
||||
return url.isEmpty();
|
||||
}
|
||||
|
||||
bool URL::isWellFormed() const
|
||||
{
|
||||
//xxx TODO
|
||||
return url.isNotEmpty();
|
||||
}
|
||||
|
||||
String URL::getDomain() const
|
||||
{
|
||||
auto start = URLHelpers::findStartOfNetLocation (url);
|
||||
auto end1 = url.indexOfChar (start, '/');
|
||||
auto end2 = url.indexOfChar (start, ':');
|
||||
|
||||
auto end = (end1 < 0 && end2 < 0) ? std::numeric_limits<int>::max()
|
||||
: ((end1 < 0 || end2 < 0) ? jmax (end1, end2)
|
||||
: jmin (end1, end2));
|
||||
return url.substring (start, end);
|
||||
}
|
||||
|
||||
String URL::getSubPath() const
|
||||
{
|
||||
auto startOfPath = URLHelpers::findStartOfPath (url);
|
||||
|
||||
return startOfPath <= 0 ? String()
|
||||
: url.substring (startOfPath);
|
||||
}
|
||||
|
||||
String URL::getScheme() const
|
||||
{
|
||||
return url.substring (0, URLHelpers::findEndOfScheme (url) - 1);
|
||||
}
|
||||
|
||||
#ifndef JUCE_ANDROID
|
||||
bool URL::isLocalFile() const
|
||||
{
|
||||
return (getScheme() == "file");
|
||||
}
|
||||
|
||||
File URL::getLocalFile() const
|
||||
{
|
||||
return fileFromFileSchemeURL (*this);
|
||||
}
|
||||
|
||||
String URL::getFileName() const
|
||||
{
|
||||
return toString (false).fromLastOccurrenceOf ("/", false, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
File URL::fileFromFileSchemeURL (const URL& fileURL)
|
||||
{
|
||||
if (! fileURL.isLocalFile())
|
||||
{
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto path = removeEscapeChars (fileURL.getDomain()).replace ("+", "%2B");
|
||||
|
||||
#ifdef JUCE_WINDOWS
|
||||
bool isUncPath = (! fileURL.url.startsWith ("file:///"));
|
||||
#else
|
||||
path = File::getSeparatorString() + path;
|
||||
#endif
|
||||
|
||||
auto urlElements = StringArray::fromTokens (fileURL.getSubPath(), "/", "");
|
||||
|
||||
for (auto urlElement : urlElements)
|
||||
path += File::getSeparatorString() + removeEscapeChars (urlElement.replace ("+", "%2B"));
|
||||
|
||||
#ifdef JUCE_WINDOWS
|
||||
if (isUncPath)
|
||||
path = "\\\\" + path;
|
||||
#endif
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
int URL::getPort() const
|
||||
{
|
||||
auto colonPos = url.indexOfChar (URLHelpers::findStartOfNetLocation (url), ':');
|
||||
|
||||
return colonPos > 0 ? url.substring (colonPos + 1).getIntValue() : 0;
|
||||
}
|
||||
|
||||
URL URL::withNewDomainAndPath (const String& newURL) const
|
||||
{
|
||||
URL u (*this);
|
||||
u.url = newURL;
|
||||
return u;
|
||||
}
|
||||
|
||||
URL URL::withNewSubPath (const String& newPath) const
|
||||
{
|
||||
const int startOfPath = URLHelpers::findStartOfPath (url);
|
||||
|
||||
URL u (*this);
|
||||
|
||||
if (startOfPath > 0)
|
||||
u.url = url.substring (0, startOfPath);
|
||||
|
||||
URLHelpers::concatenatePaths (u.url, newPath);
|
||||
return u;
|
||||
}
|
||||
|
||||
URL URL::getChildURL (const String& subPath) const
|
||||
{
|
||||
URL u (*this);
|
||||
URLHelpers::concatenatePaths (u.url, subPath);
|
||||
return u;
|
||||
}
|
||||
|
||||
void URL::createHeadersAndPostData (String& headers, MemoryBlock& postDataToWrite) const
|
||||
{
|
||||
MemoryOutputStream data (postDataToWrite, false);
|
||||
|
||||
if (filesToUpload.size() > 0)
|
||||
{
|
||||
// (this doesn't currently support mixing custom post-data with uploads..)
|
||||
jassert (postData.getSize() == 0);
|
||||
|
||||
auto boundary = String::toHexString (Random::getSystemRandom().nextInt64());
|
||||
|
||||
headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n";
|
||||
|
||||
data << "--" << boundary;
|
||||
|
||||
for (int i = 0; i < parameterNames.size(); ++i)
|
||||
{
|
||||
data << "\r\nContent-Disposition: form-data; name=\"" << parameterNames[i]
|
||||
<< "\"\r\n\r\n" << parameterValues[i]
|
||||
<< "\r\n--" << boundary;
|
||||
}
|
||||
|
||||
for (auto* f : filesToUpload)
|
||||
{
|
||||
data << "\r\nContent-Disposition: form-data; name=\"" << f->parameterName
|
||||
<< "\"; filename=\"" << f->filename << "\"\r\n";
|
||||
|
||||
if (f->mimeType.isNotEmpty())
|
||||
data << "Content-Type: " << f->mimeType << "\r\n";
|
||||
|
||||
data << "Content-Transfer-Encoding: binary\r\n\r\n";
|
||||
|
||||
if (f->data != nullptr)
|
||||
data << *f->data;
|
||||
else
|
||||
data << f->file;
|
||||
|
||||
data << "\r\n--" << boundary;
|
||||
}
|
||||
|
||||
data << "--\r\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
data << URLHelpers::getMangledParameters (*this)
|
||||
<< postData;
|
||||
|
||||
// if the user-supplied headers didn't contain a content-type, add one now..
|
||||
if (! headers.containsIgnoreCase ("Content-Type"))
|
||||
headers << "Content-Type: application/x-www-form-urlencoded\r\n";
|
||||
|
||||
headers << "Content-length: " << (int) data.getDataSize() << "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool URL::isProbablyAWebsiteURL (const String& possibleURL)
|
||||
{
|
||||
static const char* validProtocols[] = { "http:", "ftp:", "https:" };
|
||||
|
||||
for (auto* protocol : validProtocols)
|
||||
if (possibleURL.startsWithIgnoreCase (protocol))
|
||||
return true;
|
||||
|
||||
if (possibleURL.containsChar ('@')
|
||||
|| possibleURL.containsChar (' '))
|
||||
return false;
|
||||
|
||||
const String topLevelDomain (possibleURL.upToFirstOccurrenceOf ("/", false, false)
|
||||
.fromLastOccurrenceOf (".", false, false));
|
||||
|
||||
return topLevelDomain.isNotEmpty() && topLevelDomain.length() <= 3;
|
||||
}
|
||||
|
||||
bool URL::isProbablyAnEmailAddress (const String& possibleEmailAddress)
|
||||
{
|
||||
auto atSign = possibleEmailAddress.indexOfChar ('@');
|
||||
|
||||
return atSign > 0
|
||||
&& possibleEmailAddress.lastIndexOfChar ('.') > (atSign + 1)
|
||||
&& ! possibleEmailAddress.endsWithChar ('.');
|
||||
}
|
||||
|
||||
#if JUCE_IOS
|
||||
URL::Bookmark::Bookmark (void* bookmarkToUse)
|
||||
: data (bookmarkToUse)
|
||||
{
|
||||
}
|
||||
|
||||
URL::Bookmark::~Bookmark()
|
||||
{
|
||||
[(NSData*) data release];
|
||||
}
|
||||
|
||||
void setURLBookmark (URL& u, void* bookmark)
|
||||
{
|
||||
u.bookmark = new URL::Bookmark (bookmark);
|
||||
}
|
||||
|
||||
void* getURLBookmark (URL& u)
|
||||
{
|
||||
if (u.bookmark.get() == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return u.bookmark.get()->data;
|
||||
}
|
||||
|
||||
template <typename Stream> struct iOSFileStreamWrapperFlush { static void flush (Stream*) {} };
|
||||
template <> struct iOSFileStreamWrapperFlush<FileOutputStream> { static void flush (OutputStream* o) { o->flush(); } };
|
||||
|
||||
template <typename Stream>
|
||||
class iOSFileStreamWrapper : public Stream
|
||||
{
|
||||
public:
|
||||
iOSFileStreamWrapper (URL& urlToUse)
|
||||
: Stream (getLocalFileAccess (urlToUse)),
|
||||
url (urlToUse)
|
||||
{}
|
||||
|
||||
~iOSFileStreamWrapper()
|
||||
{
|
||||
iOSFileStreamWrapperFlush<Stream>::flush (this);
|
||||
|
||||
if (NSData* bookmark = (NSData*) getURLBookmark (url))
|
||||
{
|
||||
BOOL isBookmarkStale = false;
|
||||
NSError* error = nil;
|
||||
|
||||
auto* nsURL = [NSURL URLByResolvingBookmarkData: bookmark
|
||||
options: 0
|
||||
relativeToURL: nil
|
||||
bookmarkDataIsStale: &isBookmarkStale
|
||||
error: &error];
|
||||
|
||||
if (error == nil)
|
||||
{
|
||||
if (isBookmarkStale)
|
||||
updateStaleBookmark (nsURL, url);
|
||||
|
||||
[nsURL stopAccessingSecurityScopedResource];
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* desc = [error localizedDescription];
|
||||
ignoreUnused (desc);
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
URL url;
|
||||
bool securityAccessSucceeded = false;
|
||||
|
||||
File getLocalFileAccess (URL& urlToUse)
|
||||
{
|
||||
if (NSData* bookmark = (NSData*) getURLBookmark (urlToUse))
|
||||
{
|
||||
BOOL isBookmarkStale = false;
|
||||
NSError* error = nil;
|
||||
|
||||
auto* nsURL = [NSURL URLByResolvingBookmarkData: bookmark
|
||||
options: 0
|
||||
relativeToURL: nil
|
||||
bookmarkDataIsStale: &isBookmarkStale
|
||||
error: &error];
|
||||
|
||||
if (error == nil)
|
||||
{
|
||||
securityAccessSucceeded = [nsURL startAccessingSecurityScopedResource];
|
||||
|
||||
if (isBookmarkStale)
|
||||
updateStaleBookmark (nsURL, urlToUse);
|
||||
|
||||
return urlToUse.getLocalFile();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* desc = [error localizedDescription];
|
||||
ignoreUnused (desc);
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
|
||||
return urlToUse.getLocalFile();
|
||||
}
|
||||
|
||||
void updateStaleBookmark (NSURL* nsURL, URL& juceUrl)
|
||||
{
|
||||
NSError* error = nil;
|
||||
|
||||
NSData* bookmark = [nsURL bookmarkDataWithOptions: NSURLBookmarkCreationSuitableForBookmarkFile
|
||||
includingResourceValuesForKeys: nil
|
||||
relativeToURL: nil
|
||||
error: &error];
|
||||
|
||||
if (error == nil)
|
||||
setURLBookmark (juceUrl, (void*) bookmark);
|
||||
else
|
||||
jassertfalse;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
InputStream* URL::createInputStream (const bool usePostCommand,
|
||||
OpenStreamProgressCallback* const progressCallback,
|
||||
void* const progressCallbackContext,
|
||||
String headers,
|
||||
const int timeOutMs,
|
||||
StringPairArray* const responseHeaders,
|
||||
int* statusCode,
|
||||
const int numRedirectsToFollow,
|
||||
String httpRequestCmd) const
|
||||
{
|
||||
if (isLocalFile())
|
||||
{
|
||||
#if JUCE_IOS
|
||||
// We may need to refresh the embedded bookmark.
|
||||
return new iOSFileStreamWrapper<FileInputStream> (const_cast<URL&>(*this));
|
||||
#else
|
||||
return getLocalFile().createInputStream();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<WebInputStream> wi (new WebInputStream (*this, usePostCommand));
|
||||
|
||||
struct ProgressCallbackCaller : public WebInputStream::Listener
|
||||
{
|
||||
ProgressCallbackCaller (OpenStreamProgressCallback* progressCallbackToUse, void* progressCallbackContextToUse)
|
||||
: callback (progressCallbackToUse), data (progressCallbackContextToUse)
|
||||
{}
|
||||
|
||||
bool postDataSendProgress (WebInputStream&, int bytesSent, int totalBytes) override
|
||||
{
|
||||
return callback(data, bytesSent, totalBytes);
|
||||
}
|
||||
|
||||
OpenStreamProgressCallback* const callback;
|
||||
void* const data;
|
||||
|
||||
// workaround a MSVC 2013 compiler warning
|
||||
ProgressCallbackCaller (const ProgressCallbackCaller& o) : callback (o.callback), data (o.data) { jassertfalse; }
|
||||
ProgressCallbackCaller& operator= (const ProgressCallbackCaller&) { jassertfalse; return *this; }
|
||||
};
|
||||
|
||||
std::unique_ptr<ProgressCallbackCaller> callbackCaller
|
||||
(progressCallback != nullptr ? new ProgressCallbackCaller (progressCallback, progressCallbackContext) : nullptr);
|
||||
|
||||
if (headers.isNotEmpty())
|
||||
wi->withExtraHeaders (headers);
|
||||
|
||||
if (timeOutMs != 0)
|
||||
wi->withConnectionTimeout (timeOutMs);
|
||||
|
||||
if (httpRequestCmd.isNotEmpty())
|
||||
wi->withCustomRequestCommand (httpRequestCmd);
|
||||
|
||||
wi->withNumRedirectsToFollow (numRedirectsToFollow);
|
||||
|
||||
bool success = wi->connect (callbackCaller.get());
|
||||
|
||||
if (statusCode != nullptr)
|
||||
*statusCode = wi->getStatusCode();
|
||||
|
||||
if (responseHeaders != nullptr)
|
||||
*responseHeaders = wi->getResponseHeaders();
|
||||
|
||||
if (! success || wi->isError())
|
||||
return nullptr;
|
||||
|
||||
return wi.release();
|
||||
}
|
||||
|
||||
#if JUCE_ANDROID
|
||||
OutputStream* juce_CreateContentURIOutputStream (const URL&);
|
||||
#endif
|
||||
|
||||
OutputStream* URL::createOutputStream() const
|
||||
{
|
||||
if (isLocalFile())
|
||||
{
|
||||
#if JUCE_IOS
|
||||
// We may need to refresh the embedded bookmark.
|
||||
return new iOSFileStreamWrapper<FileOutputStream> (const_cast<URL&> (*this));
|
||||
#else
|
||||
return new FileOutputStream (getLocalFile());
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JUCE_ANDROID
|
||||
return juce_CreateContentURIOutputStream (*this);
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) const
|
||||
{
|
||||
const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
|
||||
: static_cast<InputStream*> (createInputStream (usePostCommand)));
|
||||
|
||||
if (in != nullptr)
|
||||
{
|
||||
in->readIntoMemoryBlock (destData);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String URL::readEntireTextStream (bool usePostCommand) const
|
||||
{
|
||||
const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
|
||||
: static_cast<InputStream*> (createInputStream (usePostCommand)));
|
||||
|
||||
if (in != nullptr)
|
||||
return in->readEntireStreamAsString();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
XmlElement* URL::readEntireXmlStream (bool usePostCommand) const
|
||||
{
|
||||
return XmlDocument::parse (readEntireTextStream (usePostCommand));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
URL URL::withParameter (const String& parameterName,
|
||||
const String& parameterValue) const
|
||||
{
|
||||
URL u (*this);
|
||||
u.addParameter (parameterName, parameterValue);
|
||||
return u;
|
||||
}
|
||||
|
||||
URL URL::withParameters (const StringPairArray& parametersToAdd) const
|
||||
{
|
||||
URL u (*this);
|
||||
|
||||
for (int i = 0; i < parametersToAdd.size(); ++i)
|
||||
u.addParameter (parametersToAdd.getAllKeys()[i],
|
||||
parametersToAdd.getAllValues()[i]);
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
URL URL::withPOSTData (const String& newPostData) const
|
||||
{
|
||||
return withPOSTData (MemoryBlock (newPostData.toRawUTF8(), newPostData.getNumBytesAsUTF8()));
|
||||
}
|
||||
|
||||
URL URL::withPOSTData (const MemoryBlock& newPostData) const
|
||||
{
|
||||
URL u (*this);
|
||||
u.postData = newPostData;
|
||||
return u;
|
||||
}
|
||||
|
||||
URL::Upload::Upload (const String& param, const String& name,
|
||||
const String& mime, const File& f, MemoryBlock* mb)
|
||||
: parameterName (param), filename (name), mimeType (mime), file (f), data (mb)
|
||||
{
|
||||
jassert (mimeType.isNotEmpty()); // You need to supply a mime type!
|
||||
}
|
||||
|
||||
URL URL::withUpload (Upload* const f) const
|
||||
{
|
||||
URL u (*this);
|
||||
|
||||
for (int i = u.filesToUpload.size(); --i >= 0;)
|
||||
if (u.filesToUpload.getObjectPointerUnchecked(i)->parameterName == f->parameterName)
|
||||
u.filesToUpload.remove (i);
|
||||
|
||||
u.filesToUpload.add (f);
|
||||
return u;
|
||||
}
|
||||
|
||||
URL URL::withFileToUpload (const String& parameterName, const File& fileToUpload,
|
||||
const String& mimeType) const
|
||||
{
|
||||
return withUpload (new Upload (parameterName, fileToUpload.getFileName(),
|
||||
mimeType, fileToUpload, nullptr));
|
||||
}
|
||||
|
||||
URL URL::withDataToUpload (const String& parameterName, const String& filename,
|
||||
const MemoryBlock& fileContentToUpload, const String& mimeType) const
|
||||
{
|
||||
return withUpload (new Upload (parameterName, filename, mimeType, File(),
|
||||
new MemoryBlock (fileContentToUpload)));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String URL::removeEscapeChars (const String& s)
|
||||
{
|
||||
auto result = s.replaceCharacter ('+', ' ');
|
||||
|
||||
if (! result.containsChar ('%'))
|
||||
return result;
|
||||
|
||||
// We need to operate on the string as raw UTF8 chars, and then recombine them into unicode
|
||||
// after all the replacements have been made, so that multi-byte chars are handled.
|
||||
Array<char> utf8 (result.toRawUTF8(), (int) result.getNumBytesAsUTF8());
|
||||
|
||||
for (int i = 0; i < utf8.size(); ++i)
|
||||
{
|
||||
if (utf8.getUnchecked(i) == '%')
|
||||
{
|
||||
const int hexDigit1 = CharacterFunctions::getHexDigitValue ((juce_wchar) (uint8) utf8 [i + 1]);
|
||||
const int hexDigit2 = CharacterFunctions::getHexDigitValue ((juce_wchar) (uint8) utf8 [i + 2]);
|
||||
|
||||
if (hexDigit1 >= 0 && hexDigit2 >= 0)
|
||||
{
|
||||
utf8.set (i, (char) ((hexDigit1 << 4) + hexDigit2));
|
||||
utf8.removeRange (i + 1, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size());
|
||||
}
|
||||
|
||||
String URL::addEscapeChars (const String& s, bool isParameter, bool roundBracketsAreLegal)
|
||||
{
|
||||
String legalChars (isParameter ? "_-.~"
|
||||
: ",$_-.*!'");
|
||||
|
||||
if (roundBracketsAreLegal)
|
||||
legalChars += "()";
|
||||
|
||||
Array<char> utf8 (s.toRawUTF8(), (int) s.getNumBytesAsUTF8());
|
||||
|
||||
for (int i = 0; i < utf8.size(); ++i)
|
||||
{
|
||||
auto c = utf8.getUnchecked(i);
|
||||
|
||||
if (! (CharacterFunctions::isLetterOrDigit (c)
|
||||
|| legalChars.containsChar ((juce_wchar) c)))
|
||||
{
|
||||
utf8.set (i, '%');
|
||||
utf8.insert (++i, "0123456789ABCDEF" [((uint8) c) >> 4]);
|
||||
utf8.insert (++i, "0123456789ABCDEF" [c & 15]);
|
||||
}
|
||||
}
|
||||
|
||||
return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool URL::launchInDefaultBrowser() const
|
||||
{
|
||||
auto u = toString (true);
|
||||
|
||||
if (u.containsChar ('@') && ! u.containsChar (':'))
|
||||
u = "mailto:" + u;
|
||||
|
||||
return Process::openDocument (u, {});
|
||||
}
|
||||
|
||||
} // namespace juce
|
569
modules/juce_core/network/juce_URL.h
Normal file
569
modules/juce_core/network/juce_URL.h
Normal file
@ -0,0 +1,569 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class WebInputStream;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a URL and has a bunch of useful functions to manipulate it.
|
||||
|
||||
This class can be used to launch URLs in browsers, and also to create
|
||||
InputStreams that can read from remote http or ftp sources.
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API URL
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty URL. */
|
||||
URL() noexcept;
|
||||
|
||||
/** Creates a URL from a string.
|
||||
This will parse any embedded parameters after a '?' character and store them
|
||||
in the list (see getParameterNames etc). If you don't want this to happen, you
|
||||
can use createWithoutParsing().
|
||||
*/
|
||||
URL (const String& url);
|
||||
|
||||
URL (const URL&) = default;
|
||||
URL& operator= (const URL&) = default;
|
||||
|
||||
// VS2013 can't default move constructors and assignments
|
||||
URL (URL&&);
|
||||
URL& operator= (URL&&);
|
||||
|
||||
/** Creates URL referring to a local file on your disk using the file:// scheme. */
|
||||
explicit URL (File);
|
||||
|
||||
/** Destructor. */
|
||||
~URL();
|
||||
|
||||
/** Compares two URLs.
|
||||
All aspects of the URLs must be identical for them to match, including any parameters,
|
||||
upload files, etc.
|
||||
*/
|
||||
bool operator== (const URL&) const;
|
||||
bool operator!= (const URL&) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a string version of the URL.
|
||||
|
||||
If includeGetParameters is true and any parameters have been set with the
|
||||
withParameter() method, then the string will have these appended on the
|
||||
end and url-encoded.
|
||||
*/
|
||||
String toString (bool includeGetParameters) const;
|
||||
|
||||
/** Returns true if the URL is an empty string. */
|
||||
bool isEmpty() const noexcept;
|
||||
|
||||
/** True if it seems to be valid. */
|
||||
bool isWellFormed() const;
|
||||
|
||||
/** Returns just the domain part of the URL.
|
||||
E.g. for "http://www.xyz.com/foobar", this will return "www.xyz.com".
|
||||
*/
|
||||
String getDomain() const;
|
||||
|
||||
/** Returns the path part of the URL.
|
||||
E.g. for "http://www.xyz.com/foo/bar?x=1", this will return "foo/bar".
|
||||
*/
|
||||
String getSubPath() const;
|
||||
|
||||
/** Returns the scheme of the URL.
|
||||
E.g. for "http://www.xyz.com/foobar", this will return "http". (It won't
|
||||
include the colon).
|
||||
*/
|
||||
String getScheme() const;
|
||||
|
||||
/** Returns true if this URL refers to a local file. */
|
||||
bool isLocalFile() const;
|
||||
|
||||
/** Returns the file path of the local file to which this URL refers to.
|
||||
If the URL does not represent a local file URL (i.e. the URL's scheme is not 'file')
|
||||
then this method will assert.
|
||||
|
||||
This method also supports converting Android's content:// URLs to
|
||||
local file paths.
|
||||
|
||||
@see isLocalFile
|
||||
*/
|
||||
File getLocalFile() const;
|
||||
|
||||
/** Returns the file name. For all but Android's content:// scheme, it will
|
||||
simply return the last segment of the URL.
|
||||
E.g. for "http://www.xyz.com/foo/bar.txt", this will return "bar.txt".
|
||||
|
||||
For Android's content:// scheme, it will attempt to resolve the filename
|
||||
located under the URL.
|
||||
*/
|
||||
String getFileName() const;
|
||||
|
||||
/** Attempts to read a port number from the URL.
|
||||
@returns the port number, or 0 if none is explicitly specified.
|
||||
*/
|
||||
int getPort() const;
|
||||
|
||||
/** Returns a new version of this URL with a different domain and path.
|
||||
E.g. if the URL is "http://www.xyz.com/foo?x=1" and you call this with
|
||||
"abc.com/zzz", it'll return "http://abc.com/zzz?x=1".
|
||||
@see withNewSubPath
|
||||
*/
|
||||
URL withNewDomainAndPath (const String& newFullPath) const;
|
||||
|
||||
/** Returns a new version of this URL with a different sub-path.
|
||||
E.g. if the URL is "http://www.xyz.com/foo?x=1" and you call this with
|
||||
"bar", it'll return "http://www.xyz.com/bar?x=1".
|
||||
@see withNewDomainAndPath
|
||||
*/
|
||||
URL withNewSubPath (const String& newPath) const;
|
||||
|
||||
/** Returns a new URL that refers to a sub-path relative to this one.
|
||||
E.g. if the URL is "http://www.xyz.com/foo" and you call this with
|
||||
"bar", it'll return "http://www.xyz.com/foo/bar". Note that there's no way for
|
||||
this method to know whether the original URL is a file or directory, so it's
|
||||
up to you to make sure it's a directory. It also won't attempt to be smart about
|
||||
the content of the childPath string, so if this string is an absolute URL, it'll
|
||||
still just get bolted onto the end of the path.
|
||||
|
||||
@see File::getChildFile
|
||||
*/
|
||||
URL getChildURL (const String& subPath) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a copy of this URL, with a GET or POST parameter added to the end.
|
||||
|
||||
Any control characters in the value will be encoded.
|
||||
e.g. calling "withParameter ("amount", "some fish") for the url "www.fish.com"
|
||||
would produce a new url whose toString(true) method would return
|
||||
"www.fish.com?amount=some+fish".
|
||||
|
||||
@see getParameterNames, getParameterValues
|
||||
*/
|
||||
URL withParameter (const String& parameterName,
|
||||
const String& parameterValue) const;
|
||||
|
||||
/** Returns a copy of this URL, with a set of GET or POST parameters added.
|
||||
This is a convenience method, equivalent to calling withParameter for each value.
|
||||
@see withParameter
|
||||
*/
|
||||
URL withParameters (const StringPairArray& parametersToAdd) const;
|
||||
|
||||
/** Returns a copy of this URL, with a file-upload type parameter added to it.
|
||||
|
||||
When performing a POST where one of your parameters is a binary file, this
|
||||
lets you specify the file.
|
||||
|
||||
Note that the filename is stored, but the file itself won't actually be read
|
||||
until this URL is later used to create a network input stream. If you want to
|
||||
upload data from memory, use withDataToUpload().
|
||||
|
||||
@see withDataToUpload
|
||||
*/
|
||||
URL withFileToUpload (const String& parameterName,
|
||||
const File& fileToUpload,
|
||||
const String& mimeType) const;
|
||||
|
||||
/** Returns a copy of this URL, with a file-upload type parameter added to it.
|
||||
|
||||
When performing a POST where one of your parameters is a binary file, this
|
||||
lets you specify the file content.
|
||||
Note that the filename parameter should not be a full path, it's just the
|
||||
last part of the filename.
|
||||
|
||||
@see withFileToUpload
|
||||
*/
|
||||
URL withDataToUpload (const String& parameterName,
|
||||
const String& filename,
|
||||
const MemoryBlock& fileContentToUpload,
|
||||
const String& mimeType) const;
|
||||
|
||||
/** Returns an array of the names of all the URL's parameters.
|
||||
|
||||
E.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would
|
||||
contain two items: "type" and "amount".
|
||||
|
||||
You can call getParameterValues() to get the corresponding value of each
|
||||
parameter. Note that the list can contain multiple parameters with the same name.
|
||||
|
||||
@see getParameterValues, withParameter
|
||||
*/
|
||||
const StringArray& getParameterNames() const noexcept { return parameterNames; }
|
||||
|
||||
/** Returns an array of the values of all the URL's parameters.
|
||||
|
||||
E.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would
|
||||
contain two items: "haddock" and "some fish".
|
||||
|
||||
The values returned will have been cleaned up to remove any escape characters.
|
||||
|
||||
You can call getParameterNames() to get the corresponding name of each
|
||||
parameter. Note that the list can contain multiple parameters with the same name.
|
||||
|
||||
@see getParameterNames, withParameter
|
||||
*/
|
||||
const StringArray& getParameterValues() const noexcept { return parameterValues; }
|
||||
|
||||
/** Returns a copy of this URL, with a block of data to send as the POST data.
|
||||
|
||||
If you're setting the POST data, be careful not to have any parameters set
|
||||
as well, otherwise it'll all get thrown in together, and might not have the
|
||||
desired effect.
|
||||
|
||||
If the URL already contains some POST data, this will replace it, rather
|
||||
than being appended to it.
|
||||
|
||||
This data will only be used if you specify a post operation when you call
|
||||
createInputStream().
|
||||
*/
|
||||
URL withPOSTData (const String& postData) const;
|
||||
|
||||
/** Returns a copy of this URL, with a block of data to send as the POST data.
|
||||
|
||||
If you're setting the POST data, be careful not to have any parameters set
|
||||
as well, otherwise it'll all get thrown in together, and might not have the
|
||||
desired effect.
|
||||
|
||||
If the URL already contains some POST data, this will replace it, rather
|
||||
than being appended to it.
|
||||
|
||||
This data will only be used if you specify a post operation when you call
|
||||
createInputStream().
|
||||
*/
|
||||
URL withPOSTData (const MemoryBlock& postData) const;
|
||||
|
||||
/** Returns the data that was set using withPOSTData(). */
|
||||
String getPostData() const noexcept { return postData.toString(); }
|
||||
|
||||
/** Returns the data that was set using withPOSTData() as MemoryBlock. */
|
||||
const MemoryBlock& getPostDataAsMemoryBlock() const noexcept { return postData; }
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to launch the system's default browser to open the URL.
|
||||
Returns true if this seems to have worked.
|
||||
*/
|
||||
bool launchInDefaultBrowser() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Takes a guess as to whether a string might be a valid website address.
|
||||
This isn't foolproof!
|
||||
*/
|
||||
static bool isProbablyAWebsiteURL (const String& possibleURL);
|
||||
|
||||
/** Takes a guess as to whether a string might be a valid email address.
|
||||
This isn't foolproof!
|
||||
*/
|
||||
static bool isProbablyAnEmailAddress (const String& possibleEmailAddress);
|
||||
|
||||
//==============================================================================
|
||||
/** This callback function can be used by the createInputStream() method.
|
||||
|
||||
It allows your app to receive progress updates during a lengthy POST operation. If you
|
||||
want to continue the operation, this should return true, or false to abort.
|
||||
*/
|
||||
typedef bool (OpenStreamProgressCallback) (void* context, int bytesSent, int totalBytes);
|
||||
|
||||
/** Attempts to open a stream that can read from this URL.
|
||||
|
||||
Note that this method will block until the first byte of data has been received or an
|
||||
error has occurred.
|
||||
|
||||
Note that on some platforms (Android, for example) it's not permitted to do any network
|
||||
action from the message thread, so you must only call it from a background thread.
|
||||
|
||||
Unless the URL represents a local file, this method returns an instance of a
|
||||
WebInputStream. You can use dynamic_cast to cast the return value to a WebInputStream
|
||||
which allows you more fine-grained control of the transfer process.
|
||||
|
||||
If the URL represents a local file, then this method simply returns a FileInputStream.
|
||||
|
||||
@param doPostLikeRequest if true, the parameters added to this class will be transferred
|
||||
via the HTTP headers which is typical for POST requests. Otherwise
|
||||
the parameters will be added to the URL address. Additionally,
|
||||
if the parameter httpRequestCmd is not specified (or empty) then this
|
||||
parameter will determine which HTTP request command will be used
|
||||
(POST or GET).
|
||||
@param progressCallback if this is not a nullptr, it lets you supply a callback function
|
||||
to keep track of the operation's progress. This can be useful
|
||||
for lengthy POST operations, so that you can provide user feedback.
|
||||
@param progressCallbackContext if a callback is specified, this value will be passed to
|
||||
the function
|
||||
@param extraHeaders if not empty, this string is appended onto the headers that
|
||||
are used for the request. It must therefore be a valid set of HTML
|
||||
header directives, separated by newlines.
|
||||
@param connectionTimeOutMs if 0, this will use whatever default setting the OS chooses. If
|
||||
a negative number, it will be infinite. Otherwise it specifies a
|
||||
time in milliseconds.
|
||||
@param responseHeaders if this is non-null, all the (key, value) pairs received as headers
|
||||
in the response will be stored in this array
|
||||
@param statusCode if this is non-null, it will get set to the http status code, if one
|
||||
is known, or 0 if a code isn't available
|
||||
@param numRedirectsToFollow specifies the number of redirects that will be followed before
|
||||
returning a response (ignored for Android which follows up to 5 redirects)
|
||||
@param httpRequestCmd Specify which HTTP Request to use. If this is empty, then doPostRequest
|
||||
will determine the HTTP request.
|
||||
@returns an input stream that the caller must delete, or a null pointer if there was an
|
||||
error trying to open it.
|
||||
*/
|
||||
InputStream* createInputStream (bool doPostLikeRequest,
|
||||
OpenStreamProgressCallback* progressCallback = nullptr,
|
||||
void* progressCallbackContext = nullptr,
|
||||
String extraHeaders = String(),
|
||||
int connectionTimeOutMs = 0,
|
||||
StringPairArray* responseHeaders = nullptr,
|
||||
int* statusCode = nullptr,
|
||||
int numRedirectsToFollow = 5,
|
||||
String httpRequestCmd = String()) const;
|
||||
|
||||
/** Attempts to open an output stream to a URL for writing
|
||||
|
||||
This method can only be used for certain scheme types such as local files
|
||||
and content:// URIs on Android.
|
||||
*/
|
||||
OutputStream* createOutputStream() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a download task.
|
||||
Returned by downloadToFile to allow querying and controling the download task.
|
||||
*/
|
||||
class DownloadTask
|
||||
{
|
||||
public:
|
||||
/** Used to receive callbacks for download progress */
|
||||
struct Listener
|
||||
{
|
||||
virtual ~Listener();
|
||||
|
||||
/** Called when the download has finished. Be aware that this callback may
|
||||
come on an arbitrary thread. */
|
||||
virtual void finished (DownloadTask* task, bool success) = 0;
|
||||
|
||||
/** Called periodically by the OS to indicate download progress.
|
||||
Beware that this callback may come on an arbitrary thread.
|
||||
*/
|
||||
virtual void progress (DownloadTask* task, int64 bytesDownloaded, int64 totalLength);
|
||||
};
|
||||
|
||||
|
||||
/** Releases the resources of the download task, unregisters the listener
|
||||
and cancels the download if necessary. */
|
||||
virtual ~DownloadTask();
|
||||
|
||||
/** Returns the total length of the download task. This may return -1 if the length
|
||||
was not returned by the server. */
|
||||
int64 getTotalLength() const { return contentLength; }
|
||||
|
||||
/** Returns the number of bytes that have been downloaded so far. */
|
||||
int64 getLengthDownloaded() const { return downloaded; }
|
||||
|
||||
/** Returns true if the download finished or there was an error. */
|
||||
bool isFinished() const { return finished; }
|
||||
|
||||
/** Returns the status code of the server's response.
|
||||
This will only be valid after the download has finished.
|
||||
@see isFinished
|
||||
*/
|
||||
int statusCode() const { return httpCode; }
|
||||
|
||||
/** Returns true if there was an error. */
|
||||
inline bool hadError() const { return error; }
|
||||
|
||||
protected:
|
||||
int64 contentLength = -1, downloaded = 0;
|
||||
bool finished = false, error = false;
|
||||
int httpCode = -1;
|
||||
|
||||
DownloadTask();
|
||||
|
||||
private:
|
||||
friend class URL;
|
||||
static DownloadTask* createFallbackDownloader (const URL&, const File&, const String&, Listener*, bool);
|
||||
|
||||
public:
|
||||
#if JUCE_IOS
|
||||
/** internal **/
|
||||
static void juce_iosURLSessionNotify (const String&);
|
||||
#endif
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadTask)
|
||||
};
|
||||
|
||||
/** Download the URL to a file.
|
||||
|
||||
This method attempts to download the URL to a given file location.
|
||||
|
||||
Using this method to download files on mobile is less flexible but more reliable
|
||||
than using createInputStream or WebInputStreams as it will attempt to download the file
|
||||
using a native OS background network task. Such tasks automatically deal with
|
||||
network re-connections and continuing your download while your app is suspended.
|
||||
*/
|
||||
DownloadTask* downloadToFile (const File& targetLocation,
|
||||
String extraHeaders = String(),
|
||||
DownloadTask::Listener* listener = nullptr,
|
||||
bool usePostCommand = false);
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to download the entire contents of this URL into a binary data block.
|
||||
|
||||
If it succeeds, this will return true and append the data it read onto the end
|
||||
of the memory block.
|
||||
|
||||
Note that on some platforms (Android, for example) it's not permitted to do any network
|
||||
action from the message thread, so you must only call it from a background thread.
|
||||
|
||||
@param destData the memory block to append the new data to
|
||||
@param usePostCommand whether to use a POST command to get the data (uses
|
||||
a GET command if this is false)
|
||||
@see readEntireTextStream, readEntireXmlStream
|
||||
*/
|
||||
bool readEntireBinaryStream (MemoryBlock& destData,
|
||||
bool usePostCommand = false) const;
|
||||
|
||||
/** Tries to download the entire contents of this URL as a string.
|
||||
|
||||
If it fails, this will return an empty string, otherwise it will return the
|
||||
contents of the downloaded file. If you need to distinguish between a read
|
||||
operation that fails and one that returns an empty string, you'll need to use
|
||||
a different method, such as readEntireBinaryStream().
|
||||
|
||||
Note that on some platforms (Android, for example) it's not permitted to do any network
|
||||
action from the message thread, so you must only call it from a background thread.
|
||||
|
||||
@param usePostCommand whether to use a POST command to get the data (uses
|
||||
a GET command if this is false)
|
||||
@see readEntireBinaryStream, readEntireXmlStream
|
||||
*/
|
||||
String readEntireTextStream (bool usePostCommand = false) const;
|
||||
|
||||
/** Tries to download the entire contents of this URL and parse it as XML.
|
||||
|
||||
If it fails, or if the text that it reads can't be parsed as XML, this will
|
||||
return nullptr.
|
||||
|
||||
When it returns a valid XmlElement object, the caller is responsibile for deleting
|
||||
this object when no longer needed.
|
||||
|
||||
Note that on some platforms (Android, for example) it's not permitted to do any network
|
||||
action from the message thread, so you must only call it from a background thread.
|
||||
|
||||
@param usePostCommand whether to use a POST command to get the data (uses
|
||||
a GET command if this is false)
|
||||
|
||||
@see readEntireBinaryStream, readEntireTextStream
|
||||
*/
|
||||
XmlElement* readEntireXmlStream (bool usePostCommand = false) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Adds escape sequences to a string to encode any characters that aren't
|
||||
legal in a URL.
|
||||
|
||||
E.g. any spaces will be replaced with "%20".
|
||||
|
||||
This is the opposite of removeEscapeChars().
|
||||
|
||||
@param stringToAddEscapeCharsTo The string to escape.
|
||||
@param isParameter If true then the string is going to be
|
||||
used as a parameter, so it also encodes
|
||||
'$' and ',' (which would otherwise be
|
||||
legal in a URL.
|
||||
@param roundBracketsAreLegal Technically round brackets are ok in URLs,
|
||||
however, some servers (like AWS) also want
|
||||
round brackets to be escaped.
|
||||
|
||||
@see removeEscapeChars
|
||||
*/
|
||||
static String addEscapeChars (const String& stringToAddEscapeCharsTo,
|
||||
bool isParameter,
|
||||
bool roundBracketsAreLegal = true);
|
||||
|
||||
/** Replaces any escape character sequences in a string with their original
|
||||
character codes.
|
||||
|
||||
E.g. any instances of "%20" will be replaced by a space.
|
||||
|
||||
This is the opposite of addEscapeChars().
|
||||
|
||||
@see addEscapeChars
|
||||
*/
|
||||
static String removeEscapeChars (const String& stringToRemoveEscapeCharsFrom);
|
||||
|
||||
/** Returns a URL without attempting to remove any embedded parameters from the string.
|
||||
This may be necessary if you need to create a request that involves both POST
|
||||
parameters and parameters which are embedded in the URL address itself.
|
||||
*/
|
||||
static URL createWithoutParsing (const String& url);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class WebInputStream;
|
||||
|
||||
String url;
|
||||
MemoryBlock postData;
|
||||
StringArray parameterNames, parameterValues;
|
||||
|
||||
static File fileFromFileSchemeURL (const URL&);
|
||||
|
||||
struct Upload : public ReferenceCountedObject
|
||||
{
|
||||
Upload (const String&, const String&, const String&, const File&, MemoryBlock*);
|
||||
String parameterName, filename, mimeType;
|
||||
File file;
|
||||
std::unique_ptr<MemoryBlock> data;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Upload)
|
||||
};
|
||||
|
||||
friend struct ContainerDeletePolicy<Upload>;
|
||||
ReferenceCountedArray<Upload> filesToUpload;
|
||||
|
||||
#if JUCE_IOS
|
||||
struct Bookmark : public ReferenceCountedObject
|
||||
{
|
||||
using Ptr = ReferenceCountedObjectPtr<Bookmark>;
|
||||
|
||||
Bookmark (void*);
|
||||
~Bookmark();
|
||||
|
||||
void* data;
|
||||
};
|
||||
|
||||
Bookmark::Ptr bookmark;
|
||||
|
||||
friend void setURLBookmark (URL&, void*);
|
||||
friend void* getURLBookmark (URL&);
|
||||
#endif
|
||||
|
||||
URL (const String&, int);
|
||||
void init();
|
||||
void addParameter (const String&, const String&);
|
||||
void createHeadersAndPostData (String&, MemoryBlock&) const;
|
||||
URL withUpload (Upload*) const;
|
||||
|
||||
JUCE_LEAK_DETECTOR (URL)
|
||||
};
|
||||
|
||||
} // namespace juce
|
87
modules/juce_core/network/juce_WebInputStream.cpp
Normal file
87
modules/juce_core/network/juce_WebInputStream.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
WebInputStream::WebInputStream (const URL& url, const bool usePost)
|
||||
: pimpl (new Pimpl (*this, url, usePost)), hasCalledConnect (false)
|
||||
{
|
||||
}
|
||||
|
||||
WebInputStream::~WebInputStream()
|
||||
{
|
||||
delete pimpl;
|
||||
}
|
||||
|
||||
WebInputStream& WebInputStream::withExtraHeaders (const String& extra) { pimpl->withExtraHeaders (extra); return *this; }
|
||||
WebInputStream& WebInputStream::withCustomRequestCommand (const String& cmd) { pimpl->withCustomRequestCommand(cmd); return *this; }
|
||||
WebInputStream& WebInputStream::withConnectionTimeout (int t) { pimpl->withConnectionTimeout (t); return *this; }
|
||||
WebInputStream& WebInputStream::withNumRedirectsToFollow (int num) { pimpl->withNumRedirectsToFollow (num); return *this; }
|
||||
StringPairArray WebInputStream::getRequestHeaders() const { return pimpl->getRequestHeaders(); }
|
||||
StringPairArray WebInputStream::getResponseHeaders() { connect (nullptr); return pimpl->getResponseHeaders(); }
|
||||
bool WebInputStream::isError() const { return pimpl->isError(); }
|
||||
void WebInputStream::cancel() { pimpl->cancel(); }
|
||||
bool WebInputStream::isExhausted() { return pimpl->isExhausted(); }
|
||||
int64 WebInputStream::getPosition() { return pimpl->getPosition(); }
|
||||
int64 WebInputStream::getTotalLength() { connect (nullptr); return pimpl->getTotalLength(); }
|
||||
int WebInputStream::read (void* buffer, int bytes) { connect (nullptr); return pimpl->read (buffer, bytes); }
|
||||
bool WebInputStream::setPosition (int64 pos) { return pimpl->setPosition (pos); }
|
||||
int WebInputStream::getStatusCode() { connect (nullptr); return pimpl->getStatusCode(); }
|
||||
|
||||
bool WebInputStream::connect (Listener* listener)
|
||||
{
|
||||
if (hasCalledConnect)
|
||||
return ! isError();
|
||||
|
||||
hasCalledConnect = true;
|
||||
return pimpl->connect (listener);
|
||||
}
|
||||
|
||||
StringPairArray WebInputStream::parseHttpHeaders (const String& headerData)
|
||||
{
|
||||
StringPairArray headerPairs;
|
||||
StringArray headerLines = StringArray::fromLines (headerData);
|
||||
|
||||
// ignore the first line as this is the status line
|
||||
for (int i = 1; i < headerLines.size(); ++i)
|
||||
{
|
||||
const String& headersEntry = headerLines[i];
|
||||
|
||||
if (headersEntry.isNotEmpty())
|
||||
{
|
||||
const String key (headersEntry.upToFirstOccurrenceOf (": ", false, false));
|
||||
const String value (headersEntry.fromFirstOccurrenceOf (": ", false, false));
|
||||
const String previousValue (headerPairs [key]);
|
||||
headerPairs.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value));
|
||||
}
|
||||
}
|
||||
|
||||
return headerPairs;
|
||||
}
|
||||
|
||||
void WebInputStream::createHeadersAndPostData (const URL& aURL, String& headers, MemoryBlock& data)
|
||||
{
|
||||
aURL.createHeadersAndPostData (headers, data);
|
||||
}
|
||||
|
||||
} // namespace juce
|
216
modules/juce_core/network/juce_WebInputStream.h
Normal file
216
modules/juce_core/network/juce_WebInputStream.h
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An InputStream which can be used to read from a given url.
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
class JUCE_API WebInputStream : public InputStream
|
||||
{
|
||||
public:
|
||||
/** Used to receive callbacks for data send progress */
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
virtual ~Listener() {}
|
||||
|
||||
virtual bool postDataSendProgress (WebInputStream& /*request*/, int /*bytesSent*/, int /*totalBytes*/) { return true; }
|
||||
};
|
||||
|
||||
/** Creates a new WebInputstream which can be used to read from a url.
|
||||
|
||||
@param url The url that should be retrieved. This parameter may also contain
|
||||
post data and/or parameters.
|
||||
@param usePost Specifies whether a GET or a POST command should be used. This
|
||||
parameter will also influence the way parameters are encoded.
|
||||
*/
|
||||
WebInputStream (const URL& url, const bool usePost);
|
||||
|
||||
~WebInputStream();
|
||||
|
||||
|
||||
/** Add extra headers to http request
|
||||
|
||||
Returns a reference to itself so that several methods can be chained.
|
||||
|
||||
@param extraHeaders this string is appended onto the headers that are used for
|
||||
the request. It must therefore be a valid set of HTML
|
||||
header directives, separated by newlines.
|
||||
*/
|
||||
WebInputStream& withExtraHeaders (const String& extraHeaders);
|
||||
|
||||
/** Override the http command that is sent
|
||||
|
||||
Returns a reference to itself so that several methods can be chained.
|
||||
|
||||
Note that this command will not change the way parameters are sent. This
|
||||
must be specified in the constructor.
|
||||
|
||||
@param customRequestCommand this string is the custom http request command such
|
||||
as POST or GET.
|
||||
*/
|
||||
WebInputStream& withCustomRequestCommand (const String& customRequestCommand);
|
||||
|
||||
/** Specify the connection time-out
|
||||
|
||||
Returns a reference to itself so that several methods can be chained.
|
||||
|
||||
@param timeoutInMs the number of milliseconds to wait until the connection
|
||||
request is aborted.
|
||||
*/
|
||||
WebInputStream& withConnectionTimeout (int timeoutInMs);
|
||||
|
||||
/** Specify the number of redirects to be followed
|
||||
|
||||
Returns a reference to itself so that several methods can be chained.
|
||||
|
||||
@param numRedirects specifies the number of redirects that will be followed
|
||||
before returning a response (ignored for Android which
|
||||
follows up to 5 redirects)
|
||||
*/
|
||||
WebInputStream& withNumRedirectsToFollow (int numRedirects);
|
||||
|
||||
/** Returns a string array pair of the request headers */
|
||||
StringPairArray getRequestHeaders() const;
|
||||
|
||||
/** Returns a string array pair of response headers
|
||||
|
||||
If getResponseHeaders is called without an established connection, then
|
||||
getResponseHeaders will call connect internally and block until connect
|
||||
returns - either due to a succesful connection or a connection
|
||||
error.
|
||||
|
||||
@see connect
|
||||
*/
|
||||
StringPairArray getResponseHeaders();
|
||||
|
||||
/** Returns the status code returned by the http server
|
||||
|
||||
If getStatusCode is called without an established connection, then
|
||||
getStatusCode will call connect internally and block until connect
|
||||
returns - either due to a succesful connection or a connection
|
||||
error.
|
||||
|
||||
@see connect
|
||||
*/
|
||||
int getStatusCode();
|
||||
|
||||
/** Wait until the first byte is ready for reading
|
||||
|
||||
This method will attempt to connect to the url given in the constructor
|
||||
and block until the status code and all response headers have been received or
|
||||
an error has occurred.
|
||||
|
||||
Note that most methods will call connect internally if they are called without
|
||||
an established connection. Therefore, it is not necessary to explicitly
|
||||
call connect unless you would like to use a custom listener.
|
||||
|
||||
After a successful call to connect, getResponseHeaders, getTotalLength and
|
||||
getStatusCode will all be non-blocking.
|
||||
|
||||
@param listener A listener to receive progress callbacks on the status
|
||||
of a POST data upload.
|
||||
|
||||
@see getResponseHeaders, getTotalLength, getStatusCode
|
||||
*/
|
||||
bool connect (Listener* listener);
|
||||
|
||||
/** Returns true if there was an error during the connection attempt. */
|
||||
bool isError() const;
|
||||
|
||||
/** Will cancel a blocking read and prevent any subsequent connection attempts. */
|
||||
void cancel();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the total number of bytes available for reading in this stream.
|
||||
|
||||
Note that this is the number of bytes available from the start of the
|
||||
stream, not from the current position.
|
||||
|
||||
If getTotalLength is called without an established connection, then
|
||||
getTotalLength will call connect internally and block until connect
|
||||
returns - either due to a succesful connection or a connection
|
||||
error.
|
||||
|
||||
If the size of the stream isn't actually known, this will return -1.
|
||||
*/
|
||||
int64 getTotalLength() override;
|
||||
|
||||
/** Reads some data from the stream into a memory buffer.
|
||||
|
||||
This method will block until the bytesToRead bytes are available.
|
||||
|
||||
This method calls connect internally if the connection hasn't already
|
||||
been established.
|
||||
|
||||
@param destBuffer the destination buffer for the data. This must not be null.
|
||||
@param maxBytesToRead the maximum number of bytes to read - make sure the
|
||||
memory block passed in is big enough to contain this
|
||||
many bytes. This value must not be negative.
|
||||
|
||||
@returns the actual number of bytes that were read, which may be less than
|
||||
maxBytesToRead if the stream is exhausted before it gets that far
|
||||
*/
|
||||
int read (void* destBuffer, int maxBytesToRead) override;
|
||||
|
||||
/** Returns true if the stream has no more data to read. */
|
||||
bool isExhausted() override;
|
||||
|
||||
/** Returns the offset of the next byte that will be read from the stream.
|
||||
@see setPosition
|
||||
*/
|
||||
int64 getPosition() override;
|
||||
|
||||
/** Tries to move the current read position of the stream.
|
||||
|
||||
The position is an absolute number of bytes from the stream's start.
|
||||
|
||||
For a WebInputStream, this method will fail if wantedPos is smaller
|
||||
than the curent position. If wantedPos is greater than the current
|
||||
position, then calling setPosition is the same as calling read, i.e.
|
||||
the skipped data will still be downloaded, although skipped bytes will
|
||||
be discarded immedietely.
|
||||
|
||||
@returns true if the stream manages to reposition itself correctly
|
||||
@see getPosition
|
||||
*/
|
||||
bool setPosition (int64 wantedPos) override;
|
||||
|
||||
private:
|
||||
static void createHeadersAndPostData (const URL&, String&, MemoryBlock&);
|
||||
static StringPairArray parseHttpHeaders (const String& headerData);
|
||||
|
||||
class Pimpl;
|
||||
friend class Pimpl;
|
||||
|
||||
Pimpl* const pimpl;
|
||||
bool hasCalledConnect;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user