From bfcfdeec64978189d7dec057de9465732e464500 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Mon, 15 Jun 2015 14:51:11 +0200 Subject: [PATCH 01/14] shell_i: Use an NSConnection instead of a local socket #2340 This prepares the switch to the official FinderSync API on Yosemite which requires the extension to run in a sandbox. This complicates the usage of a local socket to communicate with a non-sandboxed GUI client. An NSConnection is easier to use in this case, which we can use as long as the server name (i.e. Mach port registered name) is prefixed with the code signing Team Identifier. A placeholder server implementation is also added to the client's SocketApi which basically reproduces the interface of a QLocalSocket. Most of the references to individual sockets we're only using QIODevice methods so the type was simply reduced. A typedef to replace the QLocalServer was the only other part needed. --- .../MacOSX/OwnCloudFinder/ContentManager.m | 4 +- .../MacOSX/OwnCloudFinder/GCDAsyncSocket.h | 1094 --- .../MacOSX/OwnCloudFinder/GCDAsyncSocket.m | 7952 ----------------- .../OwnCloudFinder.xcodeproj/project.pbxproj | 29 +- .../MacOSX/OwnCloudFinder/RequestManager.h | 10 +- .../MacOSX/OwnCloudFinder/RequestManager.m | 232 +- .../MacOSX/common/SyncClientProxy.h | 44 + .../MacOSX/common/SyncClientProxy.m | 151 + src/gui/CMakeLists.txt | 1 + src/gui/socketapi.cpp | 42 +- src/gui/socketapi.h | 30 +- src/gui/socketapisocket_mac.h | 69 + src/gui/socketapisocket_mac.mm | 225 + 13 files changed, 584 insertions(+), 9299 deletions(-) delete mode 100644 shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.h delete mode 100644 shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.m create mode 100644 shell_integration/MacOSX/common/SyncClientProxy.h create mode 100644 shell_integration/MacOSX/common/SyncClientProxy.m create mode 100644 src/gui/socketapisocket_mac.h create mode 100644 src/gui/socketapisocket_mac.mm diff --git a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m index 527320573..94d90efec 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m +++ b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m @@ -149,11 +149,11 @@ static ContentManager* sharedInstance = nil; // NSLog(@"XXXXXXX Asking for icon for path %@ = %d",normalizedPath, [result intValue]); if( result == nil ) { - // start the async call - [[RequestManager sharedInstance] askForIcon:normalizedPath isDirectory:isDir]; result = [NSNumber numberWithInt:0]; // Set 0 into the cache, meaning "don't have an icon, but already requested it" [_fileNamesCache setObject:result forKey:normalizedPath]; + // start the async call + [[RequestManager sharedInstance] askForIcon:normalizedPath isDirectory:isDir]; } if ([result intValue] == 0) { // Show the old state while we wait for the new one diff --git a/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.h b/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.h deleted file mode 100644 index 0d62bc251..000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.h +++ /dev/null @@ -1,1094 +0,0 @@ -// -// GCDAsyncSocket.h -// -// This class is in the public domain. -// Originally created by Robbie Hanson in Q3 2010. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#import -#import -#import -#import - -@class GCDAsyncReadPacket; -@class GCDAsyncWritePacket; -@class GCDAsyncSocketPreBuffer; - -#if TARGET_OS_IPHONE - - // Compiling for iOS - - #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 // iOS 5.0 supported - - #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000 // iOS 5.0 supported and required - - #define IS_SECURE_TRANSPORT_AVAILABLE YES - #define SECURE_TRANSPORT_MAYBE_AVAILABLE 1 - #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 0 - - #else // iOS 5.0 supported but not required - - #ifndef NSFoundationVersionNumber_iPhoneOS_5_0 - #define NSFoundationVersionNumber_iPhoneOS_5_0 881.00 - #endif - - #define IS_SECURE_TRANSPORT_AVAILABLE (NSFoundationVersionNumber >= NSFoundationVersionNumber_iPhoneOS_5_0) - #define SECURE_TRANSPORT_MAYBE_AVAILABLE 1 - #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 1 - - #endif - - #else // iOS 5.0 not supported - - #define IS_SECURE_TRANSPORT_AVAILABLE NO - #define SECURE_TRANSPORT_MAYBE_AVAILABLE 0 - #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 1 - - #endif - -#else - - // Compiling for Mac OS X - - #define IS_SECURE_TRANSPORT_AVAILABLE YES - #define SECURE_TRANSPORT_MAYBE_AVAILABLE 1 - #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 0 - -#endif - -extern NSString *const GCDAsyncSocketException; -extern NSString *const GCDAsyncSocketErrorDomain; - -extern NSString *const GCDAsyncSocketQueueName; -extern NSString *const GCDAsyncSocketThreadName; - -#if SECURE_TRANSPORT_MAYBE_AVAILABLE -extern NSString *const GCDAsyncSocketSSLCipherSuites; -#if TARGET_OS_IPHONE -extern NSString *const GCDAsyncSocketSSLProtocolVersionMin; -extern NSString *const GCDAsyncSocketSSLProtocolVersionMax; -#else -extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters; -#endif -#endif - -enum GCDAsyncSocketError -{ - GCDAsyncSocketNoError = 0, // Never used - GCDAsyncSocketBadConfigError, // Invalid configuration - GCDAsyncSocketBadParamError, // Invalid parameter was passed - GCDAsyncSocketConnectTimeoutError, // A connect operation timed out - GCDAsyncSocketReadTimeoutError, // A read operation timed out - GCDAsyncSocketWriteTimeoutError, // A write operation timed out - GCDAsyncSocketReadMaxedOutError, // Reached set maxLength without completing - GCDAsyncSocketClosedError, // The remote peer closed the connection - GCDAsyncSocketOtherError, // Description provided in userInfo -}; -typedef enum GCDAsyncSocketError GCDAsyncSocketError; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface GCDAsyncSocket : NSObject - -/** - * GCDAsyncSocket uses the standard delegate paradigm, - * but executes all delegate callbacks on a given delegate dispatch queue. - * This allows for maximum concurrency, while at the same time providing easy thread safety. - * - * You MUST set a delegate AND delegate dispatch queue before attempting to - * use the socket, or you will get an error. - * - * The socket queue is optional. - * If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue. - * If you choose to provide a socket queue, the socket queue must not be a concurrent queue. - * If you choose to provide a socket queue, and the socket queue has a configured target queue, - * then please see the discussion for the method markSocketQueueTargetQueue. - * - * The delegate queue and socket queue can optionally be the same. -**/ -- (id)init; -- (id)initWithSocketQueue:(dispatch_queue_t)sq; -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq; -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq; - -#pragma mark Configuration - -- (id)delegate; -- (void)setDelegate:(id)delegate; -- (void)synchronouslySetDelegate:(id)delegate; - -- (dispatch_queue_t)delegateQueue; -- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)delegateQueue; - -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr; -- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; - -/** - * By default, both IPv4 and IPv6 are enabled. - * - * For accepting incoming connections, this means GCDAsyncSocket automatically supports both protocols, - * and can simulataneously accept incoming connections on either protocol. - * - * For outgoing connections, this means GCDAsyncSocket can connect to remote hosts running either protocol. - * If a DNS lookup returns only IPv4 results, GCDAsyncSocket will automatically use IPv4. - * If a DNS lookup returns only IPv6 results, GCDAsyncSocket will automatically use IPv6. - * If a DNS lookup returns both IPv4 and IPv6 results, the preferred protocol will be chosen. - * By default, the preferred protocol is IPv4, but may be configured as desired. -**/ -- (BOOL)isIPv4Enabled; -- (void)setIPv4Enabled:(BOOL)flag; - -- (BOOL)isIPv6Enabled; -- (void)setIPv6Enabled:(BOOL)flag; - -- (BOOL)isIPv4PreferredOverIPv6; -- (void)setPreferIPv4OverIPv6:(BOOL)flag; - -/** - * User data allows you to associate arbitrary information with the socket. - * This data is not used internally by socket in any way. -**/ -- (id)userData; -- (void)setUserData:(id)arbitraryUserData; - -#pragma mark Accepting - -/** - * Tells the socket to begin listening and accepting connections on the given port. - * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it, - * and the socket:didAcceptNewSocket: delegate method will be invoked. - * - * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) -**/ -- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr; - -/** - * This method is the same as acceptOnPort:error: with the - * additional option of specifying which interface to listen on. - * - * For example, you could specify that the socket should only accept connections over ethernet, - * and not other interfaces such as wifi. - * - * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34"). - * You may also use the special strings "localhost" or "loopback" to specify that - * the socket only accept connections from the local machine. - * - * You can see the list of interfaces via the command line utility "ifconfig", - * or programmatically via the getifaddrs() function. - * - * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method. -**/ -- (BOOL)acceptOnInterface:(NSString *)interface port:(uint16_t)port error:(NSError **)errPtr; - -/** - * Tells the socket to begin listening and accepting connections on the unix domain at the given url. - * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it, - * and the socket:didAcceptNewSocket: delegate method will be invoked. - * - * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) - **/ -- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr; - -#pragma mark Connecting - -/** - * Connects to the given host and port. - * - * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: - * and uses the default interface, and no timeout. -**/ -- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr; - -/** - * Connects to the given host and port with an optional timeout. - * - * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: and uses the default interface. -**/ -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -/** - * Connects to the given host & port, via the optional interface, with an optional timeout. - * - * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). - * The host may also be the special strings "localhost" or "loopback" to specify connecting - * to a service on the local machine. - * - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * The interface may also be used to specify the local port (see below). - * - * To not time out use a negative time interval. - * - * This method will return NO if an error is detected, and set the error pointer (if one was given). - * Possible errors would be a nil host, invalid interface, or socket is already connected. - * - * If no errors are detected, this method will start a background connect operation and immediately return YES. - * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable. - * - * Since this class supports queued reads and writes, you can immediately start reading and/or writing. - * All read/write operations will be queued, and upon socket connection, - * the operations will be dequeued and processed in order. - * - * The interface may optionally contain a port number at the end of the string, separated by a colon. - * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end) - * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424". - * To specify only local port: ":8082". - * Please note this is an advanced feature, and is somewhat hidden on purpose. - * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection. - * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere. - * Local ports do NOT need to match remote ports. In fact, they almost never do. - * This feature is here for networking professionals using very advanced techniques. -**/ -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - viaInterface:(NSString *)interface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -/** - * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object. - * For example, a NSData object returned from NSNetService's addresses method. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; - * - * This method invokes connectToAdd -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; - -/** - * This method is the same as connectToAddress:error: with an additional timeout option. - * To not time out use a negative time interval, or simply use the connectToAddress:error: method. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; - -/** - * Connects to the given address, using the specified interface and timeout. - * - * The address is specified as a sockaddr structure wrapped in a NSData object. - * For example, a NSData object returned from NSNetService's addresses method. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; - * - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * The interface may also be used to specify the local port (see below). - * - * The timeout is optional. To not time out use a negative time interval. - * - * This method will return NO if an error is detected, and set the error pointer (if one was given). - * Possible errors would be a nil host, invalid interface, or socket is already connected. - * - * If no errors are detected, this method will start a background connect operation and immediately return YES. - * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable. - * - * Since this class supports queued reads and writes, you can immediately start reading and/or writing. - * All read/write operations will be queued, and upon socket connection, - * the operations will be dequeued and processed in order. - * - * The interface may optionally contain a port number at the end of the string, separated by a colon. - * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end) - * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424". - * To specify only local port: ":8082". - * Please note this is an advanced feature, and is somewhat hidden on purpose. - * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection. - * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere. - * Local ports do NOT need to match remote ports. In fact, they almost never do. - * This feature is here for networking professionals using very advanced techniques. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr - viaInterface:(NSString *)interface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; -/** - * Connects to the unix domain socket at the given url, using the specified timeout. - */ -- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; - -#pragma mark Disconnecting - -/** - * Disconnects immediately (synchronously). Any pending reads or writes are dropped. - * - * If the socket is not already disconnected, an invocation to the socketDidDisconnect:withError: delegate method - * will be queued onto the delegateQueue asynchronously (behind any previously queued delegate methods). - * In other words, the disconnected delegate method will be invoked sometime shortly after this method returns. - * - * Please note the recommended way of releasing a GCDAsyncSocket instance (e.g. in a dealloc method) - * [asyncSocket setDelegate:nil]; - * [asyncSocket disconnect]; - * [asyncSocket release]; - * - * If you plan on disconnecting the socket, and then immediately asking it to connect again, - * you'll likely want to do so like this: - * [asyncSocket setDelegate:nil]; - * [asyncSocket disconnect]; - * [asyncSocket setDelegate:self]; - * [asyncSocket connect...]; -**/ -- (void)disconnect; - -/** - * Disconnects after all pending reads have completed. - * After calling this, the read and write methods will do nothing. - * The socket will disconnect even if there are still pending writes. -**/ -- (void)disconnectAfterReading; - -/** - * Disconnects after all pending writes have completed. - * After calling this, the read and write methods will do nothing. - * The socket will disconnect even if there are still pending reads. -**/ -- (void)disconnectAfterWriting; - -/** - * Disconnects after all pending reads and writes have completed. - * After calling this, the read and write methods will do nothing. -**/ -- (void)disconnectAfterReadingAndWriting; - -#pragma mark Diagnostics - -/** - * Returns whether the socket is disconnected or connected. - * - * A disconnected socket may be recycled. - * That is, it can used again for connecting or listening. - * - * If a socket is in the process of connecting, it may be neither disconnected nor connected. -**/ -- (BOOL)isDisconnected; -- (BOOL)isConnected; - -/** - * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected. - * The host will be an IP address. -**/ -- (NSString *)connectedHost; -- (uint16_t)connectedPort; -- (NSURL *)connectedUrl; - -- (NSString *)localHost; -- (uint16_t)localPort; - -/** - * Returns the local or remote address to which this socket is connected, - * specified as a sockaddr structure wrapped in a NSData object. - * - * See also the connectedHost, connectedPort, localHost and localPort methods. -**/ -- (NSData *)connectedAddress; -- (NSData *)localAddress; - -/** - * Returns whether the socket is IPv4 or IPv6. - * An accepting socket may be both. -**/ -- (BOOL)isIPv4; -- (BOOL)isIPv6; - -/** - * Returns whether or not the socket has been secured via SSL/TLS. - * - * See also the startTLS method. -**/ -- (BOOL)isSecure; - -#pragma mark Reading - -// The readData and writeData methods won't block (they are asynchronous). -// -// When a read is complete the socket:didReadData:withTag: delegate method is dispatched on the delegateQueue. -// When a write is complete the socket:didWriteDataWithTag: delegate method is dispatched on the delegateQueue. -// -// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.) -// If a read/write opertion times out, the corresponding "socket:shouldTimeout..." delegate method -// is called to optionally allow you to extend the timeout. -// Upon a timeout, the "socket:didDisconnectWithError:" method is called -// -// The tag is for your convenience. -// You can use it as an array index, step number, state id, pointer, etc. - -/** - * Reads the first available bytes that become available on the socket. - * - * If the timeout value is negative, the read operation will not use a timeout. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads the first available bytes that become available on the socket. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, the socket will create a buffer for you. - * - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads the first available bytes that become available on the socket. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * A maximum of length bytes will be read. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. - * If maxLength is zero, no length restriction is enforced. - * - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag; - -/** - * Reads the given number of bytes. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If the length is 0, this method does nothing and the delegate is not called. -**/ -- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads the given number of bytes. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. - * - * If the length is 0, this method does nothing and the delegate is not called. - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataToLength:(NSUInteger)length - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If you pass nil or zero-length data as the "data" parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. - * - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If maxLength is zero, no length restriction is enforced. - * Otherwise if maxLength bytes are read without completing the read, - * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError. - * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. - * - * If you pass nil or zero-length data as the "data" parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * If you pass a maxLength parameter that is less than the length of the data parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. - * - * If maxLength is zero, no length restriction is enforced. - * Otherwise if maxLength bytes are read without completing the read, - * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError. - * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. - * - * If you pass a maxLength parameter that is less than the length of the data (separator) parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag; - -/** - * Returns progress of the current read, from 0.0 to 1.0, or NaN if no current read (use isnan() to check). - * The parameters "tag", "done" and "total" will be filled in if they aren't NULL. -**/ -- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr; - -#pragma mark Writing - -/** - * Writes data to the socket, and calls the delegate when finished. - * - * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called. - * If the timeout value is negative, the write operation will not use a timeout. - * - * Thread-Safety Note: - * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while - * the socket is writing it. In other words, it's not safe to alter the data until after the delegate method - * socket:didWriteDataWithTag: is invoked signifying that this particular write operation has completed. - * This is due to the fact that GCDAsyncSocket does NOT copy the data. It simply retains it. - * This is for performance reasons. Often times, if NSMutableData is passed, it is because - * a request/response was built up in memory. Copying this data adds an unwanted/unneeded overhead. - * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket - * completes writing the bytes (which is NOT immediately after this method returns, but rather at a later time - * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. -**/ -- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Returns progress of the current write, from 0.0 to 1.0, or NaN if no current write (use isnan() to check). - * The parameters "tag", "done" and "total" will be filled in if they aren't NULL. -**/ -- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr; - -#pragma mark Security - -/** - * Secures the connection using SSL/TLS. - * - * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes - * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing - * the upgrade to TLS at the same time, without having to wait for the write to finish. - * Any reads or writes scheduled after this method is called will occur over the secured connection. - * - * The possible keys and values for the TLS settings are well documented. - * Standard keys are: - * - * - kCFStreamSSLLevel - * - kCFStreamSSLAllowsExpiredCertificates - * - kCFStreamSSLAllowsExpiredRoots - * - kCFStreamSSLAllowsAnyRoot - * - kCFStreamSSLValidatesCertificateChain - * - kCFStreamSSLPeerName - * - kCFStreamSSLCertificates - * - kCFStreamSSLIsServer - * - * If SecureTransport is available on iOS: - * - * - GCDAsyncSocketSSLCipherSuites - * - GCDAsyncSocketSSLProtocolVersionMin - * - GCDAsyncSocketSSLProtocolVersionMax - * - * If SecureTransport is available on Mac OS X: - * - * - GCDAsyncSocketSSLCipherSuites - * - GCDAsyncSocketSSLDiffieHellmanParameters; - * - * - * Please refer to Apple's documentation for associated values, as well as other possible keys. - * - * If you pass in nil or an empty dictionary, the default settings will be used. - * - * The default settings will check to make sure the remote party's certificate is signed by a - * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired. - * However it will not verify the name on the certificate unless you - * give it a name to verify against via the kCFStreamSSLPeerName key. - * The security implications of this are important to understand. - * Imagine you are attempting to create a secure connection to MySecureServer.com, - * but your socket gets directed to MaliciousServer.com because of a hacked DNS server. - * If you simply use the default settings, and MaliciousServer.com has a valid certificate, - * the default settings will not detect any problems since the certificate is valid. - * To properly secure your connection in this particular scenario you - * should set the kCFStreamSSLPeerName property to "MySecureServer.com". - * If you do not know the peer name of the remote host in advance (for example, you're not sure - * if it will be "domain.com" or "www.domain.com"), then you can use the default settings to validate the - * certificate, and then use the X509Certificate class to verify the issuer after the socket has been secured. - * The X509Certificate class is part of the CocoaAsyncSocket open source project. - **/ -- (void)startTLS:(NSDictionary *)tlsSettings; - -#pragma mark Advanced - -/** - * Traditionally sockets are not closed until the conversation is over. - * However, it is technically possible for the remote enpoint to close its write stream. - * Our socket would then be notified that there is no more data to be read, - * but our socket would still be writeable and the remote endpoint could continue to receive our data. - * - * The argument for this confusing functionality stems from the idea that a client could shut down its - * write stream after sending a request to the server, thus notifying the server there are to be no further requests. - * In practice, however, this technique did little to help server developers. - * - * To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close - * and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell - * is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work. - * Otherwise an error will be occur shortly (when the remote end sends us a RST packet). - * - * In addition to the technical challenges and confusion, many high level socket/stream API's provide - * no support for dealing with the problem. If the read stream is closed, the API immediately declares the - * socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does. - * It might sound like poor design at first, but in fact it simplifies development. - * - * The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket. - * Thus it actually makes sense to close the socket at this point. - * And in fact this is what most networking developers want and expect to happen. - * However, if you are writing a server that interacts with a plethora of clients, - * you might encounter a client that uses the discouraged technique of shutting down its write stream. - * If this is the case, you can set this property to NO, - * and make use of the socketDidCloseReadStream delegate method. - * - * The default value is YES. -**/ -- (BOOL)autoDisconnectOnClosedReadStream; -- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag; - -/** - * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue. - * In most cases, the instance creates this queue itself. - * However, to allow for maximum flexibility, the internal queue may be passed in the init method. - * This allows for some advanced options such as controlling socket priority via target queues. - * However, when one begins to use target queues like this, they open the door to some specific deadlock issues. - * - * For example, imagine there are 2 queues: - * dispatch_queue_t socketQueue; - * dispatch_queue_t socketTargetQueue; - * - * If you do this (pseudo-code): - * socketQueue.targetQueue = socketTargetQueue; - * - * Then all socketQueue operations will actually get run on the given socketTargetQueue. - * This is fine and works great in most situations. - * But if you run code directly from within the socketTargetQueue that accesses the socket, - * you could potentially get deadlock. Imagine the following code: - * - * - (BOOL)socketHasSomething - * { - * __block BOOL result = NO; - * dispatch_block_t block = ^{ - * result = [self someInternalMethodToBeRunOnlyOnSocketQueue]; - * } - * if (is_executing_on_queue(socketQueue)) - * block(); - * else - * dispatch_sync(socketQueue, block); - * - * return result; - * } - * - * What happens if you call this method from the socketTargetQueue? The result is deadlock. - * This is because the GCD API offers no mechanism to discover a queue's targetQueue. - * Thus we have no idea if our socketQueue is configured with a targetQueue. - * If we had this information, we could easily avoid deadlock. - * But, since these API's are missing or unfeasible, you'll have to explicitly set it. - * - * IF you pass a socketQueue via the init method, - * AND you've configured the passed socketQueue with a targetQueue, - * THEN you should pass the end queue in the target hierarchy. - * - * For example, consider the following queue hierarchy: - * socketQueue -> ipQueue -> moduleQueue - * - * This example demonstrates priority shaping within some server. - * All incoming client connections from the same IP address are executed on the same target queue. - * And all connections for a particular module are executed on the same target queue. - * Thus, the priority of all networking for the entire module can be changed on the fly. - * Additionally, networking traffic from a single IP cannot monopolize the module. - * - * Here's how you would accomplish something like that: - * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock - * { - * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL); - * dispatch_queue_t ipQueue = [self ipQueueForAddress:address]; - * - * dispatch_set_target_queue(socketQueue, ipQueue); - * dispatch_set_target_queue(iqQueue, moduleQueue); - * - * return socketQueue; - * } - * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket - * { - * [clientConnections addObject:newSocket]; - * [newSocket markSocketQueueTargetQueue:moduleQueue]; - * } - * - * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue. - * This is often NOT the case, as such queues are used solely for execution shaping. -**/ -- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue; -- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue; - -/** - * It's not thread-safe to access certain variables from outside the socket's internal queue. - * - * For example, the socket file descriptor. - * File descriptors are simply integers which reference an index in the per-process file table. - * However, when one requests a new file descriptor (by opening a file or socket), - * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor. - * So if we're not careful, the following could be possible: - * - * - Thread A invokes a method which returns the socket's file descriptor. - * - The socket is closed via the socket's internal queue on thread B. - * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD. - * - Thread A is now accessing/altering the file instead of the socket. - * - * In addition to this, other variables are not actually objects, - * and thus cannot be retained/released or even autoreleased. - * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct. - * - * Although there are internal variables that make it difficult to maintain thread-safety, - * it is important to provide access to these variables - * to ensure this class can be used in a wide array of environments. - * This method helps to accomplish this by invoking the current block on the socket's internal queue. - * The methods below can be invoked from within the block to access - * those generally thread-unsafe internal variables in a thread-safe manner. - * The given block will be invoked synchronously on the socket's internal queue. - * - * If you save references to any protected variables and use them outside the block, you do so at your own peril. -**/ -- (void)performBlock:(dispatch_block_t)block; - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's file descriptor(s). - * If the socket is a server socket (is accepting incoming connections), - * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6. -**/ -- (int)socketFD; -- (int)socket4FD; -- (int)socket6FD; - -#if TARGET_OS_IPHONE - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's internal CFReadStream/CFWriteStream. - * - * These streams are only used as workarounds for specific iOS shortcomings: - * - * - Apple has decided to keep the SecureTransport framework private is iOS. - * This means the only supplied way to do SSL/TLS is via CFStream or some other API layered on top of it. - * Thus, in order to provide SSL/TLS support on iOS we are forced to rely on CFStream, - * instead of the preferred and faster and more powerful SecureTransport. - * - * - If a socket doesn't have backgrounding enabled, and that socket is closed while the app is backgrounded, - * Apple only bothers to notify us via the CFStream API. - * The faster and more powerful GCD API isn't notified properly in this case. - * - * See also: (BOOL)enableBackgroundingOnSocket -**/ -- (CFReadStreamRef)readStream; -- (CFWriteStreamRef)writeStream; - -/** - * This method is only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Configures the socket to allow it to operate when the iOS application has been backgrounded. - * In other words, this method creates a read & write stream, and invokes: - * - * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * - * Returns YES if successful, NO otherwise. - * - * Note: Apple does not officially support backgrounding server sockets. - * That is, if your socket is accepting incoming connections, Apple does not officially support - * allowing iOS applications to accept incoming connections while an app is backgrounded. - * - * Example usage: - * - * - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port - * { - * [asyncSocket performBlock:^{ - * [asyncSocket enableBackgroundingOnSocket]; - * }]; - * } -**/ -- (BOOL)enableBackgroundingOnSocket; - -#endif - -#if SECURE_TRANSPORT_MAYBE_AVAILABLE - -/** - * This method is only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's SSLContext, if SSL/TLS has been started on the socket. -**/ -- (SSLContextRef)sslContext; - -#endif - -#pragma mark Utilities - -/** - * Extracting host and port information from raw address data. -**/ -+ (NSString *)hostFromAddress:(NSData *)address; -+ (uint16_t)portFromAddress:(NSData *)address; -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address; - -/** - * A few common line separators, for use with the readDataToData:... methods. -**/ -+ (NSData *)CRLFData; // 0x0D0A -+ (NSData *)CRData; // 0x0D -+ (NSData *)LFData; // 0x0A -+ (NSData *)ZeroData; // 0x00 - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@protocol GCDAsyncSocketDelegate -@optional - -/** - * This method is called immediately prior to socket:didAcceptNewSocket:. - * It optionally allows a listening socket to specify the socketQueue for a new accepted socket. - * If this method is not implemented, or returns NULL, the new accepted socket will create its own default queue. - * - * Since you cannot autorelease a dispatch_queue, - * this method uses the "new" prefix in its name to specify that the returned queue has been retained. - * - * Thus you could do something like this in the implementation: - * return dispatch_queue_create("MyQueue", NULL); - * - * If you are placing multiple sockets on the same queue, - * then care should be taken to increment the retain count each time this method is invoked. - * - * For example, your implementation might look something like this: - * dispatch_retain(myExistingQueue); - * return myExistingQueue; -**/ -- (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock; - -/** - * Called when a socket accepts a connection. - * Another socket is automatically spawned to handle it. - * - * You must retain the newSocket if you wish to handle the connection. - * Otherwise the newSocket instance will be released and the spawned connection will be closed. - * - * By default the new socket will have the same delegate and delegateQueue. - * You may, of course, change this at any time. -**/ -- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket; - -/** - * Called when a socket connects and is ready for reading and writing. - * The host parameter will be an IP address, not a DNS name. -**/ -- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port; - -/** - * Called when a socket connects and is ready for reading and writing. - * The host parameter will be an IP address, not a DNS name. - **/ -- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url; - -/** - * Called when a socket has completed reading the requested data into memory. - * Not called if there is an error. -**/ -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; - -/** - * Called when a socket has read in data, but has not yet completed the read. - * This would occur if using readToData: or readToLength: methods. - * It may be used to for things such as updating progress bars. -**/ -- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; - -/** - * Called when a socket has completed writing the requested data. Not called if there is an error. -**/ -- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag; - -/** - * Called when a socket has written some data, but has not yet completed the entire write. - * It may be used to for things such as updating progress bars. -**/ -- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; - -/** - * Called if a read operation has reached its timeout without completing. - * This method allows you to optionally extend the timeout. - * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount. - * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual. - * - * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. - * The length parameter is the number of bytes that have been read so far for the read operation. - * - * Note that this method may be called multiple times for a single read if you return positive numbers. -**/ -- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag - elapsed:(NSTimeInterval)elapsed - bytesDone:(NSUInteger)length; - -/** - * Called if a write operation has reached its timeout without completing. - * This method allows you to optionally extend the timeout. - * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount. - * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual. - * - * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. - * The length parameter is the number of bytes that have been written so far for the write operation. - * - * Note that this method may be called multiple times for a single write if you return positive numbers. -**/ -- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag - elapsed:(NSTimeInterval)elapsed - bytesDone:(NSUInteger)length; - -/** - * Conditionally called if the read stream closes, but the write stream may still be writeable. - * - * This delegate method is only called if autoDisconnectOnClosedReadStream has been set to NO. - * See the discussion on the autoDisconnectOnClosedReadStream method for more information. -**/ -- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock; - -/** - * Called when a socket disconnects with or without error. - * - * If you call the disconnect method, and the socket wasn't already disconnected, - * this delegate method will be called before the disconnect method returns. -**/ -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err; - -/** - * Called after the socket has successfully completed SSL/TLS negotiation. - * This method is not called unless you use the provided startTLS method. - * - * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close, - * and the socketDidDisconnect:withError: delegate method will be called with the specific SSL error code. -**/ -- (void)socketDidSecure:(GCDAsyncSocket *)sock; - -@end diff --git a/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.m b/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.m deleted file mode 100644 index 16a016fe7..000000000 --- a/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.m +++ /dev/null @@ -1,7952 +0,0 @@ -// -// GCDAsyncSocket.m -// -// This class is in the public domain. -// Originally created by Robbie Hanson in Q4 2010. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#import "GCDAsyncSocket.h" - -#if TARGET_OS_IPHONE -#import -#endif - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC -#endif - -/** - * Does ARC support support GCD objects? - * It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+ -**/ -#if TARGET_OS_IPHONE - - // Compiling for iOS - - #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else // iOS 5.X or earlier - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 - #endif - -#else - - // Compiling for Mac OS X - - #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier - #endif - -#endif - - -#if 0 - -// Logging Enabled - See log level below - -// Logging uses the CocoaLumberjack framework (which is also GCD based). -// https://github.com/robbiehanson/CocoaLumberjack -// -// It allows us to do a lot of logging without significantly slowing down the code. -#import "DDLog.h" - -#define LogAsync YES -#define LogContext 65535 - -#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) -#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) - -#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) -#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__) - -// Log levels : off, error, warn, info, verbose -static const int logLevel = LOG_LEVEL_VERBOSE; - -#else - -// Logging Disabled - -#define LogError(frmt, ...) {} -#define LogWarn(frmt, ...) {} -#define LogInfo(frmt, ...) {} -#define LogVerbose(frmt, ...) {} - -#define LogCError(frmt, ...) {} -#define LogCWarn(frmt, ...) {} -#define LogCInfo(frmt, ...) {} -#define LogCVerbose(frmt, ...) {} - -#define LogTrace() {} -#define LogCTrace(frmt, ...) {} - -#endif - -/** - * Seeing a return statements within an inner block - * can sometimes be mistaken for a return point of the enclosing method. - * This makes inline blocks a bit easier to read. -**/ -#define return_from_block return - -/** - * A socket file descriptor is really just an integer. - * It represents the index of the socket within the kernel. - * This makes invalid file descriptor comparisons easier to read. -**/ -#define SOCKET_NULL -1 - - -NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException"; -NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain"; - -NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket"; -NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream"; - -#if SECURE_TRANSPORT_MAYBE_AVAILABLE -NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites"; -#if TARGET_OS_IPHONE -NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin"; -NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax"; -#else -NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters"; -#endif -#endif - -enum GCDAsyncSocketFlags -{ - kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting) - kConnected = 1 << 1, // If set, the socket is connected - kForbidReadsWrites = 1 << 2, // If set, no new reads or writes are allowed - kReadsPaused = 1 << 3, // If set, reads are paused due to possible timeout - kWritesPaused = 1 << 4, // If set, writes are paused due to possible timeout - kDisconnectAfterReads = 1 << 5, // If set, disconnect after no more reads are queued - kDisconnectAfterWrites = 1 << 6, // If set, disconnect after no more writes are queued - kSocketCanAcceptBytes = 1 << 7, // If set, we know socket can accept bytes. If unset, it's unknown. - kReadSourceSuspended = 1 << 8, // If set, the read source is suspended - kWriteSourceSuspended = 1 << 9, // If set, the write source is suspended - kQueuedTLS = 1 << 10, // If set, we've queued an upgrade to TLS - kStartingReadTLS = 1 << 11, // If set, we're waiting for TLS negotiation to complete - kStartingWriteTLS = 1 << 12, // If set, we're waiting for TLS negotiation to complete - kSocketSecure = 1 << 13, // If set, socket is using secure communication via SSL/TLS - kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket - kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained -#if TARGET_OS_IPHONE - kAddedStreamsToRunLoop = 1 << 16, // If set, CFStreams have been added to listener thread - kUsingCFStreamForTLS = 1 << 17, // If set, we're forced to use CFStream instead of SecureTransport - kSecureSocketHasBytesAvailable = 1 << 18, // If set, CFReadStream has notified us of bytes available -#endif -}; - -enum GCDAsyncSocketConfig -{ - kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled - kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled - kPreferIPv6 = 1 << 2, // If set, IPv6 is preferred over IPv4 - kAllowHalfDuplexConnection = 1 << 3, // If set, the socket will stay open even if the read stream closes -}; - -#if TARGET_OS_IPHONE - static NSThread *cfstreamThread; // Used for CFStreams -#endif - -@interface GCDAsyncSocket () -{ - uint32_t flags; - uint16_t config; - -#if __has_feature(objc_arc_weak) - __weak id delegate; -#else - __unsafe_unretained id delegate; -#endif - dispatch_queue_t delegateQueue; - - int socket4FD; - int socket6FD; - int socketUN; - int connectIndex; - NSData * connectInterface4; - NSData * connectInterface6; - NSData * connectInterfaceUN; - NSURL * socketUrl; - - dispatch_queue_t socketQueue; - - dispatch_source_t accept4Source; - dispatch_source_t accept6Source; - dispatch_source_t acceptUNSource; - dispatch_source_t connectTimer; - dispatch_source_t readSource; - dispatch_source_t writeSource; - dispatch_source_t readTimer; - dispatch_source_t writeTimer; - - NSMutableArray *readQueue; - NSMutableArray *writeQueue; - - GCDAsyncReadPacket *currentRead; - GCDAsyncWritePacket *currentWrite; - - unsigned long socketFDBytesAvailable; - - GCDAsyncSocketPreBuffer *preBuffer; - -#if TARGET_OS_IPHONE - CFStreamClientContext streamContext; - CFReadStreamRef readStream; - CFWriteStreamRef writeStream; -#endif -#if SECURE_TRANSPORT_MAYBE_AVAILABLE - SSLContextRef sslContext; - GCDAsyncSocketPreBuffer *sslPreBuffer; - size_t sslWriteCachedLength; - OSStatus sslErrCode; -#endif - - void *IsOnSocketQueueOrTargetQueueKey; - - id userData; -} -// Accepting -- (BOOL)doAccept:(int)socketFD; - -// Connecting -- (void)startConnectTimeout:(NSTimeInterval)timeout; -- (void)endConnectTimeout; -- (void)doConnectTimeout; -- (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port; -- (void)lookup:(int)aConnectIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6; -- (void)lookup:(int)aConnectIndex didFail:(NSError *)error; -- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr; -- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr; -- (void)didConnect:(int)aConnectIndex; -- (void)didNotConnect:(int)aConnectIndex error:(NSError *)error; - -// Disconnect -- (void)closeWithError:(NSError *)error; -- (void)maybeClose; - -// Errors -- (NSError *)badConfigError:(NSString *)msg; -- (NSError *)badParamError:(NSString *)msg; -- (NSError *)gaiError:(int)gai_error; -- (NSError *)errnoError; -- (NSError *)errnoErrorWithReason:(NSString *)reason; -- (NSError *)connectTimeoutError; -- (NSError *)otherError:(NSString *)msg; - -// Diagnostics -- (NSString *)connectedHost4; -- (NSString *)connectedHost6; -- (uint16_t)connectedPort4; -- (uint16_t)connectedPort6; -- (NSString *)localHost4; -- (NSString *)localHost6; -- (uint16_t)localPort4; -- (uint16_t)localPort6; -- (NSString *)connectedHostFromSocket4:(int)socketFD; -- (NSString *)connectedHostFromSocket6:(int)socketFD; -- (uint16_t)connectedPortFromSocket4:(int)socketFD; -- (uint16_t)connectedPortFromSocket6:(int)socketFD; -- (NSURL *)connectedUrlFromSocketUN:(int)socketFD; -- (NSString *)localHostFromSocket4:(int)socketFD; -- (NSString *)localHostFromSocket6:(int)socketFD; -- (uint16_t)localPortFromSocket4:(int)socketFD; -- (uint16_t)localPortFromSocket6:(int)socketFD; - -// Utilities -- (void)getInterfaceAddress4:(NSMutableData **)addr4Ptr - address6:(NSMutableData **)addr6Ptr - fromDescription:(NSString *)interfaceDescription - port:(uint16_t)port; -- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url; -- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD; -- (void)suspendReadSource; -- (void)resumeReadSource; -- (void)suspendWriteSource; -- (void)resumeWriteSource; - -// Reading -- (void)maybeDequeueRead; -- (void)flushSSLBuffers; -- (void)doReadData; -- (void)doReadEOF; -- (void)completeCurrentRead; -- (void)endCurrentRead; -- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout; -- (void)doReadTimeout; -- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension; - -// Writing -- (void)maybeDequeueWrite; -- (void)doWriteData; -- (void)completeCurrentWrite; -- (void)endCurrentWrite; -- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout; -- (void)doWriteTimeout; -- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension; - -// Security -- (void)maybeStartTLS; -#if SECURE_TRANSPORT_MAYBE_AVAILABLE -- (void)ssl_startTLS; -- (void)ssl_continueSSLHandshake; -#endif -#if TARGET_OS_IPHONE -- (void)cf_startTLS; -#endif - -// CFStream -#if TARGET_OS_IPHONE -+ (void)startCFStreamThreadIfNeeded; -- (BOOL)createReadAndWriteStream; -- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite; -- (BOOL)addStreamsToRunLoop; -- (BOOL)openStreams; -- (void)removeStreamsFromRunLoop; -#endif - -// Class Methods -+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; -+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; -+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; -+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; -+ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr; - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * A PreBuffer is used when there is more data available on the socket - * than is being requested by current read request. - * In this case we slurp up all data from the socket (to minimize sys calls), - * and store additional yet unread data in a "prebuffer". - * - * The prebuffer is entirely drained before we read from the socket again. - * In other words, a large chunk of data is written is written to the prebuffer. - * The prebuffer is then drained via a series of one or more reads (for subsequent read request(s)). - * - * A ring buffer was once used for this purpose. - * But a ring buffer takes up twice as much memory as needed (double the size for mirroring). - * In fact, it generally takes up more than twice the needed size as everything has to be rounded up to vm_page_size. - * And since the prebuffer is always completely drained after being written to, a full ring buffer isn't needed. - * - * The current design is very simple and straight-forward, while also keeping memory requirements lower. -**/ - -@interface GCDAsyncSocketPreBuffer : NSObject -{ - uint8_t *preBuffer; - size_t preBufferSize; - - uint8_t *readPointer; - uint8_t *writePointer; -} - -- (id)initWithCapacity:(size_t)numBytes; - -- (void)ensureCapacityForWrite:(size_t)numBytes; - -- (size_t)availableBytes; -- (uint8_t *)readBuffer; - -- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr; - -- (size_t)availableSpace; -- (uint8_t *)writeBuffer; - -- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr; - -- (void)didRead:(size_t)bytesRead; -- (void)didWrite:(size_t)bytesWritten; - -- (void)reset; - -@end - -@implementation GCDAsyncSocketPreBuffer - -- (id)initWithCapacity:(size_t)numBytes -{ - if ((self = [super init])) - { - preBufferSize = numBytes; - preBuffer = malloc(preBufferSize); - - readPointer = preBuffer; - writePointer = preBuffer; - } - return self; -} - -- (void)dealloc -{ - if (preBuffer) - free(preBuffer); -} - -- (void)ensureCapacityForWrite:(size_t)numBytes -{ - size_t availableSpace = preBufferSize - (writePointer - readPointer); - - if (numBytes > availableSpace) - { - size_t additionalBytes = numBytes - availableSpace; - - size_t newPreBufferSize = preBufferSize + additionalBytes; - uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize); - - size_t readPointerOffset = readPointer - preBuffer; - size_t writePointerOffset = writePointer - preBuffer; - - preBuffer = newPreBuffer; - preBufferSize = newPreBufferSize; - - readPointer = preBuffer + readPointerOffset; - writePointer = preBuffer + writePointerOffset; - } -} - -- (size_t)availableBytes -{ - return writePointer - readPointer; -} - -- (uint8_t *)readBuffer -{ - return readPointer; -} - -- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr -{ - if (bufferPtr) *bufferPtr = readPointer; - if (availableBytesPtr) *availableBytesPtr = writePointer - readPointer; -} - -- (void)didRead:(size_t)bytesRead -{ - readPointer += bytesRead; - - if (readPointer == writePointer) - { - // The prebuffer has been drained. Reset pointers. - readPointer = preBuffer; - writePointer = preBuffer; - } -} - -- (size_t)availableSpace -{ - return preBufferSize - (writePointer - readPointer); -} - -- (uint8_t *)writeBuffer -{ - return writePointer; -} - -- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr -{ - if (bufferPtr) *bufferPtr = writePointer; - if (availableSpacePtr) *availableSpacePtr = preBufferSize - (writePointer - readPointer); -} - -- (void)didWrite:(size_t)bytesWritten -{ - writePointer += bytesWritten; -} - -- (void)reset -{ - readPointer = preBuffer; - writePointer = preBuffer; -} - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncReadPacket encompasses the instructions for any given read. - * The content of a read packet allows the code to determine if we're: - * - reading to a certain length - * - reading to a certain separator - * - or simply reading the first chunk of available data -**/ -@interface GCDAsyncReadPacket : NSObject -{ - @public - NSMutableData *buffer; - NSUInteger startOffset; - NSUInteger bytesDone; - NSUInteger maxLength; - NSTimeInterval timeout; - NSUInteger readLength; - NSData *term; - BOOL bufferOwner; - NSUInteger originalBufferLength; - long tag; -} -- (id)initWithData:(NSMutableData *)d - startOffset:(NSUInteger)s - maxLength:(NSUInteger)m - timeout:(NSTimeInterval)t - readLength:(NSUInteger)l - terminator:(NSData *)e - tag:(long)i; - -- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead; - -- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr; - -- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable; -- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr; -- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr; - -- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes; - -@end - -@implementation GCDAsyncReadPacket - -- (id)initWithData:(NSMutableData *)d - startOffset:(NSUInteger)s - maxLength:(NSUInteger)m - timeout:(NSTimeInterval)t - readLength:(NSUInteger)l - terminator:(NSData *)e - tag:(long)i -{ - if((self = [super init])) - { - bytesDone = 0; - maxLength = m; - timeout = t; - readLength = l; - term = [e copy]; - tag = i; - - if (d) - { - buffer = d; - startOffset = s; - bufferOwner = NO; - originalBufferLength = [d length]; - } - else - { - if (readLength > 0) - buffer = [[NSMutableData alloc] initWithLength:readLength]; - else - buffer = [[NSMutableData alloc] initWithLength:0]; - - startOffset = 0; - bufferOwner = YES; - originalBufferLength = 0; - } - } - return self; -} - -/** - * Increases the length of the buffer (if needed) to ensure a read of the given size will fit. -**/ -- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead -{ - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - NSUInteger buffSpace = buffSize - buffUsed; - - if (bytesToRead > buffSpace) - { - NSUInteger buffInc = bytesToRead - buffSpace; - - [buffer increaseLengthBy:buffInc]; - } -} - -/** - * This method is used when we do NOT know how much data is available to be read from the socket. - * This method returns the default value unless it exceeds the specified readLength or maxLength. - * - * Furthermore, the shouldPreBuffer decision is based upon the packet type, - * and whether the returned value would fit in the current buffer without requiring a resize of the buffer. -**/ -- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr -{ - NSUInteger result; - - if (readLength > 0) - { - // Read a specific length of data - - result = MIN(defaultValue, (readLength - bytesDone)); - - // There is no need to prebuffer since we know exactly how much data we need to read. - // Even if the buffer isn't currently big enough to fit this amount of data, - // it would have to be resized eventually anyway. - - if (shouldPreBufferPtr) - *shouldPreBufferPtr = NO; - } - else - { - // Either reading until we find a specified terminator, - // or we're simply reading all available data. - // - // In other words, one of: - // - // - readDataToData packet - // - readDataWithTimeout packet - - if (maxLength > 0) - result = MIN(defaultValue, (maxLength - bytesDone)); - else - result = defaultValue; - - // Since we don't know the size of the read in advance, - // the shouldPreBuffer decision is based upon whether the returned value would fit - // in the current buffer without requiring a resize of the buffer. - // - // This is because, in all likelyhood, the amount read from the socket will be less than the default value. - // Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead. - - if (shouldPreBufferPtr) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - NSUInteger buffSpace = buffSize - buffUsed; - - if (buffSpace >= result) - *shouldPreBufferPtr = NO; - else - *shouldPreBufferPtr = YES; - } - } - - return result; -} - -/** - * For read packets without a set terminator, returns the amount of data - * that can be read without exceeding the readLength or maxLength. - * - * The given parameter indicates the number of bytes estimated to be available on the socket, - * which is taken into consideration during the calculation. - * - * The given hint MUST be greater than zero. -**/ -- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable -{ - NSAssert(term == nil, @"This method does not apply to term reads"); - NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); - - if (readLength > 0) - { - // Read a specific length of data - - return MIN(bytesAvailable, (readLength - bytesDone)); - - // No need to avoid resizing the buffer. - // If the user provided their own buffer, - // and told us to read a certain length of data that exceeds the size of the buffer, - // then it is clear that our code will resize the buffer during the read operation. - // - // This method does not actually do any resizing. - // The resizing will happen elsewhere if needed. - } - else - { - // Read all available data - - NSUInteger result = bytesAvailable; - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - // No need to avoid resizing the buffer. - // If the user provided their own buffer, - // and told us to read all available data without giving us a maxLength, - // then it is clear that our code might resize the buffer during the read operation. - // - // This method does not actually do any resizing. - // The resizing will happen elsewhere if needed. - - return result; - } -} - -/** - * For read packets with a set terminator, returns the amount of data - * that can be read without exceeding the maxLength. - * - * The given parameter indicates the number of bytes estimated to be available on the socket, - * which is taken into consideration during the calculation. - * - * To optimize memory allocations, mem copies, and mem moves - * the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first, - * or if the data can be read directly into the read packet's buffer. -**/ -- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); - - - NSUInteger result = bytesAvailable; - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - // Should the data be read into the read packet's buffer, or into a pre-buffer first? - // - // One would imagine the preferred option is the faster one. - // So which one is faster? - // - // Reading directly into the packet's buffer requires: - // 1. Possibly resizing packet buffer (malloc/realloc) - // 2. Filling buffer (read) - // 3. Searching for term (memcmp) - // 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy) - // - // Reading into prebuffer first: - // 1. Possibly resizing prebuffer (malloc/realloc) - // 2. Filling buffer (read) - // 3. Searching for term (memcmp) - // 4. Copying underflow into packet buffer (malloc/realloc, memcpy) - // 5. Removing underflow from prebuffer (memmove) - // - // Comparing the performance of the two we can see that reading - // data into the prebuffer first is slower due to the extra memove. - // - // However: - // The implementation of NSMutableData is open source via core foundation's CFMutableData. - // Decreasing the length of a mutable data object doesn't cause a realloc. - // In other words, the capacity of a mutable data object can grow, but doesn't shrink. - // - // This means the prebuffer will rarely need a realloc. - // The packet buffer, on the other hand, may often need a realloc. - // This is especially true if we are the buffer owner. - // Furthermore, if we are constantly realloc'ing the packet buffer, - // and then moving the overflow into the prebuffer, - // then we're consistently over-allocating memory for each term read. - // And now we get into a bit of a tradeoff between speed and memory utilization. - // - // The end result is that the two perform very similarly. - // And we can answer the original question very simply by another means. - // - // If we can read all the data directly into the packet's buffer without resizing it first, - // then we do so. Otherwise we use the prebuffer. - - if (shouldPreBufferPtr) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - if ((buffSize - buffUsed) >= result) - *shouldPreBufferPtr = NO; - else - *shouldPreBufferPtr = YES; - } - - return result; -} - -/** - * For read packets with a set terminator, - * returns the amount of data that can be read from the given preBuffer, - * without going over a terminator or the maxLength. - * - * It is assumed the terminator has not already been read. -**/ -- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert([preBuffer availableBytes] > 0, @"Invoked with empty pre buffer!"); - - // We know that the terminator, as a whole, doesn't exist in our own buffer. - // But it is possible that a _portion_ of it exists in our buffer. - // So we're going to look for the terminator starting with a portion of our own buffer. - // - // Example: - // - // term length = 3 bytes - // bytesDone = 5 bytes - // preBuffer length = 5 bytes - // - // If we append the preBuffer to our buffer, - // it would look like this: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // --------------------- - // - // So we start our search here: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // -------^-^-^--------- - // - // And move forwards... - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------^-^-^------- - // - // Until we find the terminator or reach the end. - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------------^-^-^- - - BOOL found = NO; - - NSUInteger termLength = [term length]; - NSUInteger preBufferLength = [preBuffer availableBytes]; - - if ((bytesDone + preBufferLength) < termLength) - { - // Not enough data for a full term sequence yet - return preBufferLength; - } - - NSUInteger maxPreBufferLength; - if (maxLength > 0) { - maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone)); - - // Note: maxLength >= termLength - } - else { - maxPreBufferLength = preBufferLength; - } - - uint8_t seq[termLength]; - const void *termBuf = [term bytes]; - - NSUInteger bufLen = MIN(bytesDone, (termLength - 1)); - uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen; - - NSUInteger preLen = termLength - bufLen; - const uint8_t *pre = [preBuffer readBuffer]; - - NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above. - - NSUInteger result = maxPreBufferLength; - - NSUInteger i; - for (i = 0; i < loopCount; i++) - { - if (bufLen > 0) - { - // Combining bytes from buffer and preBuffer - - memcpy(seq, buf, bufLen); - memcpy(seq + bufLen, pre, preLen); - - if (memcmp(seq, termBuf, termLength) == 0) - { - result = preLen; - found = YES; - break; - } - - buf++; - bufLen--; - preLen++; - } - else - { - // Comparing directly from preBuffer - - if (memcmp(pre, termBuf, termLength) == 0) - { - NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic - - result = preOffset + termLength; - found = YES; - break; - } - - pre++; - } - } - - // There is no need to avoid resizing the buffer in this particular situation. - - if (foundPtr) *foundPtr = found; - return result; -} - -/** - * For read packets with a set terminator, scans the packet buffer for the term. - * It is assumed the terminator had not been fully read prior to the new bytes. - * - * If the term is found, the number of excess bytes after the term are returned. - * If the term is not found, this method will return -1. - * - * Note: A return value of zero means the term was found at the very end. - * - * Prerequisites: - * The given number of bytes have been added to the end of our buffer. - * Our bytesDone variable has NOT been changed due to the prebuffered bytes. -**/ -- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - - // The implementation of this method is very similar to the above method. - // See the above method for a discussion of the algorithm used here. - - uint8_t *buff = [buffer mutableBytes]; - NSUInteger buffLength = bytesDone + numBytes; - - const void *termBuff = [term bytes]; - NSUInteger termLength = [term length]; - - // Note: We are dealing with unsigned integers, - // so make sure the math doesn't go below zero. - - NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0; - - while (i + termLength <= buffLength) - { - uint8_t *subBuffer = buff + startOffset + i; - - if (memcmp(subBuffer, termBuff, termLength) == 0) - { - return buffLength - (i + termLength); - } - - i++; - } - - return -1; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncWritePacket encompasses the instructions for any given write. -**/ -@interface GCDAsyncWritePacket : NSObject -{ - @public - NSData *buffer; - NSUInteger bytesDone; - long tag; - NSTimeInterval timeout; -} -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; -@end - -@implementation GCDAsyncWritePacket - -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i -{ - if((self = [super init])) - { - buffer = d; // Retain not copy. For performance as documented in header file. - bytesDone = 0; - timeout = t; - tag = i; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues. - * This class my be altered to support more than just TLS in the future. -**/ -@interface GCDAsyncSpecialPacket : NSObject -{ - @public - NSDictionary *tlsSettings; -} -- (id)initWithTLSSettings:(NSDictionary *)settings; -@end - -@implementation GCDAsyncSpecialPacket - -- (id)initWithTLSSettings:(NSDictionary *)settings -{ - if((self = [super init])) - { - tlsSettings = [settings copy]; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation GCDAsyncSocket - -- (id)init -{ - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; -} - -- (id)initWithSocketQueue:(dispatch_queue_t)sq -{ - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; -} - -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq -{ - return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; -} - -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq -{ - if((self = [super init])) - { - delegate = aDelegate; - delegateQueue = dq; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (dq) dispatch_retain(dq); - #endif - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - socketUN = SOCKET_NULL; - socketUrl = nil; - connectIndex = 0; - - if (sq) - { - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - - socketQueue = sq; - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_retain(sq); - #endif - } - else - { - socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL); - } - - // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. - // From the documentation: - // - // > Keys are only compared as pointers and are never dereferenced. - // > Thus, you can use a pointer to a static variable for a specific subsystem or - // > any other value that allows you to identify the value uniquely. - // - // We're just going to use the memory address of an ivar. - // Specifically an ivar that is explicitly named for our purpose to make the code more readable. - // - // However, it feels tedious (and less readable) to include the "&" all the time: - // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) - // - // So we're going to make it so it doesn't matter if we use the '&' or not, - // by assigning the value of the ivar to the address of the ivar. - // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; - - IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; - - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); - - readQueue = [[NSMutableArray alloc] initWithCapacity:5]; - currentRead = nil; - - writeQueue = [[NSMutableArray alloc] initWithCapacity:5]; - currentWrite = nil; - - preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; - } - return self; -} - -- (void)dealloc -{ - LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - [self closeWithError:nil]; - } - else - { - dispatch_sync(socketQueue, ^{ - [self closeWithError:nil]; - }); - } - - delegate = nil; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); - #endif - delegateQueue = NULL; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (socketQueue) dispatch_release(socketQueue); - #endif - socketQueue = NULL; - - LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Configuration -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (id)delegate -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegate; - } - else - { - __block id result; - - dispatch_sync(socketQueue, ^{ - result = delegate; - }); - - return result; - } -} - -- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - delegate = newDelegate; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:NO]; -} - -- (void)synchronouslySetDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:YES]; -} - -- (dispatch_queue_t)delegateQueue -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegateQueue; - } - else - { - __block dispatch_queue_t result; - - dispatch_sync(socketQueue, ^{ - result = delegateQueue; - }); - - return result; - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif - - delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:NO]; -} - -- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:YES]; -} - -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (delegatePtr) *delegatePtr = delegate; - if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; - } - else - { - __block id dPtr = NULL; - __block dispatch_queue_t dqPtr = NULL; - - dispatch_sync(socketQueue, ^{ - dPtr = delegate; - dqPtr = delegateQueue; - }); - - if (delegatePtr) *delegatePtr = dPtr; - if (delegateQueuePtr) *delegateQueuePtr = dqPtr; - } -} - -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - - delegate = newDelegate; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif - - delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; -} - -- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; -} - -- (BOOL)isIPv4Enabled -{ - // Note: YES means kIPv4Disabled is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kIPv4Disabled) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kIPv4Disabled) == 0); - }); - - return result; - } -} - -- (void)setIPv4Enabled:(BOOL)flag -{ - // Note: YES means kIPv4Disabled is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kIPv4Disabled; - else - config |= kIPv4Disabled; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv6Enabled -{ - // Note: YES means kIPv6Disabled is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kIPv6Disabled) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kIPv6Disabled) == 0); - }); - - return result; - } -} - -- (void)setIPv6Enabled:(BOOL)flag -{ - // Note: YES means kIPv6Disabled is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kIPv6Disabled; - else - config |= kIPv6Disabled; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv4PreferredOverIPv6 -{ - // Note: YES means kPreferIPv6 is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kPreferIPv6) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kPreferIPv6) == 0); - }); - - return result; - } -} - -- (void)setPreferIPv4OverIPv6:(BOOL)flag -{ - // Note: YES means kPreferIPv6 is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kPreferIPv6; - else - config |= kPreferIPv6; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (id)userData -{ - __block id result = nil; - - dispatch_block_t block = ^{ - - result = userData; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setUserData:(id)arbitraryUserData -{ - dispatch_block_t block = ^{ - - if (userData != arbitraryUserData) - { - userData = arbitraryUserData; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Accepting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr -{ - return [self acceptOnInterface:nil port:port error:errPtr]; -} - -- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr -{ - LogTrace(); - - // Just in-case interface parameter is immutable. - NSString *interface = [inInterface copy]; - - __block BOOL result = NO; - __block NSError *err = nil; - - // CreateSocket Block - // This block will be invoked within the dispatch block below. - - int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { - - int socketFD = socket(domain, SOCK_STREAM, 0); - - if (socketFD == SOCKET_NULL) - { - NSString *reason = @"Error in socket() function"; - err = [self errnoErrorWithReason:reason]; - - return SOCKET_NULL; - } - - int status; - - // Set socket options - - status = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (status == -1) - { - NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - int reuseOn = 1; - status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - if (status == -1) - { - NSString *reason = @"Error enabling address reuse (setsockopt)"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - // Bind socket - - const struct sockaddr *addr = (const struct sockaddr *)[interfaceAddr bytes]; - status = bind(socketFD, addr, addr->sa_len); - if (status == -1) - { - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - // Listen - - status = listen(socketFD, 1024); - if (status == -1) - { - NSString *reason = @"Error in listen() function"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - return socketFD; - }; - - // Create dispatch block and run on socketQueue - - dispatch_block_t block = ^{ @autoreleasepool { - - if (delegate == nil) // Must have delegate set - { - NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - if (![self isDisconnected]) // Must be disconnected - { - NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - // Resolve interface from description - - NSMutableData *interface4 = nil; - NSMutableData *interface6 = nil; - - [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port]; - - if ((interface4 == nil) && (interface6 == nil)) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv4Disabled && (interface6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && (interface4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - err = [self badParamError:msg]; - - return_from_block; - } - - BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil); - BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil); - - // Create sockets, configure, bind, and listen - - if (enableIPv4) - { - LogVerbose(@"Creating IPv4 socket"); - socket4FD = createSocket(AF_INET, interface4); - - if (socket4FD == SOCKET_NULL) - { - return_from_block; - } - } - - if (enableIPv6) - { - LogVerbose(@"Creating IPv6 socket"); - - if (enableIPv4 && (port == 0)) - { - // No specific port was specified, so we allowed the OS to pick an available port for us. - // Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket. - - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes]; - addr6->sin6_port = htons([self localPort4]); - } - - socket6FD = createSocket(AF_INET6, interface6); - - if (socket6FD == SOCKET_NULL) - { - if (socket4FD != SOCKET_NULL) - { - LogVerbose(@"close(socket4FD)"); - close(socket4FD); - } - - return_from_block; - } - } - - // Create accept sources - - if (enableIPv4) - { - accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue); - - int socketFD = socket4FD; - dispatch_source_t acceptSource = accept4Source; - - dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool { - - LogVerbose(@"event4Block"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([self doAccept:socketFD] && (++i < numPendingConnections)); - }}); - - dispatch_source_set_cancel_handler(accept4Source, ^{ - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(accept4Source)"); - dispatch_release(acceptSource); - #endif - - LogVerbose(@"close(socket4FD)"); - close(socketFD); - }); - - LogVerbose(@"dispatch_resume(accept4Source)"); - dispatch_resume(accept4Source); - } - - if (enableIPv6) - { - accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue); - - int socketFD = socket6FD; - dispatch_source_t acceptSource = accept6Source; - - dispatch_source_set_event_handler(accept6Source, ^{ @autoreleasepool { - - LogVerbose(@"event6Block"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([self doAccept:socketFD] && (++i < numPendingConnections)); - }}); - - dispatch_source_set_cancel_handler(accept6Source, ^{ - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(accept6Source)"); - dispatch_release(acceptSource); - #endif - - LogVerbose(@"close(socket6FD)"); - close(socketFD); - }); - - LogVerbose(@"dispatch_resume(accept6Source)"); - dispatch_resume(accept6Source); - } - - flags |= kSocketStarted; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - LogInfo(@"Error in accept: %@", err); - - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr; -{ - LogTrace(); - - __block BOOL result = NO; - __block NSError *err = nil; - - // CreateSocket Block - // This block will be invoked within the dispatch block below. - - int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { - - int socketFD = socket(domain, SOCK_STREAM, 0); - - if (socketFD == SOCKET_NULL) - { - NSString *reason = @"Error in socket() function"; - err = [self errnoErrorWithReason:reason]; - - return SOCKET_NULL; - } - - int status; - - // Set socket options - - status = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (status == -1) - { - NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - int reuseOn = 1; - status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - if (status == -1) - { - NSString *reason = @"Error enabling address reuse (setsockopt)"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - // Bind socket - - status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]); - if (status == -1) - { - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - // Listen - - status = listen(socketFD, 1024); - if (status == -1) - { - NSString *reason = @"Error in listen() function"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - return socketFD; - }; - - // Create dispatch block and run on socketQueue - - dispatch_block_t block = ^{ @autoreleasepool { - - if (delegate == nil) // Must have delegate set - { - NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - if (![self isDisconnected]) // Must be disconnected - { - NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - // Remove a previous socket - - NSError *error = nil; - NSFileManager *fileManager = [NSFileManager defaultManager]; - if ([fileManager fileExistsAtPath:url.path]) { - if (![[NSFileManager defaultManager] removeItemAtURL:url error:&error]) { - NSString *msg = @"Could not remove previous unix domain socket at given url."; - err = [self otherError:msg]; - - return_from_block; - } - } - - // Resolve interface from description - - NSData *interface = [self getInterfaceAddressFromUrl:url]; - - if (interface == nil) - { - NSString *msg = @"Invalid unix domain url. Specify a valid file url that does not exist (e.g. \"file:///tmp/socket\")"; - err = [self badParamError:msg]; - - return_from_block; - } - - // Create sockets, configure, bind, and listen - - LogVerbose(@"Creating unix domain socket"); - socketUN = createSocket(AF_UNIX, interface); - - if (socketUN == SOCKET_NULL) - { - return_from_block; - } - - socketUrl = url; - - // Create accept sources - - acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketUN, 0, socketQueue); - - int socketFD = socketUN; - dispatch_source_t acceptSource = acceptUNSource; - - dispatch_source_set_event_handler(acceptUNSource, ^{ @autoreleasepool { - - LogVerbose(@"eventUNBlock"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([self doAccept:socketFD] && (++i < numPendingConnections)); - }}); - - dispatch_source_set_cancel_handler(acceptUNSource, ^{ - -#if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(accept4Source)"); - dispatch_release(acceptSource); -#endif - - LogVerbose(@"close(socket4FD)"); - close(socketFD); - }); - - LogVerbose(@"dispatch_resume(accept4Source)"); - dispatch_resume(acceptUNSource); - - flags |= kSocketStarted; - - result = YES; - }}; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - LogInfo(@"Error in accept: %@", err); - - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (BOOL)doAccept:(int)parentSocketFD -{ - LogTrace(); - - int socketType; - int childSocketFD; - NSData *childSocketAddress; - - if (parentSocketFD == socket4FD) - { - socketType = 0; - - struct sockaddr_in addr; - socklen_t addrLen = sizeof(addr); - - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } - - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } - else if (parentSocketFD == socket6FD) - { - socketType = 1; - - struct sockaddr_in6 addr; - socklen_t addrLen = sizeof(addr); - - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } - - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } - else // if (parentSocketFD == socketUN) - { - socketType = 2; - - struct sockaddr_un addr; - socklen_t addrLen = sizeof(addr); - - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } - - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } - - // Enable non-blocking IO on the socket - - int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK); - if (result == -1) - { - LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)"); - return NO; - } - - // Prevent SIGPIPE signals - - int nosigpipe = 1; - setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - - // Notify delegate - - if (delegateQueue) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - // Query delegate for custom socket queue - - dispatch_queue_t childSocketQueue = NULL; - - if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)]) - { - childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress - onSocket:self]; - } - - // Create GCDAsyncSocket instance for accepted socket - - GCDAsyncSocket *acceptedSocket = [[GCDAsyncSocket alloc] initWithDelegate:theDelegate - delegateQueue:delegateQueue - socketQueue:childSocketQueue]; - - if (socketType == 0) - acceptedSocket->socket4FD = childSocketFD; - else if (socketType == 1) - acceptedSocket->socket6FD = childSocketFD; - else - acceptedSocket->socketUN = childSocketFD; - - acceptedSocket->flags = (kSocketStarted | kConnected); - - // Setup read and write sources for accepted socket - - dispatch_async(acceptedSocket->socketQueue, ^{ @autoreleasepool { - - [acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD]; - }}); - - // Notify delegate - - if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)]) - { - [theDelegate socket:self didAcceptNewSocket:acceptedSocket]; - } - - // Release the socket queue returned from the delegate (it was retained by acceptedSocket) - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (childSocketQueue) dispatch_release(childSocketQueue); - #endif - - // The accepted socket should have been retained by the delegate. - // Otherwise it gets properly released when exiting the block. - }}); - } - - return YES; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Connecting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method runs through the various checks required prior to a connection attempt. - * It is shared between the connectToHost and connectToAddress methods. - * -**/ -- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - if (delegate == nil) // Must have delegate set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (![self isDisconnected]) // Must be disconnected - { - if (errPtr) - { - NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - if (errPtr) - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (interface) - { - NSMutableData *interface4 = nil; - NSMutableData *interface6 = nil; - - [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0]; - - if ((interface4 == nil) && (interface6 == nil)) - { - if (errPtr) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - if (isIPv4Disabled && (interface6 == nil)) - { - if (errPtr) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - if (isIPv6Disabled && (interface4 == nil)) - { - if (errPtr) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - connectInterface4 = interface4; - connectInterface6 = interface6; - } - - // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - return YES; -} - -- (BOOL)preConnectWithUrl:(NSURL *)url error:(NSError **)errPtr -{ - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - if (delegate == nil) // Must have delegate set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (![self isDisconnected]) // Must be disconnected - { - if (errPtr) - { - NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - NSData *interface = [self getInterfaceAddressFromUrl:url]; - - if (interface == nil) - { - if (errPtr) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - connectInterfaceUN = interface; - - // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - return YES; -} - -- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr -{ - return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr]; -} - -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr]; -} - -- (BOOL)connectToHost:(NSString *)inHost - onPort:(uint16_t)port - viaInterface:(NSString *)inInterface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - LogTrace(); - - // Just in case immutable objects were passed - NSString *host = [inHost copy]; - NSString *interface = [inInterface copy]; - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Check for problems with host parameter - - if ([host length] == 0) - { - NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithInterface:interface error:&err]) - { - return_from_block; - } - - // We've made it past all the checks. - // It's time to start the connection process. - - flags |= kSocketStarted; - - LogVerbose(@"Dispatching DNS lookup..."); - - // It's possible that the given host parameter is actually a NSMutableString. - // So we want to copy it now, within this block that will be executed synchronously. - // This way the asynchronous lookup block below doesn't have to worry about it changing. - - int aConnectIndex = connectIndex; - NSString *hostCpy = [host copy]; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { - - [self lookup:aConnectIndex host:hostCpy port:port]; - }}); - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr -{ - return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr]; -} - -- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr -{ - return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr]; -} - -- (BOOL)connectToAddress:(NSData *)inRemoteAddr - viaInterface:(NSString *)inInterface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - LogTrace(); - - // Just in case immutable objects were passed - NSData *remoteAddr = [inRemoteAddr copy]; - NSString *interface = [inInterface copy]; - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Check for problems with remoteAddr parameter - - NSData *address4 = nil; - NSData *address6 = nil; - - if ([remoteAddr length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes]; - - if (sockaddr->sa_family == AF_INET) - { - if ([remoteAddr length] == sizeof(struct sockaddr_in)) - { - address4 = remoteAddr; - } - } - else if (sockaddr->sa_family == AF_INET6) - { - if ([remoteAddr length] == sizeof(struct sockaddr_in6)) - { - address6 = remoteAddr; - } - } - } - - if ((address4 == nil) && (address6 == nil)) - { - NSString *msg = @"A valid IPv4 or IPv6 address was not given"; - err = [self badParamError:msg]; - - return_from_block; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && (address4 != nil)) - { - NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && (address6 != nil)) - { - NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithInterface:interface error:&err]) - { - return_from_block; - } - - // We've made it past all the checks. - // It's time to start the connection process. - - if (![self connectWithAddress4:address4 address6:address6 error:&err]) - { - return_from_block; - } - - flags |= kSocketStarted; - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; -{ - LogTrace(); - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Check for problems with host parameter - - if ([url.path length] == 0) - { - NSString *msg = @"Invalid unix domain socket url."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithUrl:url error:&err]) - { - return_from_block; - } - - // We've made it past all the checks. - // It's time to start the connection process. - - flags |= kSocketStarted; - - // Start the normal connection process - - NSError *err = nil; - if (![self connectWithAddressUN:connectInterfaceUN error:&err]) - { - [self closeWithError:err]; - - return_from_block; - } - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port -{ - LogTrace(); - - // This method is executed on a global concurrent queue. - // It posts the results back to the socket queue. - // The lookupIndex is used to ignore the results if the connect operation was cancelled or timed out. - - NSError *error = nil; - - NSData *address4 = nil; - NSData *address6 = nil; - - - if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) - { - // Use LOOPBACK address - struct sockaddr_in nativeAddr; - nativeAddr.sin_len = sizeof(struct sockaddr_in); - nativeAddr.sin_family = AF_INET; - nativeAddr.sin_port = htons(port); - nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero)); - - struct sockaddr_in6 nativeAddr6; - nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); - nativeAddr6.sin6_family = AF_INET6; - nativeAddr6.sin6_port = htons(port); - nativeAddr6.sin6_flowinfo = 0; - nativeAddr6.sin6_addr = in6addr_loopback; - nativeAddr6.sin6_scope_id = 0; - - // Wrap the native address structures - address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)]; - address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - else - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - - struct addrinfo hints, *res, *res0; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); - - if (gai_error) - { - error = [self gaiError:gai_error]; - } - else - { - for(res = res0; res; res = res->ai_next) - { - if ((address4 == nil) && (res->ai_family == AF_INET)) - { - // Found IPv4 address - // Wrap the native address structure - address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - else if ((address6 == nil) && (res->ai_family == AF_INET6)) - { - // Found IPv6 address - // Wrap the native address structure - address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - } - freeaddrinfo(res0); - - if ((address4 == nil) && (address6 == nil)) - { - error = [self gaiError:EAI_FAIL]; - } - } - } - - if (error) - { - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self lookup:aConnectIndex didFail:error]; - }}); - } - else - { - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self lookup:aConnectIndex didSucceedWithAddress4:address4 address6:address6]; - }}); - } -} - -- (void)lookup:(int)aConnectIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6 -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert(address4 || address6, @"Expected at least one valid address"); - - if (aConnectIndex != connectIndex) - { - LogInfo(@"Ignoring lookupDidSucceed, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - // Check for problems - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && (address6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - if (isIPv6Disabled && (address4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - // Start the normal connection process - - NSError *err = nil; - if (![self connectWithAddress4:address4 address6:address6 error:&err]) - { - [self closeWithError:err]; - } -} - -/** - * This method is called if the DNS lookup fails. - * This method is executed on the socketQueue. - * - * Since the DNS lookup executed synchronously on a global concurrent queue, - * the original connection request may have already been cancelled or timed-out by the time this method is invoked. - * The lookupIndex tells us whether the lookup is still valid or not. -**/ -- (void)lookup:(int)aConnectIndex didFail:(NSError *)error -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aConnectIndex != connectIndex) - { - LogInfo(@"Ignoring lookup:didFail: - already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - [self endConnectTimeout]; - [self closeWithError:error]; -} - -- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]); - LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]); - - // Determine socket type - - BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; - - BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil)); - - // Create the socket - - int socketFD; - NSData *address; - NSData *connectInterface; - - if (useIPv6) - { - LogVerbose(@"Creating IPv6 socket"); - - socket6FD = socket(AF_INET6, SOCK_STREAM, 0); - - socketFD = socket6FD; - address = address6; - connectInterface = connectInterface6; - } - else - { - LogVerbose(@"Creating IPv4 socket"); - - socket4FD = socket(AF_INET, SOCK_STREAM, 0); - - socketFD = socket4FD; - address = address4; - connectInterface = connectInterface4; - } - - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; - - return NO; - } - - // Bind the socket to the desired interface (if needed) - - if (connectInterface) - { - LogVerbose(@"Binding socket..."); - - if ([[self class] portFromAddress:connectInterface] > 0) - { - // Since we're going to be binding to a specific port, - // we should turn on reuseaddr to allow us to override sockets in time_wait. - - int reuseOn = 1; - setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - } - - const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes]; - - int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]); - if (result != 0) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in bind() function"]; - - return NO; - } - } - - // Prevent SIGPIPE signals - - int nosigpipe = 1; - setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - - // Start the connection process in a background queue - - int aConnectIndex = connectIndex; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ - - int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]); - if (result == 0) - { - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self didConnect:aConnectIndex]; - }}); - } - else - { - NSError *error = [self errnoErrorWithReason:@"Error in connect() function"]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self didNotConnect:aConnectIndex error:error]; - }}); - } - }); - - LogVerbose(@"Connecting..."); - - return YES; -} - -- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - // Create the socket - - int socketFD; - - LogVerbose(@"Creating unix domain socket"); - - socketUN = socket(AF_UNIX, SOCK_STREAM, 0); - - socketFD = socketUN; - - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; - - return NO; - } - - // Bind the socket to the desired interface (if needed) - - LogVerbose(@"Binding socket..."); - - int reuseOn = 1; - setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - -// const struct sockaddr *interfaceAddr = (const struct sockaddr *)[address bytes]; -// -// int result = bind(socketFD, interfaceAddr, (socklen_t)[address length]); -// if (result != 0) -// { -// if (errPtr) -// *errPtr = [self errnoErrorWithReason:@"Error in bind() function"]; -// -// return NO; -// } - - // Prevent SIGPIPE signals - - int nosigpipe = 1; - setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - - // Start the connection process in a background queue - - int aConnectIndex = connectIndex; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ - - const struct sockaddr *addr = (const struct sockaddr *)[address bytes]; - int result = connect(socketFD, addr, addr->sa_len); - if (result == 0) - { - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self didConnect:aConnectIndex]; - }}); - } - else - { - // TODO: Bad file descriptor - perror("connect"); - NSError *error = [self errnoErrorWithReason:@"Error in connect() function"]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self didNotConnect:aConnectIndex error:error]; - }}); - } - }); - - LogVerbose(@"Connecting..."); - - return YES; -} - -- (void)didConnect:(int)aConnectIndex -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aConnectIndex != connectIndex) - { - LogInfo(@"Ignoring didConnect, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - flags |= kConnected; - - [self endConnectTimeout]; - - #if TARGET_OS_IPHONE - // The endConnectTimeout method executed above incremented the connectIndex. - aConnectIndex = connectIndex; - #endif - - // Setup read/write streams (as workaround for specific shortcomings in the iOS platform) - // - // Note: - // There may be configuration options that must be set by the delegate before opening the streams. - // The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream. - // - // Thus we wait until after the socket:didConnectToHost:port: delegate method has completed. - // This gives the delegate time to properly configure the streams if needed. - - dispatch_block_t SetupStreamsPart1 = ^{ - #if TARGET_OS_IPHONE - - if (![self createReadAndWriteStream]) - { - [self closeWithError:[self otherError:@"Error creating CFStreams"]]; - return; - } - - if (![self registerForStreamCallbacksIncludingReadWrite:NO]) - { - [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; - return; - } - - #endif - }; - dispatch_block_t SetupStreamsPart2 = ^{ - #if TARGET_OS_IPHONE - - if (aConnectIndex != connectIndex) - { - // The socket has been disconnected. - return; - } - - if (![self addStreamsToRunLoop]) - { - [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; - return; - } - - if (![self openStreams]) - { - [self closeWithError:[self otherError:@"Error creating CFStreams"]]; - return; - } - - #endif - }; - - // Notify delegate - - NSString *host = [self connectedHost]; - uint16_t port = [self connectedPort]; - NSURL *url = [self connectedUrl]; - - if (delegateQueue && host != nil && [delegate respondsToSelector:@selector(socket:didConnectToHost:port:)]) - { - SetupStreamsPart1(); - - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didConnectToHost:host port:port]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - SetupStreamsPart2(); - }}); - }}); - } - else if (delegateQueue && url != nil && [delegate respondsToSelector:@selector(socket:didConnectToUrl:)]) - { - SetupStreamsPart1(); - - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didConnectToUrl:url]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - SetupStreamsPart2(); - }}); - }}); - } - else - { - SetupStreamsPart1(); - SetupStreamsPart2(); - } - - // Get the connected socket - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - // Enable non-blocking IO on the socket - - int result = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (result == -1) - { - NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)"; - [self closeWithError:[self otherError:errMsg]]; - - return; - } - - // Setup our read/write sources - - [self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD]; - - // Dequeue any pending read/write requests - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; -} - -- (void)didNotConnect:(int)aConnectIndex error:(NSError *)error -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aConnectIndex != connectIndex) - { - LogInfo(@"Ignoring didNotConnect, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - [self closeWithError:error]; -} - -- (void)startConnectTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); - - dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool { - - [self doConnectTimeout]; - }}); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_source_t theConnectTimer = connectTimer; - dispatch_source_set_cancel_handler(connectTimer, ^{ - LogVerbose(@"dispatch_release(connectTimer)"); - dispatch_release(theConnectTimer); - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC)); - dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0); - - dispatch_resume(connectTimer); - } -} - -- (void)endConnectTimeout -{ - LogTrace(); - - if (connectTimer) - { - dispatch_source_cancel(connectTimer); - connectTimer = NULL; - } - - // Increment connectIndex. - // This will prevent us from processing results from any related background asynchronous operations. - // - // Note: This should be called from close method even if connectTimer is NULL. - // This is because one might disconnect a socket prior to a successful connection which had no timeout. - - connectIndex++; - - if (connectInterface4) - { - connectInterface4 = nil; - } - if (connectInterface6) - { - connectInterface6 = nil; - } -} - -- (void)doConnectTimeout -{ - LogTrace(); - - [self endConnectTimeout]; - [self closeWithError:[self connectTimeoutError]]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Disconnecting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)closeWithError:(NSError *)error -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - [self endConnectTimeout]; - - if (currentRead != nil) [self endCurrentRead]; - if (currentWrite != nil) [self endCurrentWrite]; - - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - [preBuffer reset]; - - #if TARGET_OS_IPHONE - { - if (readStream || writeStream) - { - [self removeStreamsFromRunLoop]; - - if (readStream) - { - CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL); - CFReadStreamClose(readStream); - CFRelease(readStream); - readStream = NULL; - } - if (writeStream) - { - CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL); - CFWriteStreamClose(writeStream); - CFRelease(writeStream); - writeStream = NULL; - } - } - } - #endif - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - { - [sslPreBuffer reset]; - sslErrCode = noErr; - - if (sslContext) - { - // Getting a linker error here about the SSLx() functions? - // You need to add the Security Framework to your application. - - SSLClose(sslContext); - - #if TARGET_OS_IPHONE - CFRelease(sslContext); - #else - SSLDisposeContext(sslContext); - #endif - - sslContext = NULL; - } - } - #endif - - // For some crazy reason (in my opinion), cancelling a dispatch source doesn't - // invoke the cancel handler if the dispatch source is paused. - // So we have to unpause the source if needed. - // This allows the cancel handler to be run, which in turn releases the source and closes the socket. - - if (!accept4Source && !accept6Source && !readSource && !writeSource) - { - LogVerbose(@"manually closing close"); - - if (socket4FD != SOCKET_NULL) - { - LogVerbose(@"close(socket4FD)"); - close(socket4FD); - socket4FD = SOCKET_NULL; - } - - if (socket6FD != SOCKET_NULL) - { - LogVerbose(@"close(socket6FD)"); - close(socket6FD); - socket6FD = SOCKET_NULL; - } - - if (socketUN != SOCKET_NULL) - { - LogVerbose(@"close(socketUN)"); - close(socketUN); - socketUN = SOCKET_NULL; - unlink(socketUrl.path.fileSystemRepresentation); - socketUrl = nil; - } - } - else - { - if (accept4Source) - { - LogVerbose(@"dispatch_source_cancel(accept4Source)"); - dispatch_source_cancel(accept4Source); - - // We never suspend accept4Source - - accept4Source = NULL; - } - - if (accept6Source) - { - LogVerbose(@"dispatch_source_cancel(accept6Source)"); - dispatch_source_cancel(accept6Source); - - // We never suspend accept6Source - - accept6Source = NULL; - } - - if (acceptUNSource) - { - LogVerbose(@"dispatch_source_cancel(acceptUNSource)"); - dispatch_source_cancel(acceptUNSource); - - // We never suspend acceptUNSource - - acceptUNSource = NULL; - } - - if (readSource) - { - LogVerbose(@"dispatch_source_cancel(readSource)"); - dispatch_source_cancel(readSource); - - [self resumeReadSource]; - - readSource = NULL; - } - - if (writeSource) - { - LogVerbose(@"dispatch_source_cancel(writeSource)"); - dispatch_source_cancel(writeSource); - - [self resumeWriteSource]; - - writeSource = NULL; - } - - // The sockets will be closed by the cancel handlers of the corresponding source - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - socketUN = SOCKET_NULL; - } - - // If the client has passed the connect/accept method, then the connection has at least begun. - // Notify delegate that it is now ending. - BOOL shouldCallDelegate = (flags & kSocketStarted); - - // Clear stored socket info and all flags (config remains as is) - socketFDBytesAvailable = 0; - flags = 0; - - if (shouldCallDelegate) - { - if (delegateQueue && [delegate respondsToSelector: @selector(socketDidDisconnect:withError:)]) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidDisconnect:self withError:error]; - }}); - } - } -} - -- (void)disconnect -{ - dispatch_block_t block = ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - [self closeWithError:nil]; - } - }}; - - // Synchronous disconnection, as documented in the header file - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} - -- (void)disconnectAfterReading -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - flags |= (kForbidReadsWrites | kDisconnectAfterReads); - [self maybeClose]; - } - }}); -} - -- (void)disconnectAfterWriting -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - flags |= (kForbidReadsWrites | kDisconnectAfterWrites); - [self maybeClose]; - } - }}); -} - -- (void)disconnectAfterReadingAndWriting -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); - [self maybeClose]; - } - }}); -} - -/** - * Closes the socket if possible. - * That is, if all writes have completed, and we're set to disconnect after writing, - * or if all reads have completed, and we're set to disconnect after reading. -**/ -- (void)maybeClose -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - BOOL shouldClose = NO; - - if (flags & kDisconnectAfterReads) - { - if (([readQueue count] == 0) && (currentRead == nil)) - { - if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - shouldClose = YES; - } - } - else - { - shouldClose = YES; - } - } - } - else if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - shouldClose = YES; - } - } - - if (shouldClose) - { - [self closeWithError:nil]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Errors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (NSError *)badConfigError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo]; -} - -- (NSError *)badParamError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo]; -} - -- (NSError *)gaiError:(int)gai_error -{ - NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; -} - -- (NSError *)errnoErrorWithReason:(NSString *)reason -{ - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, - reason, NSLocalizedFailureReasonErrorKey, nil]; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; -} - -- (NSError *)errnoError -{ - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; -} - -- (NSError *)sslError:(OSStatus)ssl_error -{ - NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h"; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:msg forKey:NSLocalizedRecoverySuggestionErrorKey]; - - return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo]; -} - -- (NSError *)connectTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Attempt to connect to host timed out", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo]; -} - -/** - * Returns a standard AsyncSocket maxed out error. -**/ -- (NSError *)readMaxedOutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Read operation reached set maximum length", nil); - - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info]; -} - -/** - * Returns a standard AsyncSocket write timeout error. -**/ -- (NSError *)readTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Read operation timed out", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo]; -} - -/** - * Returns a standard AsyncSocket write timeout error. -**/ -- (NSError *)writeTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Write operation timed out", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo]; -} - -- (NSError *)connectionClosedError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Socket closed by remote peer", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo]; -} - -- (NSError *)otherError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Diagnostics -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)isDisconnected -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (flags & kSocketStarted) ? NO : YES; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isConnected -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (flags & kConnected) ? YES : NO; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (NSString *)connectedHost -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self connectedHostFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self connectedHostFromSocket6:socket6FD]; - - return nil; - } - else - { - __block NSString *result = nil; - - dispatch_sync(socketQueue, ^{ @autoreleasepool { - - if (socket4FD != SOCKET_NULL) - result = [self connectedHostFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self connectedHostFromSocket6:socket6FD]; - }}); - - return result; - } -} - -- (uint16_t)connectedPort -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self connectedPortFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self connectedPortFromSocket6:socket6FD]; - - return 0; - } - else - { - __block uint16_t result = 0; - - dispatch_sync(socketQueue, ^{ - // No need for autorelease pool - - if (socket4FD != SOCKET_NULL) - result = [self connectedPortFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self connectedPortFromSocket6:socket6FD]; - }); - - return result; - } -} - -- (NSURL *)connectedUrl -{ - if (dispatch_get_current_queue() == socketQueue) - { - if (socketUN != SOCKET_NULL) - return [self connectedUrlFromSocketUN:socketUN]; - - return nil; - } - else - { - __block NSURL *result = nil; - - dispatch_sync(socketQueue, ^{ @autoreleasepool { - - if (socketUN != SOCKET_NULL) - result = [self connectedUrlFromSocketUN:socketUN]; - }}); - - return result; - } -} - -- (NSString *)localHost -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self localHostFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self localHostFromSocket6:socket6FD]; - - return nil; - } - else - { - __block NSString *result = nil; - - dispatch_sync(socketQueue, ^{ @autoreleasepool { - - if (socket4FD != SOCKET_NULL) - result = [self localHostFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self localHostFromSocket6:socket6FD]; - }}); - - return result; - } -} - -- (uint16_t)localPort -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self localPortFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self localPortFromSocket6:socket6FD]; - - return 0; - } - else - { - __block uint16_t result = 0; - - dispatch_sync(socketQueue, ^{ - // No need for autorelease pool - - if (socket4FD != SOCKET_NULL) - result = [self localPortFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self localPortFromSocket6:socket6FD]; - }); - - return result; - } -} - -- (NSString *)connectedHost4 -{ - if (socket4FD != SOCKET_NULL) - return [self connectedHostFromSocket4:socket4FD]; - - return nil; -} - -- (NSString *)connectedHost6 -{ - if (socket6FD != SOCKET_NULL) - return [self connectedHostFromSocket6:socket6FD]; - - return nil; -} - -- (uint16_t)connectedPort4 -{ - if (socket4FD != SOCKET_NULL) - return [self connectedPortFromSocket4:socket4FD]; - - return 0; -} - -- (uint16_t)connectedPort6 -{ - if (socket6FD != SOCKET_NULL) - return [self connectedPortFromSocket6:socket6FD]; - - return 0; -} - -- (NSString *)localHost4 -{ - if (socket4FD != SOCKET_NULL) - return [self localHostFromSocket4:socket4FD]; - - return nil; -} - -- (NSString *)localHost6 -{ - if (socket6FD != SOCKET_NULL) - return [self localHostFromSocket6:socket6FD]; - - return nil; -} - -- (uint16_t)localPort4 -{ - if (socket4FD != SOCKET_NULL) - return [self localPortFromSocket4:socket4FD]; - - return 0; -} - -- (uint16_t)localPort6 -{ - if (socket6FD != SOCKET_NULL) - return [self localPortFromSocket6:socket6FD]; - - return 0; -} - -- (NSString *)connectedHostFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr4:&sockaddr4]; -} - -- (NSString *)connectedHostFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr6:&sockaddr6]; -} - -- (uint16_t)connectedPortFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr4:&sockaddr4]; -} - -- (uint16_t)connectedPortFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr6:&sockaddr6]; -} - -- (NSURL *)connectedUrlFromSocketUN:(int)socketFD -{ - struct sockaddr_un sockaddr; - socklen_t sockaddrlen = sizeof(sockaddr); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0) - { - return 0; - } - return [[self class] urlFromSockaddrUN:&sockaddr]; -} - -- (NSString *)localHostFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr4:&sockaddr4]; -} - -- (NSString *)localHostFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr6:&sockaddr6]; -} - -- (uint16_t)localPortFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr4:&sockaddr4]; -} - -- (uint16_t)localPortFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr6:&sockaddr6]; -} - -- (NSData *)connectedAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - if (socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; - } - } - - if (socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; - } - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (NSData *)localAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - if (socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; - } - } - - if (socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; - } - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPv4 -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (socket4FD != SOCKET_NULL); - } - else - { - __block BOOL result = NO; - - dispatch_sync(socketQueue, ^{ - result = (socket4FD != SOCKET_NULL); - }); - - return result; - } -} - -- (BOOL)isIPv6 -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (socket6FD != SOCKET_NULL); - } - else - { - __block BOOL result = NO; - - dispatch_sync(socketQueue, ^{ - result = (socket6FD != SOCKET_NULL); - }); - - return result; - } -} - -- (BOOL)isSecure -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (flags & kSocketSecure) ? YES : NO; - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = (flags & kSocketSecure) ? YES : NO; - }); - - return result; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Utilities -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Finds the address of an interface description. - * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34). - * - * The interface description may optionally contain a port number at the end, separated by a colon. - * If a non-zero port parameter is provided, any port number in the interface description is ignored. - * - * The returned value is a 'struct sockaddr' wrapped in an NSMutableData object. -**/ -- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr - address6:(NSMutableData **)interfaceAddr6Ptr - fromDescription:(NSString *)interfaceDescription - port:(uint16_t)port -{ - NSMutableData *addr4 = nil; - NSMutableData *addr6 = nil; - - NSString *interface = nil; - - NSArray *components = [interfaceDescription componentsSeparatedByString:@":"]; - if ([components count] > 0) - { - NSString *temp = [components objectAtIndex:0]; - if ([temp length] > 0) - { - interface = temp; - } - } - if ([components count] > 1 && port == 0) - { - long portL = strtol([[components objectAtIndex:1] UTF8String], NULL, 10); - - if (portL > 0 && portL <= UINT16_MAX) - { - port = (uint16_t)portL; - } - } - - if (interface == nil) - { - // ANY address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_any; - - addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) - { - // LOOPBACK address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_loopback; - - addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else - { - const char *iface = [interface UTF8String]; - - struct ifaddrs *addrs; - const struct ifaddrs *cursor; - - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) - { - // IPv4 - - struct sockaddr_in nativeAddr4; - memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4)); - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - nativeAddr4.sin_port = htons(port); - - addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - else - { - char ip[INET_ADDRSTRLEN]; - - const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - nativeAddr4.sin_port = htons(port); - - addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - } - } - else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) - { - // IPv6 - - struct sockaddr_in6 nativeAddr6; - memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6)); - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - else - { - char ip[INET6_ADDRSTRLEN]; - - const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - } - } - - cursor = cursor->ifa_next; - } - - freeifaddrs(addrs); - } - } - - if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; - if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; -} - -- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url; -{ - NSString *path = url.path; - if (path.length == 0) { - return nil; - } - - struct sockaddr_un nativeAddr; - nativeAddr.sun_family = AF_UNIX; - strcpy(nativeAddr.sun_path, path.fileSystemRepresentation); - nativeAddr.sun_len = SUN_LEN(&nativeAddr); - NSData *interface = [NSData dataWithBytes:&nativeAddr length:sizeof(struct sockaddr_un)]; - - return interface; -} - -- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD -{ - readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue); - writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue); - - // Setup event handlers - - dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool { - - LogVerbose(@"readEventBlock"); - - socketFDBytesAvailable = dispatch_source_get_data(readSource); - LogVerbose(@"socketFDBytesAvailable: %lu", socketFDBytesAvailable); - - if (socketFDBytesAvailable > 0) - [self doReadData]; - else - [self doReadEOF]; - }}); - - dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool { - - LogVerbose(@"writeEventBlock"); - - flags |= kSocketCanAcceptBytes; - [self doWriteData]; - }}); - - // Setup cancel handlers - - __block int socketFDRefCount = 2; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_source_t theReadSource = readSource; - dispatch_source_t theWriteSource = writeSource; - #endif - - dispatch_source_set_cancel_handler(readSource, ^{ - - LogVerbose(@"readCancelBlock"); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(readSource)"); - dispatch_release(theReadSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } - }); - - dispatch_source_set_cancel_handler(writeSource, ^{ - - LogVerbose(@"writeCancelBlock"); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(writeSource)"); - dispatch_release(theWriteSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } - }); - - // We will not be able to read until data arrives. - // But we should be able to write immediately. - - socketFDBytesAvailable = 0; - flags &= ~kReadSourceSuspended; - - LogVerbose(@"dispatch_resume(readSource)"); - dispatch_resume(readSource); - - flags |= kSocketCanAcceptBytes; - flags |= kWriteSourceSuspended; -} - -- (BOOL)usingCFStreamForTLS -{ - #if TARGET_OS_IPHONE - { - if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) - { - // Due to the fact that Apple doesn't give us the full power of SecureTransport on iOS, - // we are relegated to using the slower, less powerful, and RunLoop based CFStream API. :( Boo! - // - // Thus we're not able to use the GCD read/write sources in this particular scenario. - - return YES; - } - } - #endif - - return NO; -} - -- (BOOL)usingSecureTransportForTLS -{ - #if TARGET_OS_IPHONE - { - return ![self usingCFStreamForTLS]; - } - #endif - - return YES; -} - -- (void)suspendReadSource -{ - if (!(flags & kReadSourceSuspended)) - { - LogVerbose(@"dispatch_suspend(readSource)"); - - dispatch_suspend(readSource); - flags |= kReadSourceSuspended; - } -} - -- (void)resumeReadSource -{ - if (flags & kReadSourceSuspended) - { - LogVerbose(@"dispatch_resume(readSource)"); - - dispatch_resume(readSource); - flags &= ~kReadSourceSuspended; - } -} - -- (void)suspendWriteSource -{ - if (!(flags & kWriteSourceSuspended)) - { - LogVerbose(@"dispatch_suspend(writeSource)"); - - dispatch_suspend(writeSource); - flags |= kWriteSourceSuspended; - } -} - -- (void)resumeWriteSource -{ - if (flags & kWriteSourceSuspended) - { - LogVerbose(@"dispatch_resume(writeSource)"); - - dispatch_resume(writeSource); - flags &= ~kWriteSourceSuspended; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Reading -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; -} - -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; -} - -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag -{ - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:length - timeout:timeout - readLength:0 - terminator:nil - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag]; -} - -- (void)readDataToLength:(NSUInteger)length - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - if (length == 0) { - LogWarn(@"Cannot read: length == 0"); - return; - } - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:0 - timeout:timeout - readLength:length - terminator:nil - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; -} - -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; -} - -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag]; -} - -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)maxLength - tag:(long)tag -{ - if ([data length] == 0) { - LogWarn(@"Cannot read: [data length] == 0"); - return; - } - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - if (maxLength > 0 && maxLength < [data length]) { - LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:maxLength - timeout:timeout - readLength:0 - terminator:data - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr -{ - __block float result = 0.0F; - - dispatch_block_t block = ^{ - - if (!currentRead || ![currentRead isKindOfClass:[GCDAsyncReadPacket class]]) - { - // We're not reading anything right now. - - if (tagPtr != NULL) *tagPtr = 0; - if (donePtr != NULL) *donePtr = 0; - if (totalPtr != NULL) *totalPtr = 0; - - result = NAN; - } - else - { - // It's only possible to know the progress of our read if we're reading to a certain length. - // If we're reading to data, we of course have no idea when the data will arrive. - // If we're reading to timeout, then we have no idea when the next chunk of data will arrive. - - NSUInteger done = currentRead->bytesDone; - NSUInteger total = currentRead->readLength; - - if (tagPtr != NULL) *tagPtr = currentRead->tag; - if (donePtr != NULL) *donePtr = done; - if (totalPtr != NULL) *totalPtr = total; - - if (total > 0) - result = (float)done / (float)total; - else - result = 1.0F; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -/** - * This method starts a new read, if needed. - * - * It is called when: - * - a user requests a read - * - after a read request has finished (to handle the next request) - * - immediately after the socket opens to handle any pending requests - * - * This method also handles auto-disconnect post read/write completion. -**/ -- (void)maybeDequeueRead -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - // If we're not currently processing a read AND we have an available read stream - if ((currentRead == nil) && (flags & kConnected)) - { - if ([readQueue count] > 0) - { - // Dequeue the next object in the write queue - currentRead = [readQueue objectAtIndex:0]; - [readQueue removeObjectAtIndex:0]; - - - if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]]) - { - LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); - - // Attempt to start TLS - flags |= kStartingReadTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - LogVerbose(@"Dequeued GCDAsyncReadPacket"); - - // Setup read timer (if needed) - [self setupReadTimerWithTimeout:currentRead->timeout]; - - // Immediately read, if possible - [self doReadData]; - } - } - else if (flags & kDisconnectAfterReads) - { - if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - [self closeWithError:nil]; - } - } - else - { - [self closeWithError:nil]; - } - } - else if (flags & kSocketSecure) - { - [self flushSSLBuffers]; - - // Edge case: - // - // We just drained all data from the ssl buffers, - // and all known data from the socket (socketFDBytesAvailable). - // - // If we didn't get any data from this process, - // then we may have reached the end of the TCP stream. - // - // Be sure callbacks are enabled so we're notified about a disconnection. - - if ([preBuffer availableBytes] == 0) - { - if ([self usingCFStreamForTLS]) { - // Callbacks never disabled - } - else { - [self resumeReadSource]; - } - } - } - } -} - -- (void)flushSSLBuffers -{ - LogTrace(); - - NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket"); - - if ([preBuffer availableBytes] > 0) - { - // Only flush the ssl buffers if the prebuffer is empty. - // This is to avoid growing the prebuffer inifinitely large. - - return; - } - -#if TARGET_OS_IPHONE - - if ([self usingCFStreamForTLS]) - { - if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) - { - LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); - - CFIndex defaultBytesToRead = (1024 * 4); - - [preBuffer ensureCapacityForWrite:defaultBytesToRead]; - - uint8_t *buffer = [preBuffer writeBuffer]; - - CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead); - LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result); - - if (result > 0) - { - [preBuffer didWrite:result]; - } - - flags &= ~kSecureSocketHasBytesAvailable; - } - - return; - } - -#endif -#if SECURE_TRANSPORT_MAYBE_AVAILABLE - - __block NSUInteger estimatedBytesAvailable = 0; - - dispatch_block_t updateEstimatedBytesAvailable = ^{ - - // Figure out if there is any data available to be read - // - // socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket - // [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket - // sslInternalBufSize <- Number of decrypted bytes SecureTransport has buffered - // - // We call the variable "estimated" because we don't know how many decrypted bytes we'll get - // from the encrypted bytes in the sslPreBuffer. - // However, we do know this is an upper bound on the estimation. - - estimatedBytesAvailable = socketFDBytesAvailable + [sslPreBuffer availableBytes]; - - size_t sslInternalBufSize = 0; - SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); - - estimatedBytesAvailable += sslInternalBufSize; - }; - - updateEstimatedBytesAvailable(); - - if (estimatedBytesAvailable > 0) - { - LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); - - BOOL done = NO; - do - { - LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable); - - // Make sure there's enough room in the prebuffer - - [preBuffer ensureCapacityForWrite:estimatedBytesAvailable]; - - // Read data into prebuffer - - uint8_t *buffer = [preBuffer writeBuffer]; - size_t bytesRead = 0; - - OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead); - LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead); - - if (bytesRead > 0) - { - [preBuffer didWrite:bytesRead]; - } - - LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]); - - if (result != noErr) - { - done = YES; - } - else - { - updateEstimatedBytesAvailable(); - } - - } while (!done && estimatedBytesAvailable > 0); - } - -#endif -} - -- (void)doReadData -{ - LogTrace(); - - // This method is called on the socketQueue. - // It might be called directly, or via the readSource when data is available to be read. - - if ((currentRead == nil) || (flags & kReadsPaused)) - { - LogVerbose(@"No currentRead or kReadsPaused"); - - // Unable to read at this time - - if (flags & kSocketSecure) - { - // Here's the situation: - // - // We have an established secure connection. - // There may not be a currentRead, but there might be encrypted data sitting around for us. - // When the user does get around to issuing a read, that encrypted data will need to be decrypted. - // - // So why make the user wait? - // We might as well get a head start on decrypting some data now. - // - // The other reason we do this has to do with detecting a socket disconnection. - // The SSL/TLS protocol has it's own disconnection handshake. - // So when a secure socket is closed, a "goodbye" packet comes across the wire. - // We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection. - - [self flushSSLBuffers]; - } - - if ([self usingCFStreamForTLS]) - { - // CFReadStream only fires once when there is available data. - // It won't fire again until we've invoked CFReadStreamRead. - } - else - { - // If the readSource is firing, we need to pause it - // or else it will continue to fire over and over again. - // - // If the readSource is not firing, - // we want it to continue monitoring the socket. - - if (socketFDBytesAvailable > 0) - { - [self suspendReadSource]; - } - } - return; - } - - BOOL hasBytesAvailable; - unsigned long estimatedBytesAvailable = 0; - - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // Relegated to using CFStream... :( Boo! Give us a full SecureTransport stack Apple! - - estimatedBytesAvailable = 0; - if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) - hasBytesAvailable = YES; - else - hasBytesAvailable = NO; - - #endif - } - else - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - - estimatedBytesAvailable = socketFDBytesAvailable; - - if (flags & kSocketSecure) - { - // There are 2 buffers to be aware of here. - // - // We are using SecureTransport, a TLS/SSL security layer which sits atop TCP. - // We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction. - // Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport. - // SecureTransport then decrypts the data, and finally returns the decrypted data back to us. - // - // The first buffer is one we create. - // SecureTransport often requests small amounts of data. - // This has to do with the encypted packets that are coming across the TCP stream. - // But it's non-optimal to do a bunch of small reads from the BSD socket. - // So our SSLReadFunction reads all available data from the socket (optimizing the sys call) - // and may store excess in the sslPreBuffer. - - estimatedBytesAvailable += [sslPreBuffer availableBytes]; - - // The second buffer is within SecureTransport. - // As mentioned earlier, there are encrypted packets coming across the TCP stream. - // SecureTransport needs the entire packet to decrypt it. - // But if the entire packet produces X bytes of decrypted data, - // and we only asked SecureTransport for X/2 bytes of data, - // it must store the extra X/2 bytes of decrypted data for the next read. - // - // The SSLGetBufferedReadSize function will tell us the size of this internal buffer. - // From the documentation: - // - // "This function does not block or cause any low-level read operations to occur." - - size_t sslInternalBufSize = 0; - SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); - - estimatedBytesAvailable += sslInternalBufSize; - } - - hasBytesAvailable = (estimatedBytesAvailable > 0); - - #endif - } - - if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0)) - { - LogVerbose(@"No data available to read..."); - - // No data available to read. - - if (![self usingCFStreamForTLS]) - { - // Need to wait for readSource to fire and notify us of - // available data in the socket's internal read buffer. - - [self resumeReadSource]; - } - return; - } - - if (flags & kStartingReadTLS) - { - LogVerbose(@"Waiting for SSL/TLS handshake to complete"); - - // The readQueue is waiting for SSL/TLS handshake to complete. - - if (flags & kStartingWriteTLS) - { - if ([self usingSecureTransportForTLS]) - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - - // We are in the process of a SSL Handshake. - // We were waiting for incoming data which has just arrived. - - [self ssl_continueSSLHandshake]; - - #endif - } - } - else - { - // We are still waiting for the writeQueue to drain and start the SSL/TLS process. - // We now know data is available to read. - - if (![self usingCFStreamForTLS]) - { - // Suspend the read source or else it will continue to fire nonstop. - - [self suspendReadSource]; - } - } - - return; - } - - BOOL done = NO; // Completed read operation - NSError *error = nil; // Error occured - - NSUInteger totalBytesReadForCurrentRead = 0; - - // - // STEP 1 - READ FROM PREBUFFER - // - - if ([preBuffer availableBytes] > 0) - { - // There are 3 types of read packets: - // - // 1) Read all available data. - // 2) Read a specific length of data. - // 3) Read up to a particular terminator. - - NSUInteger bytesToCopy; - - if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - } - else - { - // Read type #1 or #2 - - bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]]; - } - - // Make sure we have enough room in the buffer for our read. - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; - - // Copy bytes from prebuffer into packet buffer - - uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + - currentRead->bytesDone; - - memcpy(buffer, [preBuffer readBuffer], bytesToCopy); - - // Remove the copied bytes from the preBuffer - [preBuffer didRead:bytesToCopy]; - - LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]); - - // Update totals - - currentRead->bytesDone += bytesToCopy; - totalBytesReadForCurrentRead += bytesToCopy; - - // Check to see if the read operation is done - - if (currentRead->readLength > 0) - { - // Read type #2 - read a specific length of data - - done = (currentRead->bytesDone == currentRead->readLength); - } - else if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method - - if (!done && currentRead->maxLength > 0) - { - // We're not done and there's a set maxLength. - // Have we reached that maxLength yet? - - if (currentRead->bytesDone >= currentRead->maxLength) - { - error = [self readMaxedOutError]; - } - } - } - else - { - // Read type #1 - read all available data - // - // We're done as soon as - // - we've read all available data (in prebuffer and socket) - // - we've read the maxLength of read packet. - - done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength)); - } - - } - - // - // STEP 2 - READ FROM SOCKET - // - - BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to via socket (end of file) - BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more - - if (!done && !error && !socketEOF && !waiting && hasBytesAvailable) - { - NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic"); - - // There are 3 types of read packets: - // - // 1) Read all available data. - // 2) Read a specific length of data. - // 3) Read up to a particular terminator. - - BOOL readIntoPreBuffer = NO; - NSUInteger bytesToRead; - - if ([self usingCFStreamForTLS]) - { - // Since Apple hasn't made the full power of SecureTransport available on iOS, - // we are relegated to using the slower, less powerful, RunLoop based CFStream API. - // - // This API doesn't tell us how much data is available on the socket to be read. - // If we had that information we could optimize our memory allocations, and sys calls. - // - // But alas... - // So we do it old school, and just read as much data from the socket as we can. - - NSUInteger defaultReadLength = (1024 * 32); - - bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength - shouldPreBuffer:&readIntoPreBuffer]; - } - else - { - if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable - shouldPreBuffer:&readIntoPreBuffer]; - } - else - { - // Read type #1 or #2 - - bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable]; - } - } - - if (bytesToRead > SIZE_MAX) // NSUInteger may be bigger than size_t (read param 3) - { - bytesToRead = SIZE_MAX; - } - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - uint8_t *buffer; - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + currentRead->bytesDone; - } - - // Read data into buffer - - size_t bytesRead = 0; - - if (flags & kSocketSecure) - { - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead); - LogVerbose(@"CFReadStreamRead(): result = %i", (int)result); - - if (result < 0) - { - error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream); - } - else if (result == 0) - { - socketEOF = YES; - } - else - { - waiting = YES; - bytesRead = (size_t)result; - } - - // We only know how many decrypted bytes were read. - // The actual number of bytes read was likely more due to the overhead of the encryption. - // So we reset our flag, and rely on the next callback to alert us of more data. - flags &= ~kSecureSocketHasBytesAvailable; - - #endif - } - else - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - - // The documentation from Apple states: - // - // "a read operation might return errSSLWouldBlock, - // indicating that less data than requested was actually transferred" - // - // However, starting around 10.7, the function will sometimes return noErr, - // even if it didn't read as much data as requested. So we need to watch out for that. - - OSStatus result; - do - { - void *loop_buffer = buffer + bytesRead; - size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead; - size_t loop_bytesRead = 0; - - result = SSLRead(sslContext, loop_buffer, loop_bytesToRead, &loop_bytesRead); - LogVerbose(@"read from secure socket = %u", (unsigned)bytesRead); - - bytesRead += loop_bytesRead; - - } while ((result == noErr) && (bytesRead < bytesToRead)); - - - if (result != noErr) - { - if (result == errSSLWouldBlock) - waiting = YES; - else - { - if (result == errSSLClosedGraceful || result == errSSLClosedAbort) - { - // We've reached the end of the stream. - // Handle this the same way we would an EOF from the socket. - socketEOF = YES; - sslErrCode = result; - } - else - { - error = [self sslError:result]; - } - } - // It's possible that bytesRead > 0, even if the result was errSSLWouldBlock. - // This happens when the SSLRead function is able to read some data, - // but not the entire amount we requested. - - if (bytesRead <= 0) - { - bytesRead = 0; - } - } - - // Do not modify socketFDBytesAvailable. - // It will be updated via the SSLReadFunction(). - - #endif - } - } - else - { - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - ssize_t result = read(socketFD, buffer, (size_t)bytesToRead); - LogVerbose(@"read from socket = %i", (int)result); - - if (result < 0) - { - if (errno == EWOULDBLOCK) - waiting = YES; - else - error = [self errnoErrorWithReason:@"Error in read() function"]; - - socketFDBytesAvailable = 0; - } - else if (result == 0) - { - socketEOF = YES; - socketFDBytesAvailable = 0; - } - else - { - bytesRead = result; - - if (bytesRead < bytesToRead) - { - // The read returned less data than requested. - // This means socketFDBytesAvailable was a bit off due to timing, - // because we read from the socket right when the readSource event was firing. - socketFDBytesAvailable = 0; - } - else - { - if (socketFDBytesAvailable <= bytesRead) - socketFDBytesAvailable = 0; - else - socketFDBytesAvailable -= bytesRead; - } - - if (socketFDBytesAvailable == 0) - { - waiting = YES; - } - } - } - - if (bytesRead > 0) - { - // Check to see if the read operation is done - - if (currentRead->readLength > 0) - { - // Read type #2 - read a specific length of data - // - // Note: We should never be using a prebuffer when we're reading a specific length of data. - - NSAssert(readIntoPreBuffer == NO, @"Invalid logic"); - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - - done = (currentRead->bytesDone == currentRead->readLength); - } - else if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - if (readIntoPreBuffer) - { - // We just read a big chunk of data into the preBuffer - - [preBuffer didWrite:bytesRead]; - LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", [preBuffer availableBytes]); - - // Search for the terminating sequence - - bytesToRead = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToRead); - - // Ensure there's room on the read packet's buffer - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - // Copy bytes from prebuffer into read buffer - - uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset - + currentRead->bytesDone; - - memcpy(readBuf, [preBuffer readBuffer], bytesToRead); - - // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesToRead]; - LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - - // Update totals - currentRead->bytesDone += bytesToRead; - totalBytesReadForCurrentRead += bytesToRead; - - // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above - } - else - { - // We just read a big chunk of data directly into the packet's buffer. - // We need to move any overflow into the prebuffer. - - NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead]; - - if (overflow == 0) - { - // Perfect match! - // Every byte we read stays in the read buffer, - // and the last byte we read was the last byte of the term. - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - done = YES; - } - else if (overflow > 0) - { - // The term was found within the data that we read, - // and there are extra bytes that extend past the end of the term. - // We need to move these excess bytes out of the read packet and into the prebuffer. - - NSInteger underflow = bytesRead - overflow; - - // Copy excess data into preBuffer - - LogVerbose(@"copying %ld overflow bytes into preBuffer", (long)overflow); - [preBuffer ensureCapacityForWrite:overflow]; - - uint8_t *overflowBuffer = buffer + underflow; - memcpy([preBuffer writeBuffer], overflowBuffer, overflow); - - [preBuffer didWrite:overflow]; - LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - - // Note: The completeCurrentRead method will trim the buffer for us. - - currentRead->bytesDone += underflow; - totalBytesReadForCurrentRead += underflow; - done = YES; - } - else - { - // The term was not found within the data that we read. - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - done = NO; - } - } - - if (!done && currentRead->maxLength > 0) - { - // We're not done and there's a set maxLength. - // Have we reached that maxLength yet? - - if (currentRead->bytesDone >= currentRead->maxLength) - { - error = [self readMaxedOutError]; - } - } - } - else - { - // Read type #1 - read all available data - - if (readIntoPreBuffer) - { - // We just read a chunk of data into the preBuffer - - [preBuffer didWrite:bytesRead]; - - // Now copy the data into the read packet. - // - // Recall that we didn't read directly into the packet's buffer to avoid - // over-allocating memory since we had no clue how much data was available to be read. - // - // Ensure there's room on the read packet's buffer - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesRead]; - - // Copy bytes from prebuffer into read buffer - - uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset - + currentRead->bytesDone; - - memcpy(readBuf, [preBuffer readBuffer], bytesRead); - - // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesRead]; - - // Update totals - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - } - else - { - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - } - - done = YES; - } - - } // if (bytesRead > 0) - - } // if (!done && !error && !socketEOF && !waiting && hasBytesAvailable) - - - if (!done && currentRead->readLength == 0 && currentRead->term == nil) - { - // Read type #1 - read all available data - // - // We might arrive here if we read data from the prebuffer but not from the socket. - - done = (totalBytesReadForCurrentRead > 0); - } - - // Check to see if we're done, or if we've made progress - - if (done) - { - [self completeCurrentRead]; - - if (!error && (!socketEOF || [preBuffer availableBytes] > 0)) - { - [self maybeDequeueRead]; - } - } - else if (totalBytesReadForCurrentRead > 0) - { - // We're not done read type #2 or #3 yet, but we have read in some bytes - - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)]) - { - __strong id theDelegate = delegate; - long theReadTag = currentRead->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag]; - }}); - } - } - - // Check for errors - - if (error) - { - [self closeWithError:error]; - } - else if (socketEOF) - { - [self doReadEOF]; - } - else if (waiting) - { - if (![self usingCFStreamForTLS]) - { - // Monitor the socket for readability (if we're not already doing so) - [self resumeReadSource]; - } - } - - // Do not add any code here without first adding return statements in the error cases above. -} - -- (void)doReadEOF -{ - LogTrace(); - - // This method may be called more than once. - // If the EOF is read while there is still data in the preBuffer, - // then this method may be called continually after invocations of doReadData to see if it's time to disconnect. - - flags |= kSocketHasReadEOF; - - if (flags & kSocketSecure) - { - // If the SSL layer has any buffered data, flush it into the preBuffer now. - - [self flushSSLBuffers]; - } - - BOOL shouldDisconnect; - NSError *error = nil; - - if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS)) - { - // We received an EOF during or prior to startTLS. - // The SSL/TLS handshake is now impossible, so this is an unrecoverable situation. - - shouldDisconnect = YES; - - if ([self usingSecureTransportForTLS]) - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - error = [self sslError:errSSLClosedAbort]; - #endif - } - } - else if (flags & kReadStreamClosed) - { - // The preBuffer has already been drained. - // The config allows half-duplex connections. - // We've previously checked the socket, and it appeared writeable. - // So we marked the read stream as closed and notified the delegate. - // - // As per the half-duplex contract, the socket will be closed when a write fails, - // or when the socket is manually closed. - - shouldDisconnect = NO; - } - else if ([preBuffer availableBytes] > 0) - { - LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer"); - - // Although we won't be able to read any more data from the socket, - // there is existing data that has been prebuffered that we can read. - - shouldDisconnect = NO; - } - else if (config & kAllowHalfDuplexConnection) - { - // We just received an EOF (end of file) from the socket's read stream. - // This means the remote end of the socket (the peer we're connected to) - // has explicitly stated that it will not be sending us any more data. - // - // Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us) - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - struct pollfd pfd[1]; - pfd[0].fd = socketFD; - pfd[0].events = POLLOUT; - pfd[0].revents = 0; - - poll(pfd, 1, 0); - - if (pfd[0].revents & POLLOUT) - { - // Socket appears to still be writeable - - shouldDisconnect = NO; - flags |= kReadStreamClosed; - - // Notify the delegate that we're going half-duplex - - if (delegateQueue && [delegate respondsToSelector:@selector(socketDidCloseReadStream:)]) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidCloseReadStream:self]; - }}); - } - } - else - { - shouldDisconnect = YES; - } - } - else - { - shouldDisconnect = YES; - } - - - if (shouldDisconnect) - { - if (error == nil) - { - if ([self usingSecureTransportForTLS]) - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful) - { - error = [self sslError:sslErrCode]; - } - else - { - error = [self connectionClosedError]; - } - #endif - } - else - { - error = [self connectionClosedError]; - } - } - [self closeWithError:error]; - } - else - { - if (![self usingCFStreamForTLS]) - { - // Suspend the read source (if needed) - - [self suspendReadSource]; - } - } -} - -- (void)completeCurrentRead -{ - LogTrace(); - - NSAssert(currentRead, @"Trying to complete current read when there is no current read."); - - - NSData *result; - - if (currentRead->bufferOwner) - { - // We created the buffer on behalf of the user. - // Trim our buffer to be the proper size. - [currentRead->buffer setLength:currentRead->bytesDone]; - - result = currentRead->buffer; - } - else - { - // We did NOT create the buffer. - // The buffer is owned by the caller. - // Only trim the buffer if we had to increase its size. - - if ([currentRead->buffer length] > currentRead->originalBufferLength) - { - NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone; - NSUInteger origSize = currentRead->originalBufferLength; - - NSUInteger buffSize = MAX(readSize, origSize); - - [currentRead->buffer setLength:buffSize]; - } - - uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset; - - result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO]; - } - - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadData:withTag:)]) - { - __strong id theDelegate = delegate; - GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didReadData:result withTag:theRead->tag]; - }}); - } - - [self endCurrentRead]; -} - -- (void)endCurrentRead -{ - if (readTimer) - { - dispatch_source_cancel(readTimer); - readTimer = NULL; - } - - currentRead = nil; -} - -- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); - - dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool { - - [self doReadTimeout]; - }}); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_source_t theReadTimer = readTimer; - dispatch_source_set_cancel_handler(readTimer, ^{ - LogVerbose(@"dispatch_release(readTimer)"); - dispatch_release(theReadTimer); - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC)); - - dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(readTimer); - } -} - -- (void)doReadTimeout -{ - // This is a little bit tricky. - // Ideally we'd like to synchronously query the delegate about a timeout extension. - // But if we do so synchronously we risk a possible deadlock. - // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. - - flags |= kReadsPaused; - - if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) - { - __strong id theDelegate = delegate; - GCDAsyncReadPacket *theRead = currentRead; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - NSTimeInterval timeoutExtension = 0.0; - - timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag - elapsed:theRead->timeout - bytesDone:theRead->bytesDone]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self doReadTimeoutWithExtension:timeoutExtension]; - }}); - }}); - } - else - { - [self doReadTimeoutWithExtension:0.0]; - } -} - -- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension -{ - if (currentRead) - { - if (timeoutExtension > 0.0) - { - currentRead->timeout += timeoutExtension; - - // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeoutExtension * NSEC_PER_SEC)); - dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); - - // Unpause reads, and continue - flags &= ~kReadsPaused; - [self doReadData]; - } - else - { - LogVerbose(@"ReadTimeout"); - - [self closeWithError:[self readTimeoutError]]; - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Writing -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - if ([data length] == 0) return; - - GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [writeQueue addObject:packet]; - [self maybeDequeueWrite]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr -{ - __block float result = 0.0F; - - dispatch_block_t block = ^{ - - if (!currentWrite || ![currentWrite isKindOfClass:[GCDAsyncWritePacket class]]) - { - // We're not writing anything right now. - - if (tagPtr != NULL) *tagPtr = 0; - if (donePtr != NULL) *donePtr = 0; - if (totalPtr != NULL) *totalPtr = 0; - - result = NAN; - } - else - { - NSUInteger done = currentWrite->bytesDone; - NSUInteger total = [currentWrite->buffer length]; - - if (tagPtr != NULL) *tagPtr = currentWrite->tag; - if (donePtr != NULL) *donePtr = done; - if (totalPtr != NULL) *totalPtr = total; - - result = (float)done / (float)total; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -/** - * Conditionally starts a new write. - * - * It is called when: - * - a user requests a write - * - after a write request has finished (to handle the next request) - * - immediately after the socket opens to handle any pending requests - * - * This method also handles auto-disconnect post read/write completion. -**/ -- (void)maybeDequeueWrite -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - // If we're not currently processing a write AND we have an available write stream - if ((currentWrite == nil) && (flags & kConnected)) - { - if ([writeQueue count] > 0) - { - // Dequeue the next object in the write queue - currentWrite = [writeQueue objectAtIndex:0]; - [writeQueue removeObjectAtIndex:0]; - - - if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]]) - { - LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); - - // Attempt to start TLS - flags |= kStartingWriteTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - LogVerbose(@"Dequeued GCDAsyncWritePacket"); - - // Setup write timer (if needed) - [self setupWriteTimerWithTimeout:currentWrite->timeout]; - - // Immediately write, if possible - [self doWriteData]; - } - } - else if (flags & kDisconnectAfterWrites) - { - if (flags & kDisconnectAfterReads) - { - if (([readQueue count] == 0) && (currentRead == nil)) - { - [self closeWithError:nil]; - } - } - else - { - [self closeWithError:nil]; - } - } - } -} - -- (void)doWriteData -{ - LogTrace(); - - // This method is called by the writeSource via the socketQueue - - if ((currentWrite == nil) || (flags & kWritesPaused)) - { - LogVerbose(@"No currentWrite or kWritesPaused"); - - // Unable to write at this time - - if ([self usingCFStreamForTLS]) - { - // CFWriteStream only fires once when there is available data. - // It won't fire again until we've invoked CFWriteStreamWrite. - } - else - { - // If the writeSource is firing, we need to pause it - // or else it will continue to fire over and over again. - - if (flags & kSocketCanAcceptBytes) - { - [self suspendWriteSource]; - } - } - return; - } - - if (!(flags & kSocketCanAcceptBytes)) - { - LogVerbose(@"No space available to write..."); - - // No space available to write. - - if (![self usingCFStreamForTLS]) - { - // Need to wait for writeSource to fire and notify us of - // available space in the socket's internal write buffer. - - [self resumeWriteSource]; - } - return; - } - - if (flags & kStartingWriteTLS) - { - LogVerbose(@"Waiting for SSL/TLS handshake to complete"); - - // The writeQueue is waiting for SSL/TLS handshake to complete. - - if (flags & kStartingReadTLS) - { - if ([self usingSecureTransportForTLS]) - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - - // We are in the process of a SSL Handshake. - // We were waiting for available space in the socket's internal OS buffer to continue writing. - - [self ssl_continueSSLHandshake]; - - #endif - } - } - else - { - // We are still waiting for the readQueue to drain and start the SSL/TLS process. - // We now know we can write to the socket. - - if (![self usingCFStreamForTLS]) - { - // Suspend the write source or else it will continue to fire nonstop. - - [self suspendWriteSource]; - } - } - - return; - } - - // Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet) - - BOOL waiting = NO; - NSError *error = nil; - size_t bytesWritten = 0; - - if (flags & kSocketSecure) - { - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // - // Writing data using CFStream (over internal TLS) - // - - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite); - LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result); - - if (result < 0) - { - error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream); - } - else - { - bytesWritten = (size_t)result; - - // We always set waiting to true in this scenario. - // CFStream may have altered our underlying socket to non-blocking. - // Thus if we attempt to write without a callback, we may end up blocking our queue. - waiting = YES; - } - - #endif - } - else - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - - // We're going to use the SSLWrite function. - // - // OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed) - // - // Parameters: - // context - An SSL session context reference. - // data - A pointer to the buffer of data to write. - // dataLength - The amount, in bytes, of data to write. - // processed - On return, the length, in bytes, of the data actually written. - // - // It sounds pretty straight-forward, - // but there are a few caveats you should be aware of. - // - // The SSLWrite method operates in a non-obvious (and rather annoying) manner. - // According to the documentation: - // - // Because you may configure the underlying connection to operate in a non-blocking manner, - // a write operation might return errSSLWouldBlock, indicating that less data than requested - // was actually transferred. In this case, you should repeat the call to SSLWrite until some - // other result is returned. - // - // This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock, - // then the SSLWrite method returns (with the proper errSSLWouldBlock return value), - // but it sets processed to dataLength !! - // - // In other words, if the SSLWrite function doesn't completely write all the data we tell it to, - // then it doesn't tell us how many bytes were actually written. So, for example, if we tell it to - // write 256 bytes then it might actually write 128 bytes, but then report 0 bytes written. - // - // You might be wondering: - // If the SSLWrite function doesn't tell us how many bytes were written, - // then how in the world are we supposed to update our parameters (buffer & bytesToWrite) - // for the next time we invoke SSLWrite? - // - // The answer is that SSLWrite cached all the data we told it to write, - // and it will push out that data next time we call SSLWrite. - // If we call SSLWrite with new data, it will push out the cached data first, and then the new data. - // If we call SSLWrite with empty data, then it will simply push out the cached data. - // - // For this purpose we're going to break large writes into a series of smaller writes. - // This allows us to report progress back to the delegate. - - OSStatus result; - - BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0); - BOOL hasNewDataToWrite = YES; - - if (hasCachedDataToWrite) - { - size_t processed = 0; - - result = SSLWrite(sslContext, NULL, 0, &processed); - - if (result == noErr) - { - bytesWritten = sslWriteCachedLength; - sslWriteCachedLength = 0; - - if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten)) - { - // We've written all data for the current write. - hasNewDataToWrite = NO; - } - } - else - { - if (result == errSSLWouldBlock) - { - waiting = YES; - } - else - { - error = [self sslError:result]; - } - - // Can't write any new data since we were unable to write the cached data. - hasNewDataToWrite = NO; - } - } - - if (hasNewDataToWrite) - { - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] - + currentWrite->bytesDone - + bytesWritten; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - size_t bytesRemaining = bytesToWrite; - - BOOL keepLooping = YES; - while (keepLooping) - { - size_t sslBytesToWrite = MIN(bytesRemaining, 32768); - size_t sslBytesWritten = 0; - - result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten); - - if (result == noErr) - { - buffer += sslBytesWritten; - bytesWritten += sslBytesWritten; - bytesRemaining -= sslBytesWritten; - - keepLooping = (bytesRemaining > 0); - } - else - { - if (result == errSSLWouldBlock) - { - waiting = YES; - sslWriteCachedLength = sslBytesToWrite; - } - else - { - error = [self sslError:result]; - } - - keepLooping = NO; - } - - } // while (keepLooping) - - } // if (hasNewDataToWrite) - - #endif - } - } - else - { - // - // Writing data directly over raw socket - // - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite); - LogVerbose(@"wrote to socket = %zd", result); - - // Check results - if (result < 0) - { - if (errno == EWOULDBLOCK) - { - waiting = YES; - } - else - { - error = [self errnoErrorWithReason:@"Error in write() function"]; - } - } - else - { - bytesWritten = result; - } - } - - // We're done with our writing. - // If we explictly ran into a situation where the socket told us there was no room in the buffer, - // then we immediately resume listening for notifications. - // - // We must do this before we dequeue another write, - // as that may in turn invoke this method again. - // - // Note that if CFStream is involved, it may have maliciously put our socket in blocking mode. - - if (waiting) - { - flags &= ~kSocketCanAcceptBytes; - - if (![self usingCFStreamForTLS]) - { - [self resumeWriteSource]; - } - } - - // Check our results - - BOOL done = NO; - - if (bytesWritten > 0) - { - // Update total amount read for the current write - currentWrite->bytesDone += bytesWritten; - LogVerbose(@"currentWrite->bytesDone = %lu", (unsigned long)currentWrite->bytesDone); - - // Is packet done? - done = (currentWrite->bytesDone == [currentWrite->buffer length]); - } - - if (done) - { - [self completeCurrentWrite]; - - if (!error) - { - [self maybeDequeueWrite]; - } - } - else - { - // We were unable to finish writing the data, - // so we're waiting for another callback to notify us of available space in the lower-level output buffer. - - if (!waiting & !error) - { - // This would be the case if our write was able to accept some data, but not all of it. - - flags &= ~kSocketCanAcceptBytes; - - if (![self usingCFStreamForTLS]) - { - [self resumeWriteSource]; - } - } - - if (bytesWritten > 0) - { - // We're not done with the entire write, but we have written some bytes - - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)]) - { - __strong id theDelegate = delegate; - long theWriteTag = currentWrite->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag]; - }}); - } - } - } - - // Check for errors - - if (error) - { - [self closeWithError:[self errnoErrorWithReason:@"Error in write() function"]]; - } - - // Do not add any code here without first adding a return statement in the error case above. -} - -- (void)completeCurrentWrite -{ - LogTrace(); - - NSAssert(currentWrite, @"Trying to complete current write when there is no current write."); - - - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) - { - __strong id theDelegate = delegate; - long theWriteTag = currentWrite->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didWriteDataWithTag:theWriteTag]; - }}); - } - - [self endCurrentWrite]; -} - -- (void)endCurrentWrite -{ - if (writeTimer) - { - dispatch_source_cancel(writeTimer); - writeTimer = NULL; - } - - currentWrite = nil; -} - -- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); - - dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool { - - [self doWriteTimeout]; - }}); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_source_t theWriteTimer = writeTimer; - dispatch_source_set_cancel_handler(writeTimer, ^{ - LogVerbose(@"dispatch_release(writeTimer)"); - dispatch_release(theWriteTimer); - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC)); - - dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(writeTimer); - } -} - -- (void)doWriteTimeout -{ - // This is a little bit tricky. - // Ideally we'd like to synchronously query the delegate about a timeout extension. - // But if we do so synchronously we risk a possible deadlock. - // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. - - flags |= kWritesPaused; - - if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) - { - __strong id theDelegate = delegate; - GCDAsyncWritePacket *theWrite = currentWrite; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - NSTimeInterval timeoutExtension = 0.0; - - timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag - elapsed:theWrite->timeout - bytesDone:theWrite->bytesDone]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self doWriteTimeoutWithExtension:timeoutExtension]; - }}); - }}); - } - else - { - [self doWriteTimeoutWithExtension:0.0]; - } -} - -- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension -{ - if (currentWrite) - { - if (timeoutExtension > 0.0) - { - currentWrite->timeout += timeoutExtension; - - // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeoutExtension * NSEC_PER_SEC)); - dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); - - // Unpause writes, and continue - flags &= ~kWritesPaused; - [self doWriteData]; - } - else - { - LogVerbose(@"WriteTimeout"); - - [self closeWithError:[self writeTimeoutError]]; - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)startTLS:(NSDictionary *)tlsSettings -{ - LogTrace(); - - if (tlsSettings == nil) - { - // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, - // but causes problems if we later try to fetch the remote host's certificate. - // - // To be exact, it causes the following to return NULL instead of the normal result: - // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) - // - // So we use an empty dictionary instead, which works perfectly. - - tlsSettings = [NSDictionary dictionary]; - } - - GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if ((flags & kSocketStarted) && !(flags & kQueuedTLS) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [writeQueue addObject:packet]; - - flags |= kQueuedTLS; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } - }}); - -} - -- (void)maybeStartTLS -{ - // We can't start TLS until: - // - All queued reads prior to the user calling startTLS are complete - // - All queued writes prior to the user calling startTLS are complete - // - // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - BOOL canUseSecureTransport = YES; - - #if TARGET_OS_IPHONE - { - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - NSDictionary *tlsSettings = tlsPacket->tlsSettings; - - NSNumber *value; - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot]; - if (value && [value boolValue] == YES) - canUseSecureTransport = NO; - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots]; - if (value && [value boolValue] == YES) - canUseSecureTransport = NO; - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLValidatesCertificateChain]; - if (value && [value boolValue] == NO) - canUseSecureTransport = NO; - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates]; - if (value && [value boolValue] == YES) - canUseSecureTransport = NO; - } - #endif - - if (IS_SECURE_TRANSPORT_AVAILABLE && canUseSecureTransport) - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - [self ssl_startTLS]; - #endif - } - else - { - #if TARGET_OS_IPHONE - [self cf_startTLS]; - #endif - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security via SecureTransport -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if SECURE_TRANSPORT_MAYBE_AVAILABLE - -- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength -{ - LogVerbose(@"sslReadWithBuffer:%p length:%lu", buffer, (unsigned long)*bufferLength); - - if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0)) - { - LogVerbose(@"%@ - No data available to read...", THIS_METHOD); - - // No data available to read. - // - // Need to wait for readSource to fire and notify us of - // available data in the socket's internal read buffer. - - [self resumeReadSource]; - - *bufferLength = 0; - return errSSLWouldBlock; - } - - size_t totalBytesRead = 0; - size_t totalBytesLeftToBeRead = *bufferLength; - - BOOL done = NO; - BOOL socketError = NO; - - // - // STEP 1 : READ FROM SSL PRE BUFFER - // - - size_t sslPreBufferLength = [sslPreBuffer availableBytes]; - - if (sslPreBufferLength > 0) - { - LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD); - - size_t bytesToCopy; - if (sslPreBufferLength > totalBytesLeftToBeRead) - bytesToCopy = totalBytesLeftToBeRead; - else - bytesToCopy = sslPreBufferLength; - - LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", THIS_METHOD, bytesToCopy); - - memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy); - [sslPreBuffer didRead:bytesToCopy]; - - LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]); - - totalBytesRead += bytesToCopy; - totalBytesLeftToBeRead -= bytesToCopy; - - done = (totalBytesLeftToBeRead == 0); - - if (done) LogVerbose(@"%@: Complete", THIS_METHOD); - } - - // - // STEP 2 : READ FROM SOCKET - // - - if (!done && (socketFDBytesAvailable > 0)) - { - LogVerbose(@"%@: Reading from socket...", THIS_METHOD); - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - BOOL readIntoPreBuffer; - size_t bytesToRead; - uint8_t *buf; - - if (socketFDBytesAvailable > totalBytesLeftToBeRead) - { - // Read all available data from socket into sslPreBuffer. - // Then copy requested amount into dataBuffer. - - LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD); - - [sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable]; - - readIntoPreBuffer = YES; - bytesToRead = (size_t)socketFDBytesAvailable; - buf = [sslPreBuffer writeBuffer]; - } - else - { - // Read available data from socket directly into dataBuffer. - - LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD); - - readIntoPreBuffer = NO; - bytesToRead = totalBytesLeftToBeRead; - buf = (uint8_t *)buffer + totalBytesRead; - } - - ssize_t result = read(socketFD, buf, bytesToRead); - LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result); - - if (result < 0) - { - LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno); - - if (errno != EWOULDBLOCK) - { - socketError = YES; - } - - socketFDBytesAvailable = 0; - } - else if (result == 0) - { - LogVerbose(@"%@: read EOF", THIS_METHOD); - - socketError = YES; - socketFDBytesAvailable = 0; - } - else - { - size_t bytesReadFromSocket = result; - - if (socketFDBytesAvailable > bytesReadFromSocket) - socketFDBytesAvailable -= bytesReadFromSocket; - else - socketFDBytesAvailable = 0; - - if (readIntoPreBuffer) - { - [sslPreBuffer didWrite:bytesReadFromSocket]; - - size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket); - - LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", THIS_METHOD, bytesToCopy); - - memcpy((uint8_t *)buffer + totalBytesRead, [sslPreBuffer readBuffer], bytesToCopy); - [sslPreBuffer didRead:bytesToCopy]; - - totalBytesRead += bytesToCopy; - totalBytesLeftToBeRead -= bytesToCopy; - - LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]); - } - else - { - totalBytesRead += bytesReadFromSocket; - totalBytesLeftToBeRead -= bytesReadFromSocket; - } - - done = (totalBytesLeftToBeRead == 0); - - if (done) LogVerbose(@"%@: Complete", THIS_METHOD); - } - } - - *bufferLength = totalBytesRead; - - if (done) - return noErr; - - if (socketError) - return errSSLClosedAbort; - - return errSSLWouldBlock; -} - -- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength -{ - if (!(flags & kSocketCanAcceptBytes)) - { - // Unable to write. - // - // Need to wait for writeSource to fire and notify us of - // available space in the socket's internal write buffer. - - [self resumeWriteSource]; - - *bufferLength = 0; - return errSSLWouldBlock; - } - - size_t bytesToWrite = *bufferLength; - size_t bytesWritten = 0; - - BOOL done = NO; - BOOL socketError = NO; - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - ssize_t result = write(socketFD, buffer, bytesToWrite); - - if (result < 0) - { - if (errno != EWOULDBLOCK) - { - socketError = YES; - } - - flags &= ~kSocketCanAcceptBytes; - } - else if (result == 0) - { - flags &= ~kSocketCanAcceptBytes; - } - else - { - bytesWritten = result; - - done = (bytesWritten == bytesToWrite); - } - - *bufferLength = bytesWritten; - - if (done) - return noErr; - - if (socketError) - return errSSLClosedAbort; - - return errSSLWouldBlock; -} - -static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; - - NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?"); - - return [asyncSocket sslReadWithBuffer:data length:dataLength]; -} - -static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, size_t *dataLength) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; - - NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?"); - - return [asyncSocket sslWriteWithBuffer:data length:dataLength]; -} - -- (void)ssl_startTLS -{ - LogTrace(); - - LogVerbose(@"Starting TLS (via SecureTransport)..."); - - OSStatus status; - - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - NSDictionary *tlsSettings = tlsPacket->tlsSettings; - - // Create SSLContext, and setup IO callbacks and connection ref - - BOOL isServer = [[tlsSettings objectForKey:(NSString *)kCFStreamSSLIsServer] boolValue]; - - #if TARGET_OS_IPHONE - { - if (isServer) - sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType); - else - sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType); - - if (sslContext == NULL) - { - [self closeWithError:[self otherError:@"Error in SSLCreateContext"]]; - return; - } - } - #else - { - status = SSLNewContext(isServer, &sslContext); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLNewContext"]]; - return; - } - } - #endif - - status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]]; - return; - } - - status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetConnection"]]; - return; - } - - // Configure SSLContext from given settings - // - // Checklist: - // 1. kCFStreamSSLPeerName - // 2. kCFStreamSSLAllowsAnyRoot - // 3. kCFStreamSSLAllowsExpiredRoots - // 4. kCFStreamSSLValidatesCertificateChain - // 5. kCFStreamSSLAllowsExpiredCertificates - // 6. kCFStreamSSLCertificates - // 7. kCFStreamSSLLevel (GCDAsyncSocketSSLProtocolVersionMin / GCDAsyncSocketSSLProtocolVersionMax) - // 8. GCDAsyncSocketSSLCipherSuites - // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac) - - id value; - - // 1. kCFStreamSSLPeerName - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLPeerName]; - if ([value isKindOfClass:[NSString class]]) - { - NSString *peerName = (NSString *)value; - - const char *peer = [peerName UTF8String]; - size_t peerLen = strlen(peer); - - status = SSLSetPeerDomainName(sslContext, peer, peerLen); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]]; - return; - } - } - - // 2. kCFStreamSSLAllowsAnyRoot - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot]; - if (value) - { - #if TARGET_OS_IPHONE - NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsAnyRoot"); - #else - - BOOL allowsAnyRoot = [value boolValue]; - - status = SSLSetAllowsAnyRoot(sslContext, allowsAnyRoot); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetAllowsAnyRoot"]]; - return; - } - - #endif - } - - // 3. kCFStreamSSLAllowsExpiredRoots - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots]; - if (value) - { - #if TARGET_OS_IPHONE - NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsExpiredRoots"); - #else - - BOOL allowsExpiredRoots = [value boolValue]; - - status = SSLSetAllowsExpiredRoots(sslContext, allowsExpiredRoots); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredRoots"]]; - return; - } - - #endif - } - - // 4. kCFStreamSSLValidatesCertificateChain - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLValidatesCertificateChain]; - if (value) - { - #if TARGET_OS_IPHONE - NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLValidatesCertificateChain"); - #else - - BOOL validatesCertChain = [value boolValue]; - - status = SSLSetEnableCertVerify(sslContext, validatesCertChain); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]]; - return; - } - - #endif - } - - // 5. kCFStreamSSLAllowsExpiredCertificates - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates]; - if (value) - { - #if TARGET_OS_IPHONE - NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsExpiredCertificates"); - #else - - BOOL allowsExpiredCerts = [value boolValue]; - - status = SSLSetAllowsExpiredCerts(sslContext, allowsExpiredCerts); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredCerts"]]; - return; - } - - #endif - } - - // 6. kCFStreamSSLCertificates - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLCertificates]; - if (value) - { - CFArrayRef certs = (__bridge CFArrayRef)value; - - status = SSLSetCertificate(sslContext, certs); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]]; - return; - } - } - - // 7. kCFStreamSSLLevel - - #if TARGET_OS_IPHONE - { - NSString *sslLevel = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel]; - - NSString *sslMinLevel = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin]; - NSString *sslMaxLevel = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax]; - - if (sslLevel) - { - if (sslMinLevel || sslMaxLevel) - { - LogWarn(@"kCFStreamSSLLevel security option ignored. Overriden by " - @"GCDAsyncSocketSSLProtocolVersionMin and/or GCDAsyncSocketSSLProtocolVersionMax"); - } - else - { - if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv3]) - { - sslMinLevel = sslMaxLevel = @"kSSLProtocol3"; - } - else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelTLSv1]) - { - sslMinLevel = sslMaxLevel = @"kTLSProtocol1"; - } - else - { - LogWarn(@"Unable to match kCFStreamSSLLevel security option to valid SSL protocol min/max"); - } - } - } - - if (sslMinLevel || sslMaxLevel) - { - OSStatus status1 = noErr; - OSStatus status2 = noErr; - - SSLProtocol (^sslProtocolForString)(NSString*) = ^SSLProtocol (NSString *protocolStr) { - - if ([protocolStr isEqualToString:@"kSSLProtocol3"]) return kSSLProtocol3; - if ([protocolStr isEqualToString:@"kTLSProtocol1"]) return kTLSProtocol1; - if ([protocolStr isEqualToString:@"kTLSProtocol11"]) return kTLSProtocol11; - if ([protocolStr isEqualToString:@"kTLSProtocol12"]) return kTLSProtocol12; - - return kSSLProtocolUnknown; - }; - - SSLProtocol minProtocol = sslProtocolForString(sslMinLevel); - SSLProtocol maxProtocol = sslProtocolForString(sslMaxLevel); - - if (minProtocol != kSSLProtocolUnknown) - { - status1 = SSLSetProtocolVersionMin(sslContext, minProtocol); - } - if (maxProtocol != kSSLProtocolUnknown) - { - status2 = SSLSetProtocolVersionMax(sslContext, maxProtocol); - } - - if (status1 != noErr || status2 != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMinMax"]]; - return; - } - } - } - #else - { - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel]; - if (value) - { - NSString *sslLevel = (NSString *)value; - - OSStatus status1 = noErr; - OSStatus status2 = noErr; - OSStatus status3 = noErr; - - if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv2]) - { - // kCFStreamSocketSecurityLevelSSLv2: - // - // Specifies that SSL version 2 be set as the security protocol. - - status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO); - status2 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2, YES); - } - else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv3]) - { - // kCFStreamSocketSecurityLevelSSLv3: - // - // Specifies that SSL version 3 be set as the security protocol. - // If SSL version 3 is not available, specifies that SSL version 2 be set as the security protocol. - - status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO); - status2 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2, YES); - status3 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol3, YES); - } - else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelTLSv1]) - { - // kCFStreamSocketSecurityLevelTLSv1: - // - // Specifies that TLS version 1 be set as the security protocol. - - status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO); - status2 = SSLSetProtocolVersionEnabled(sslContext, kTLSProtocol1, YES); - } - else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL]) - { - // kCFStreamSocketSecurityLevelNegotiatedSSL: - // - // Specifies that the highest level security protocol that can be negotiated be used. - - status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, YES); - } - - if (status1 != noErr || status2 != noErr || status3 != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionEnabled"]]; - return; - } - } - } - #endif - - // 8. GCDAsyncSocketSSLCipherSuites - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites]; - if (value) - { - NSArray *cipherSuites = (NSArray *)value; - NSUInteger numberCiphers = [cipherSuites count]; - SSLCipherSuite ciphers[numberCiphers]; - - NSUInteger cipherIndex; - for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++) - { - NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex]; - ciphers[cipherIndex] = [cipherObject shortValue]; - } - - status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]]; - return; - } - } - - // 9. GCDAsyncSocketSSLDiffieHellmanParameters - - #if !TARGET_OS_IPHONE - value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters]; - if (value) - { - NSData *diffieHellmanData = (NSData *)value; - - status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]]; - return; - } - } - #endif - - // Setup the sslPreBuffer - // - // Any data in the preBuffer needs to be moved into the sslPreBuffer, - // as this data is now part of the secure read stream. - - sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; - - size_t preBufferLength = [preBuffer availableBytes]; - - if (preBufferLength > 0) - { - [sslPreBuffer ensureCapacityForWrite:preBufferLength]; - - memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength); - [preBuffer didRead:preBufferLength]; - [sslPreBuffer didWrite:preBufferLength]; - } - - sslErrCode = noErr; - - // Start the SSL Handshake process - - [self ssl_continueSSLHandshake]; -} - -- (void)ssl_continueSSLHandshake -{ - LogTrace(); - - // If the return value is noErr, the session is ready for normal secure communication. - // If the return value is errSSLWouldBlock, the SSLHandshake function must be called again. - // Otherwise, the return value indicates an error code. - - OSStatus status = SSLHandshake(sslContext); - - if (status == noErr) - { - LogVerbose(@"SSLHandshake complete"); - - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - flags |= kSocketSecure; - - if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)]) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidSecure:self]; - }}); - } - - [self endCurrentRead]; - [self endCurrentWrite]; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } - else if (status == errSSLWouldBlock) - { - LogVerbose(@"SSLHandshake continues..."); - - // Handshake continues... - // - // This method will be called again from doReadData or doWriteData. - } - else - { - [self closeWithError:[self sslError:status]]; - } -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security via CFStream -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if TARGET_OS_IPHONE - -- (void)cf_finishSSLHandshake -{ - LogTrace(); - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - flags |= kSocketSecure; - - if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)]) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidSecure:self]; - }}); - } - - [self endCurrentRead]; - [self endCurrentWrite]; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } -} - -- (void)cf_abortSSLHandshake:(NSError *)error -{ - LogTrace(); - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - [self closeWithError:error]; - } -} - -- (void)cf_startTLS -{ - LogTrace(); - - LogVerbose(@"Starting TLS (via CFStream)..."); - - if ([preBuffer availableBytes] > 0) - { - NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - [self suspendReadSource]; - [self suspendWriteSource]; - - socketFDBytesAvailable = 0; - flags &= ~kSocketCanAcceptBytes; - flags &= ~kSecureSocketHasBytesAvailable; - - flags |= kUsingCFStreamForTLS; - - if (![self createReadAndWriteStream]) - { - [self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]]; - return; - } - - if (![self registerForStreamCallbacksIncludingReadWrite:YES]) - { - [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; - return; - } - - if (![self addStreamsToRunLoop]) - { - [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; - return; - } - - NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid read packet for startTLS"); - NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid write packet for startTLS"); - - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - CFDictionaryRef tlsSettings = (__bridge CFDictionaryRef)tlsPacket->tlsSettings; - - // Getting an error concerning kCFStreamPropertySSLSettings ? - // You need to add the CFNetwork framework to your iOS application. - - BOOL r1 = CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, tlsSettings); - BOOL r2 = CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, tlsSettings); - - // For some reason, starting around the time of iOS 4.3, - // the first call to set the kCFStreamPropertySSLSettings will return true, - // but the second will return false. - // - // Order doesn't seem to matter. - // So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order. - // Either way, the first call will return true, and the second returns false. - // - // Interestingly, this doesn't seem to affect anything. - // Which is not altogether unusual, as the documentation seems to suggest that (for many settings) - // setting it on one side of the stream automatically sets it for the other side of the stream. - // - // Although there isn't anything in the documentation to suggest that the second attempt would fail. - // - // Furthermore, this only seems to affect streams that are negotiating a security upgrade. - // In other words, the socket gets connected, there is some back-and-forth communication over the unsecure - // connection, and then a startTLS is issued. - // So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS). - - if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug. - { - [self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]]; - return; - } - - if (![self openStreams]) - { - [self closeWithError:[self otherError:@"Error in CFStreamOpen"]]; - return; - } - - LogVerbose(@"Waiting for SSL Handshake to complete..."); -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark CFStream -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if TARGET_OS_IPHONE - -+ (void)startCFStreamThreadIfNeeded -{ - static dispatch_once_t predicate; - dispatch_once(&predicate, ^{ - - cfstreamThread = [[NSThread alloc] initWithTarget:self - selector:@selector(cfstreamThread) - object:nil]; - [cfstreamThread start]; - }); -} - -+ (void)cfstreamThread { @autoreleasepool -{ - [[NSThread currentThread] setName:GCDAsyncSocketThreadName]; - - LogInfo(@"CFStreamThread: Started"); - - // We can't run the run loop unless it has an associated input source or a timer. - // So we'll just create a timer that will never fire - unless the server runs for decades. - [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] - target:self - selector:@selector(doNothingAtAll:) - userInfo:nil - repeats:YES]; - - [[NSRunLoop currentRunLoop] run]; - - LogInfo(@"CFStreamThread: Stopped"); -}} - -+ (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncSocket->readStream) - CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode); - - if (asyncSocket->writeStream) - CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode); -} - -+ (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncSocket->readStream) - CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode); - - if (asyncSocket->writeStream) - CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode); -} - -static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; - - switch(type) - { - case kCFStreamEventHasBytesAvailable: - { - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); - - if (asyncSocket->readStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. - // (A callback related to the tcp stream, but not to the SSL layer). - - if (CFReadStreamHasBytesAvailable(asyncSocket->readStream)) - { - asyncSocket->flags |= kSecureSocketHasBytesAvailable; - [asyncSocket cf_finishSSLHandshake]; - } - } - else - { - asyncSocket->flags |= kSecureSocketHasBytesAvailable; - [asyncSocket doReadData]; - } - }}); - - break; - } - default: - { - NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); - - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncSocket connectionClosedError]; - } - - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - Other"); - - if (asyncSocket->readStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - [asyncSocket cf_abortSSLHandshake:error]; - } - else - { - [asyncSocket closeWithError:error]; - } - }}); - - break; - } - } - -} - -static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; - - switch(type) - { - case kCFStreamEventCanAcceptBytes: - { - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); - - if (asyncSocket->writeStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. - // (A callback related to the tcp stream, but not to the SSL layer). - - if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream)) - { - asyncSocket->flags |= kSocketCanAcceptBytes; - [asyncSocket cf_finishSSLHandshake]; - } - } - else - { - asyncSocket->flags |= kSocketCanAcceptBytes; - [asyncSocket doWriteData]; - } - }}); - - break; - } - default: - { - NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); - - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncSocket connectionClosedError]; - } - - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - Other"); - - if (asyncSocket->writeStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - [asyncSocket cf_abortSSLHandshake:error]; - } - else - { - [asyncSocket closeWithError:error]; - } - }}); - - break; - } - } - -} - -- (BOOL)createReadAndWriteStream -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (readStream || writeStream) - { - // Streams already created - return YES; - } - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - if (socketFD == SOCKET_NULL) - { - // Cannot create streams without a file descriptor - return NO; - } - - if (![self isConnected]) - { - // Cannot create streams until file descriptor is connected - return NO; - } - - LogVerbose(@"Creating read and write stream..."); - - CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socketFD, &readStream, &writeStream); - - // The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case). - // But let's not take any chances. - - if (readStream) - CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - if (writeStream) - CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - - if ((readStream == NULL) || (writeStream == NULL)) - { - LogWarn(@"Unable to create read and write stream..."); - - if (readStream) - { - CFReadStreamClose(readStream); - CFRelease(readStream); - readStream = NULL; - } - if (writeStream) - { - CFWriteStreamClose(writeStream); - CFRelease(writeStream); - writeStream = NULL; - } - - return NO; - } - - return YES; -} - -- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite -{ - LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO")); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - streamContext.version = 0; - streamContext.info = (__bridge void *)(self); - streamContext.retain = nil; - streamContext.release = nil; - streamContext.copyDescription = nil; - - CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - if (includeReadWrite) - readStreamEvents |= kCFStreamEventHasBytesAvailable; - - if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext)) - { - return NO; - } - - CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - if (includeReadWrite) - writeStreamEvents |= kCFStreamEventCanAcceptBytes; - - if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext)) - { - return NO; - } - - return YES; -} - -- (BOOL)addStreamsToRunLoop -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - if (!(flags & kAddedStreamsToRunLoop)) - { - LogVerbose(@"Adding streams to runloop..."); - - [[self class] startCFStreamThreadIfNeeded]; - [[self class] performSelector:@selector(scheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; - - flags |= kAddedStreamsToRunLoop; - } - - return YES; -} - -- (void)removeStreamsFromRunLoop -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - if (flags & kAddedStreamsToRunLoop) - { - LogVerbose(@"Removing streams from runloop..."); - - [[self class] performSelector:@selector(unscheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; - - flags &= ~kAddedStreamsToRunLoop; - } -} - -- (BOOL)openStreams -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - CFStreamStatus readStatus = CFReadStreamGetStatus(readStream); - CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream); - - if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen)) - { - LogVerbose(@"Opening read and write stream..."); - - BOOL r1 = CFReadStreamOpen(readStream); - BOOL r2 = CFWriteStreamOpen(writeStream); - - if (!r1 || !r2) - { - LogError(@"Error in CFStreamOpen"); - return NO; - } - } - - return YES; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Advanced -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * See header file for big discussion of this method. -**/ -- (BOOL)autoDisconnectOnClosedReadStream -{ - // Note: YES means kAllowHalfDuplexConnection is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kAllowHalfDuplexConnection) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kAllowHalfDuplexConnection) == 0); - }); - - return result; - } -} - -/** - * See header file for big discussion of this method. -**/ -- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag -{ - // Note: YES means kAllowHalfDuplexConnection is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kAllowHalfDuplexConnection; - else - config |= kAllowHalfDuplexConnection; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - - -/** - * See header file for big discussion of this method. -**/ -- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue -{ - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); -} - -/** - * See header file for big discussion of this method. -**/ -- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue -{ - dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL); -} - -/** - * See header file for big discussion of this method. -**/ -- (void)performBlock:(dispatch_block_t)block -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} - -/** - * Questions? Have you read the header file? -**/ -- (int)socketFD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } - - if (socket4FD != SOCKET_NULL) - return socket4FD; - else - return socket6FD; -} - -/** - * Questions? Have you read the header file? -**/ -- (int)socket4FD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } - - return socket4FD; -} - -/** - * Questions? Have you read the header file? -**/ -- (int)socket6FD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } - - return socket6FD; -} - -#if TARGET_OS_IPHONE - -/** - * Questions? Have you read the header file? -**/ -- (CFReadStreamRef)readStream -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } - - if (readStream == NULL) - [self createReadAndWriteStream]; - - return readStream; -} - -/** - * Questions? Have you read the header file? -**/ -- (CFWriteStreamRef)writeStream -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } - - if (writeStream == NULL) - [self createReadAndWriteStream]; - - return writeStream; -} - -- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat -{ - if (![self createReadAndWriteStream]) - { - // Error occured creating streams (perhaps socket isn't open) - return NO; - } - - BOOL r1, r2; - - LogVerbose(@"Enabling backgrouding on socket"); - - r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - - if (!r1 || !r2) - { - return NO; - } - - if (!caveat) - { - if (![self openStreams]) - { - return NO; - } - } - - return YES; -} - -/** - * Questions? Have you read the header file? -**/ -- (BOOL)enableBackgroundingOnSocket -{ - LogTrace(); - - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NO; - } - - return [self enableBackgroundingOnSocketWithCaveat:NO]; -} - -- (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.??? -{ - // This method was created as a workaround for a bug in iOS. - // Apple has since fixed this bug. - // I'm not entirely sure which version of iOS they fixed it in... - - LogTrace(); - - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NO; - } - - return [self enableBackgroundingOnSocketWithCaveat:YES]; -} - -#endif - -#if SECURE_TRANSPORT_MAYBE_AVAILABLE - -- (SSLContextRef)sslContext -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } - - return sslContext; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Class Methods -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - char addrBuf[INET_ADDRSTRLEN]; - - if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - char addrBuf[INET6_ADDRSTRLEN]; - - if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - return ntohs(pSockaddr4->sin_port); -} - -+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - return ntohs(pSockaddr6->sin6_port); -} - -+ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr -{ - NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path]; - return [NSURL fileURLWithPath:path]; -} - -+ (NSString *)hostFromAddress:(NSData *)address -{ - NSString *host; - - if ([self getHost:&host port:NULL fromAddress:address]) - return host; - else - return nil; -} - -+ (uint16_t)portFromAddress:(NSData *)address -{ - uint16_t port; - - if ([self getHost:NULL port:&port fromAddress:address]) - return port; - else - return 0; -} - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = [address bytes]; - - if (sockaddrX->sa_family == AF_INET) - { - if ([address length] >= sizeof(struct sockaddr_in)) - { - struct sockaddr_in sockaddr4; - memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4)); - - if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4]; - if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4]; - - return YES; - } - } - else if (sockaddrX->sa_family == AF_INET6) - { - if ([address length] >= sizeof(struct sockaddr_in6)) - { - struct sockaddr_in6 sockaddr6; - memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6)); - - if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6]; - if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6]; - - return YES; - } - } - } - - return NO; -} - -+ (NSData *)CRLFData -{ - return [NSData dataWithBytes:"\x0D\x0A" length:2]; -} - -+ (NSData *)CRData -{ - return [NSData dataWithBytes:"\x0D" length:1]; -} - -+ (NSData *)LFData -{ - return [NSData dataWithBytes:"\x0A" length:1]; -} - -+ (NSData *)ZeroData -{ - return [NSData dataWithBytes:"" length:1]; -} - -@end diff --git a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj index 5a1d2e055..cd2a61ce2 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 0B13ECAE173C687400548DA1 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; 0B13ECAF173C687900548DA1 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BFC9ACB173C57E400CDD329 /* Security.framework */; }; 5BB74A8719DBF9BB001BAAAC /* FinishedIconCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BB74A8619DBF9BB001BAAAC /* FinishedIconCache.m */; }; 692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */; }; @@ -21,13 +20,12 @@ 8C99F6941622D145002D2135 /* IconCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C99F6931622D145002D2135 /* IconCache.m */; }; 8D576314048677EA00EA77CD /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */; }; 8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8D5B49A704867FD3000E48DA /* InfoPlist.strings */; }; + C2B573831B1CD5AE00303B36 /* SyncClientProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573821B1CD5AE00303B36 /* SyncClientProxy.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 089C167EFE841241C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = ""; }; - 0B13ECAC173C686900548DA1 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = ""; }; - 0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = ""; }; 0B2BF60B176A43DB001246CD /* Finder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Finder.h; sourceTree = ""; }; 0BFC9ACB173C57E400CDD329 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 5BB74A8519DBF9BB001BAAAC /* FinishedIconCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FinishedIconCache.h; sourceTree = ""; }; @@ -50,6 +48,8 @@ 8C99F6931622D145002D2135 /* IconCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IconCache.m; sourceTree = ""; }; 8D576316048677EA00EA77CD /* SyncStateFinder.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SyncStateFinder.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 8D576317048677EA00EA77CD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C2B573811B1CD5AE00303B36 /* SyncClientProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SyncClientProxy.h; sourceTree = ""; }; + C2B573821B1CD5AE00303B36 /* SyncClientProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncClientProxy.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -70,6 +70,7 @@ 089C166AFE841209C02AAC07 /* SyncStateFinder */ = { isa = PBXGroup; children = ( + C2B573801B1CD5AE00303B36 /* common */, 08FB77AFFE84173DC02AAC07 /* Source */, 089C167CFE841241C02AAC07 /* Resources */, 089C1671FE841209C02AAC07 /* External Frameworks and Libraries */, @@ -103,7 +104,6 @@ isa = PBXGroup; children = ( 0B2BF60A176A43DB001246CD /* Finder */, - 0B08BAC21759627700C8351E /* GCDAsyncSocket */, 8C37DD99161593BD00016A95 /* FinderHook.h */, 8C37DD9A161593BD00016A95 /* FinderHook.m */, 692C18A316660C4600BF6A53 /* ContextMenuHandlers.h */, @@ -124,15 +124,6 @@ name = Source; sourceTree = ""; }; - 0B08BAC21759627700C8351E /* GCDAsyncSocket */ = { - isa = PBXGroup; - children = ( - 0B13ECAC173C686900548DA1 /* GCDAsyncSocket.h */, - 0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */, - ); - name = GCDAsyncSocket; - sourceTree = ""; - }; 0B2BF60A176A43DB001246CD /* Finder */ = { isa = PBXGroup; children = ( @@ -149,6 +140,16 @@ name = Products; sourceTree = ""; }; + C2B573801B1CD5AE00303B36 /* common */ = { + isa = PBXGroup; + children = ( + C2B573811B1CD5AE00303B36 /* SyncClientProxy.h */, + C2B573821B1CD5AE00303B36 /* SyncClientProxy.m */, + ); + name = common; + path = ../common; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -213,10 +214,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0B13ECAE173C687400548DA1 /* GCDAsyncSocket.m in Sources */, 8C37DD9F161593BD00016A95 /* FinderHook.m in Sources */, 8C99F6941622D145002D2135 /* IconCache.m in Sources */, 69948B361636D50E0093B6CE /* ContentManager.m in Sources */, + C2B573831B1CD5AE00303B36 /* SyncClientProxy.m in Sources */, 6993878616494C000044E4DF /* RequestManager.m in Sources */, 5BB74A8719DBF9BB001BAAAC /* FinishedIconCache.m in Sources */, 692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */, diff --git a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.h b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.h index e5d4c19e1..136e4fd26 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.h +++ b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.h @@ -13,20 +13,18 @@ */ #import -#import "GCDAsyncSocket.h" #import "RequestManager.h" +#import "SyncClientProxy.h" -@interface RequestManager : NSObject +@interface RequestManager : NSObject { - GCDAsyncSocket* _socket; + SyncClientProxy *_syncClientProxy; NSMutableArray* _requestQueue; NSMutableDictionary* _registeredPathes; NSMutableSet* _requestedPaths; NSString *_shareMenuTitle; - - BOOL _isConnected; } @property (nonatomic, retain) NSString* filterFolder; @@ -34,10 +32,8 @@ + (RequestManager*)sharedInstance; - (BOOL)isRegisteredPath:(NSString*)path isDirectory:(BOOL)isDir; -- (void)askOnSocket:(NSString*)path query:(NSString*)verb; - (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir; - (void)menuItemClicked:(NSDictionary*)actionDictionary; -- (void)start; - (NSString*) shareItemTitle; diff --git a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m index 62399a441..6d5ae1b8c 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m +++ b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m @@ -26,16 +26,19 @@ static RequestManager* sharedInstance = nil; { if ((self = [super init])) { - _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; - - _isConnected = NO; + // For the sake of allowing both the legacy and the FinderSync extensions to work with the same + // client build, use the same server name including the Team ID even though we won't be signing the bundle. + NSString *serverName = @"9B5WD74GWJ.com.owncloud.desktopclient.socketApi"; + _syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName]; _registeredPathes = [[NSMutableDictionary alloc] init]; _requestedPaths = [[NSMutableSet alloc] init]; _shareMenuTitle = nil; - [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(start) userInfo:nil repeats:YES]; + // The NSConnection will block until the distant object came back and this creates a loop hanging Finder. + // Start from a timer to have time to unwind the stack first. + [NSTimer scheduledTimerWithTimeInterval:0 target:_syncClientProxy selector:@selector(start) userInfo:nil repeats:NO]; } return self; @@ -43,9 +46,7 @@ static RequestManager* sharedInstance = nil; - (void)dealloc { - [_socket setDelegate:nil delegateQueue:NULL]; - [_socket disconnect]; - [_socket release]; + [_syncClientProxy release]; sharedInstance = nil; @@ -65,20 +66,6 @@ static RequestManager* sharedInstance = nil; return sharedInstance; } - -- (void)askOnSocket:(NSString*)path query:(NSString*)verb -{ - NSString *query = [NSString stringWithFormat:@"%@:%@\n", verb,path]; - // NSLog(@"Query: %@", query); - - NSData* data = [query dataUsingEncoding:NSUTF8StringEncoding]; - [_socket writeData:data withTimeout:5 tag:4711]; - - NSData* stop = [@"\n" dataUsingEncoding:NSUTF8StringEncoding]; - [_socket readDataToData:stop withTimeout:-1 tag:READ_TAG]; -} - - - (BOOL)isRegisteredPath:(NSString*)path isDirectory:(BOOL)isDir { // check if the file in question is underneath a registered directory @@ -104,118 +91,53 @@ static RequestManager* sharedInstance = nil; - (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir { - NSString *verb = @"RETRIEVE_FILE_STATUS"; - if( [self isRegisteredPath:path isDirectory:isDir] ) { [_requestedPaths addObject:path]; - if( _isConnected ) { - if(isDir) { - verb = @"RETRIEVE_FOLDER_STATUS"; - } - - [self askOnSocket:path query:verb]; - } else { - [_requestQueue addObject:path]; - [self start]; // try again to connect - } + [_syncClientProxy askForIcon:path isDirectory:isDir]; } } - -- (void)socket:(GCDAsyncSocket*)socket didReadData:(NSData*)data withTag:(long)tag +- (void)setResultForPath:(NSString*)path result:(NSString*)result { - NSString *answer = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - NSArray *chunks = nil; - if (answer != nil && [answer length] > 0) { - // cut a trailing newline - answer = [answer substringToIndex:[answer length] - 1]; - chunks = [answer componentsSeparatedByString: @":"]; + // The client will broadcast all changes, do not fill the cache for paths that Finder didn't ask for. + if ([_requestedPaths containsObject:path]) { + [[ContentManager sharedInstance] setResultForPath:path result:result]; } - ContentManager *contentman = [ContentManager sharedInstance]; - - if( chunks && [chunks count] > 0 && tag == READ_TAG ) { - // NSLog(@"READ from socket (%ld): <%@>", tag, answer); - if( [[chunks objectAtIndex:0] isEqualToString:@"STATUS"] ) { - NSString *path = [chunks objectAtIndex:2]; - if( [chunks count] > 3 ) { - for( int i = 2; i < [chunks count]-1; i++ ) { - path = [NSString stringWithFormat:@"%@:%@", - path, [chunks objectAtIndex:i+1] ]; - } - } - // The client will broadcast all changes, do not fill the cache for paths that Finder didn't ask for. - if ([_requestedPaths containsObject:path]) { - [contentman setResultForPath:path result:[chunks objectAtIndex:1]]; - } - } else if( [[chunks objectAtIndex:0] isEqualToString:@"UPDATE_VIEW"] ) { - NSString *path = [chunks objectAtIndex:1]; - [_requestedPaths removeAllObjects]; - [contentman reFetchFileNameCacheForPath:path]; - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"REGISTER_PATH"] ) { - NSNumber *one = [NSNumber numberWithInt:1]; - NSString *path = [chunks objectAtIndex:1]; - // NSLog(@"Registering path: %@", path); - [_registeredPathes setObject:one forKey:path]; - - [contentman repaintAllWindows]; - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"UNREGISTER_PATH"] ) { - NSString *path = [chunks objectAtIndex:1]; - [_registeredPathes removeObjectForKey:path]; - - [contentman repaintAllWindows]; - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"ICON_PATH"] ) { - NSString *path = [chunks objectAtIndex:1]; - [[ContentManager sharedInstance] loadIconResourcePath:path]; - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"SHARE_MENU_TITLE"] ) { - _shareMenuTitle = [[chunks objectAtIndex:1] copy]; - // NSLog(@"Received shar menu title: %@", _shareMenuTitle); - } else { - NSLog(@"SyncState: Unknown command %@", [chunks objectAtIndex:0]); - } - } else if (tag != READ_TAG) { - NSLog(@"SyncState: Received unknown tag %ld <%@>", tag, answer); - } - // Read on and on - NSData* stop = [@"\n" dataUsingEncoding:NSUTF8StringEncoding]; - [_socket readDataToData:stop withTimeout:-1 tag:READ_TAG]; - } -- (NSTimeInterval)socket:(GCDAsyncSocket*)socket shouldTimeoutReadWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length +- (void)reFetchFileNameCacheForPath:(NSString*)path { - // Called if a read operation has reached its timeout without completing. - return 0.0; + [_requestedPaths removeAllObjects]; + [[ContentManager sharedInstance] reFetchFileNameCacheForPath:path]; } --(void)socket:(GCDAsyncSocket*)socket didConnectToUrl:(NSURL *)url { - // NSLog( @"Connected to sync client successfully on %@", url); - _isConnected = YES; - - [self askOnSocket:@"" query:@"SHARE_MENU_TITLE"]; - - if( [_requestQueue count] > 0 ) { - // NSLog( @"We have to empty the queue"); - for( NSString *path in _requestQueue ) { - [self askOnSocket:path query:@"RETRIEVE_FILE_STATUS"]; - } - [_requestQueue removeAllObjects]; - } - - ContentManager *contentman = [ContentManager sharedInstance]; - [contentman clearFileNameCache]; - [contentman repaintAllWindows]; - - // Read for the UPDATE_VIEW requests - NSData* stop = [@"\n" dataUsingEncoding:NSUTF8StringEncoding]; - [_socket readDataToData:stop withTimeout:-1 tag:READ_TAG]; +- (void)registerPath:(NSString*)path +{ + NSNumber *one = [NSNumber numberWithInt:1]; + [_registeredPathes setObject:one forKey:path]; + [[ContentManager sharedInstance] repaintAllWindows]; } -- (void)socketDidDisconnect:(GCDAsyncSocket*)socket withError:(NSError*)err +- (void)unregisterPath:(NSString*)path +{ + [_registeredPathes removeObjectForKey:path]; + [[ContentManager sharedInstance] repaintAllWindows]; +} + +- (void)setShareMenuTitle:(NSString*)title +{ + _shareMenuTitle = title; +} + +- (void)loadIconResourcePath:(NSString*)path +{ + [[ContentManager sharedInstance] loadIconResourcePath:path]; +} + +- (void)connectionDidDie { // NSLog(@"Socket DISconnected! %@", [err localizedDescription]); - _isConnected = NO; - // clear the registered pathes. [_registeredPathes release]; _registeredPathes = [[NSMutableDictionary alloc] init]; @@ -227,92 +149,18 @@ static RequestManager* sharedInstance = nil; [contentman repaintAllWindows]; } -- (NSDate*)fileDate:(NSString*)fn -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSError *error = 0; - NSDictionary *oneAttributes = [fileManager attributesOfItemAtPath:fn error:&error]; - if (!error) { - return [oneAttributes valueForKey:NSFileModificationDate]; - } - return nil; -} - -- (NSString*) getNewerFileOne:(NSString*)one two:(NSString*)two -{ - if (!one) { - return two; - } - NSDate *oneDate = [self fileDate:one]; - NSDate *twoDate = [self fileDate:two]; - if (oneDate && twoDate) { - if ([oneDate compare:twoDate] == NSOrderedDescending) { - return one; - } else if ([oneDate compare:twoDate] == NSOrderedAscending) { - return two; - } else { - return two; - } - } - return one; -} - - -- (void)start -{ - if (!_isConnected && ![_socket isConnected]) - { - NSError *err = nil; - NSURL *url = nil; - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - if ([paths count]) - { - // e.g file:///Users/guruz/Library/Caches/SyncStateHelper/ownCloud.socket - NSString *syncStateHelperDir = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"SyncStateHelper"]; - NSError *pnsError = NULL; - NSArray *paths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:syncStateHelperDir error:&pnsError]; - if (!pnsError && paths && [paths count] > 0) { - NSString *currentLatestPath = nil; - if (paths.count > 1) { - // NSLog(@"Possible paths: %@", paths); - } - for (int i = 0; i < paths.count; i++) { - NSString *currentPath = [syncStateHelperDir stringByAppendingPathComponent:[paths objectAtIndex:i]]; - if (![currentPath hasSuffix:@".socket"]) { - continue; - } - currentLatestPath = [self getNewerFileOne:currentLatestPath two:currentPath]; - } - // FIXME Instead of connecting to the newest socket we could go multi-socket to support multiple instances - if (currentLatestPath) { - url = [NSURL fileURLWithPath:currentLatestPath]; - } - } - } - if (url) { - // NSLog(@"Connect Socket to %@", url); - [_socket connectToUrl:url withTimeout:1 error:&err]; - } - } -} - - (void)menuItemClicked:(NSDictionary*)actionDictionary { // NSLog(@"RequestManager menuItemClicked %@", actionDictionary); NSArray *filePaths = [actionDictionary valueForKey:@"files"]; for (int i = 0; i < filePaths.count; i++) { - [self askOnSocket:[filePaths objectAtIndex:i] query:@"SHARE"]; + [_syncClientProxy askOnSocket:[filePaths objectAtIndex:i] query:@"SHARE"]; } } - (NSString*) shareItemTitle { - if (_socket && _socket.isConnected && _shareMenuTitle) { - return _shareMenuTitle; - } - return nil; + return _shareMenuTitle; } - - @end diff --git a/shell_integration/MacOSX/common/SyncClientProxy.h b/shell_integration/MacOSX/common/SyncClientProxy.h new file mode 100644 index 000000000..0e0ab475e --- /dev/null +++ b/shell_integration/MacOSX/common/SyncClientProxy.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) by Jocelyn Turcotte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#import + + +@protocol SyncClientProxyDelegate +- (void)setResultForPath:(NSString*)path result:(NSString*)result; +- (void)reFetchFileNameCacheForPath:(NSString*)path; +- (void)registerPath:(NSString*)path; +- (void)unregisterPath:(NSString*)path; +- (void)setShareMenuTitle:(NSString*)title; +- (void)loadIconResourcePath:(NSString*)path; +- (void)connectionDidDie; +@end + +@protocol ChannelProtocol +- (void)sendMessage:(NSData*)msg; +@end + +@interface SyncClientProxy : NSObject +{ + NSString *_serverName; + NSDistantObject *_remoteEnd; +} + +@property (weak) id delegate; + +- (instancetype)initWithDelegate:(id)arg1 serverName:(NSString*)serverName; +- (void)start; +- (void)askOnSocket:(NSString*)path query:(NSString*)verb; +- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir; +@end diff --git a/shell_integration/MacOSX/common/SyncClientProxy.m b/shell_integration/MacOSX/common/SyncClientProxy.m new file mode 100644 index 000000000..aab9b7510 --- /dev/null +++ b/shell_integration/MacOSX/common/SyncClientProxy.m @@ -0,0 +1,151 @@ +/* + * Copyright (C) by Jocelyn Turcotte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#import "SyncClientProxy.h" + +@protocol ServerProtocol +- (void)registerClient:(id)client; +@end + +@interface SyncClientProxy () +- (void)registerTransmitter:(id)tx; +@end + +@implementation SyncClientProxy + +- (instancetype)initWithDelegate:(id)arg1 serverName:(NSString*)serverName +{ + self = [super init]; + + self.delegate = arg1; + _serverName = serverName; + _remoteEnd = nil; + + return self; +} + +#pragma mark - Connection setup + +- (void)start +{ + if (_remoteEnd) + return; + + // Lookup the server connection + NSConnection *conn = [NSConnection connectionWithRegisteredName:_serverName host:nil]; + + if (!conn) { + // Could not connect to the sync client + [self scheduleRetry]; + return; + } + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(connectionDidDie:) + name:NSConnectionDidDieNotification + object:conn]; + + NSDistantObject *server = (NSDistantObject *)[conn rootProxy]; + assert(server); + + // This saves a few Mach messages, enable "Distributed Objects" in the scheme's Run diagnostics to watch + [server setProtocolForProxy:@protocol(ServerProtocol)]; + + // Send an object to the server to act as the channel rx, we'll receive the tx through registerTransmitter + [server registerClient:self]; +} + +- (void)registerTransmitter:(id)tx; +{ + // The server replied with the distant object that we will use for tx + _remoteEnd = (NSDistantObject *)tx; + [_remoteEnd setProtocolForProxy:@protocol(ChannelProtocol)]; + + // Everything is set up, start querrying + [self askOnSocket:@"" query:@"SHARE_MENU_TITLE"]; +} + +- (void)scheduleRetry +{ + [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(start) userInfo:nil repeats:NO]; +} + +- (void)connectionDidDie:(NSNotification*)notification +{ +#pragma unused(notification) + _remoteEnd = nil; + [_delegate connectionDidDie]; + + [self scheduleRetry]; +} + +#pragma mark - Communication logic + +- (void)sendMessage:(NSData*)msg +{ + NSString *answer = [[NSString alloc] initWithData:msg encoding:NSUTF8StringEncoding]; + + // Cut the trailing newline + answer = [answer substringToIndex:[answer length] - 1]; + NSArray *chunks = [answer componentsSeparatedByString: @":"]; + + if( [[chunks objectAtIndex:0] isEqualToString:@"STATUS"] ) { + NSString *result = [chunks objectAtIndex:1]; + NSString *path = [chunks objectAtIndex:2]; + if( [chunks count] > 3 ) { + for( int i = 2; i < [chunks count]-1; i++ ) { + path = [NSString stringWithFormat:@"%@:%@", + path, [chunks objectAtIndex:i+1] ]; + } + } + [_delegate setResultForPath:path result:result]; + } else if( [[chunks objectAtIndex:0] isEqualToString:@"UPDATE_VIEW"] ) { + NSString *path = [chunks objectAtIndex:1]; + [_delegate reFetchFileNameCacheForPath:path]; + } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"REGISTER_PATH"] ) { + NSString *path = [chunks objectAtIndex:1]; + [_delegate registerPath:path]; + } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"UNREGISTER_PATH"] ) { + NSString *path = [chunks objectAtIndex:1]; + [_delegate unregisterPath:path]; + } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"ICON_PATH"] ) { + // FIXME: Should also go away once we move icons into the bundle + NSString *path = [chunks objectAtIndex:1]; + [_delegate loadIconResourcePath:path]; + } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"SHARE_MENU_TITLE"] ) { + [_delegate setShareMenuTitle:[chunks objectAtIndex:1]]; + } else { + NSLog(@"SyncState: Unknown command %@", [chunks objectAtIndex:0]); + } +} + +- (void)askOnSocket:(NSString*)path query:(NSString*)verb +{ + NSString *query = [NSString stringWithFormat:@"%@:%@\n", verb,path]; + + @try { + [_remoteEnd sendMessage:[query dataUsingEncoding:NSUTF8StringEncoding]]; + } @catch(NSException* e) { + // Do nothing and wait for connectionDidDie + } +} + +- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir +{ + NSString *verb = isDir ? @"RETRIEVE_FOLDER_STATUS" : @"RETRIEVE_FILE_STATUS"; + [self askOnSocket:path query:verb]; +} + +@end + diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 0f85c3df1..369a36506 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -87,6 +87,7 @@ set(updater_SRCS IF( APPLE ) list(APPEND client_SRCS cocoainitializer_mac.mm) list(APPEND client_SRCS settingsdialogmac.cpp) + list(APPEND client_SRCS socketapisocket_mac.mm) list(APPEND client_SRCS systray.mm) if(SPARKLE_FOUND) diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index be7c037f6..aecb2c87e 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -84,13 +84,11 @@ SocketApi::SocketApi(QObject* parent) // See issue #2388 // + Theme::instance()->appName(); } else if (Utility::isMac()) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - // Always using Qt5 on OS X - QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); - socketPath = runtimeDir + "/SyncStateHelper/" + Theme::instance()->appName() + ".socket"; - // We use the generic SyncStateHelper name on OS X since the different branded clients - // should unfortunately not mention that they are ownCloud :-) -#endif + // This much match the code signing Team setting of the extension + // FIXME: Hardcoded for now, but if we want to allow builds to be + // signed by third party Apple Developer accounts, we'll have to + // allow changing this through the build system. + socketPath = "9B5WD74GWJ." APPLICATION_REV_DOMAIN ".socketApi"; } else if( Utility::isLinux() || Utility::isBSD() ) { QString runtimeDir; #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) @@ -108,7 +106,7 @@ SocketApi::SocketApi(QObject* parent) DEBUG << "An unexpected system detected"; } - QLocalServer::removeServer(socketPath); + SocketApiServer::removeServer(socketPath); QFileInfo info(socketPath); if (!info.dir().exists()) { bool result = info.dir().mkpath("."); @@ -167,7 +165,7 @@ void SocketApi::slotReadExcludes() void SocketApi::slotNewConnection() { - QLocalSocket* socket = _localServer.nextPendingConnection(); + QIODevice* socket = _localServer.nextPendingConnection(); if( ! socket ) { return; @@ -199,7 +197,7 @@ void SocketApi::onLostConnection() { DEBUG << "Lost connection " << sender(); - QLocalSocket* socket = qobject_cast(sender()); + QIODevice* socket = qobject_cast(sender()); _listeners.removeAll(socket); socket->deleteLater(); } @@ -207,7 +205,7 @@ void SocketApi::onLostConnection() void SocketApi::slotReadSocket() { - QLocalSocket* socket = qobject_cast(sender()); + QIODevice* socket = qobject_cast(sender()); Q_ASSERT(socket); while(socket->canReadLine()) { @@ -215,12 +213,12 @@ void SocketApi::slotReadSocket() QString command = line.split(":").first(); QString function = QString(QLatin1String("command_")).append(command); - QString functionWithArguments = function + QLatin1String("(QString,QLocalSocket*)"); + QString functionWithArguments = function + QLatin1String("(QString,QIODevice*)"); int indexOfMethod = this->metaObject()->indexOfMethod(functionWithArguments.toAscii()); QString argument = line.remove(0, command.length()+1).trimmed(); if(indexOfMethod != -1) { - QMetaObject::invokeMethod(this, function.toAscii(), Q_ARG(QString, argument), Q_ARG(QLocalSocket*, socket)); + QMetaObject::invokeMethod(this, function.toAscii(), Q_ARG(QString, argument), Q_ARG(QIODevice*, socket)); } else { DEBUG << "The command is not supported by this version of the client:" << command << "with argument:" << argument; } @@ -232,7 +230,7 @@ void SocketApi::slotRegisterPath( const QString& alias ) Folder *f = FolderMan::instance()->folder(alias); if (f) { QString message = buildRegisterPathMessage(f->path()); - foreach(QLocalSocket *socket, _listeners) { + foreach(QIODevice *socket, _listeners) { sendMessage(socket, message); } } @@ -336,7 +334,7 @@ void SocketApi::slotSyncItemDiscovered(const QString &folder, const SyncFileItem -void SocketApi::sendMessage(QLocalSocket *socket, const QString& message, bool doWait) +void SocketApi::sendMessage(QIODevice *socket, const QString& message, bool doWait) { DEBUG << "Sending message: " << message; QString localMessage = message; @@ -369,14 +367,12 @@ void SocketApi::broadcastMessage( const QString& verb, const QString& path, cons msg.append(QDir::toNativeSeparators(fi.absoluteFilePath())); } - // sendMessage already has a debug output - //DEBUG << "Broadcasting to" << _listeners.count() << "listeners: " << msg; - foreach(QLocalSocket *socket, _listeners) { + foreach(QIODevice *socket, _listeners) { sendMessage(socket, msg, doWait); } } -void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString& argument, QLocalSocket* socket) +void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString& argument, QIODevice* socket) { // This command is the same as RETRIEVE_FILE_STATUS @@ -384,7 +380,7 @@ void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString& argument, QLocalSo command_RETRIEVE_FILE_STATUS(argument, socket); } -void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QLocalSocket* socket) +void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QIODevice* socket) { if( !socket ) { qDebug() << "No valid socket object."; @@ -412,7 +408,7 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QLocalSock sendMessage(socket, message); } -void SocketApi::command_SHARE(const QString& localFile, QLocalSocket* socket) +void SocketApi::command_SHARE(const QString& localFile, QIODevice* socket) { if (!socket) { qDebug() << Q_FUNC_INFO << "No valid socket object."; @@ -450,12 +446,12 @@ void SocketApi::command_SHARE(const QString& localFile, QLocalSocket* socket) } } -void SocketApi::command_VERSION(const QString&, QLocalSocket* socket) +void SocketApi::command_VERSION(const QString&, QIODevice* socket) { sendMessage(socket, QLatin1String("VERSION:" MIRALL_VERSION_STRING ":" MIRALL_SOCKET_API_VERSION)); } -void SocketApi::command_SHARE_MENU_TITLE(const QString &, QLocalSocket* socket) +void SocketApi::command_SHARE_MENU_TITLE(const QString &, QIODevice* socket) { sendMessage(socket, QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is ownCloud").arg(Theme::instance()->appNameGUI())); } diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index 64982bbc4..3fe33516c 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -20,17 +20,17 @@ extern "C" { #include } -#include - -#include -#include -#include -#include - #include "syncfileitem.h" #include "syncjournalfilerecord.h" #include "ownsql.h" +#if defined(Q_OS_MAC) +#include "socketapisocket_mac.h" +#else +#include +typedef QLocalServer SocketApiServer; +#endif + class QUrl; class QLocalSocket; class QStringList; @@ -70,20 +70,20 @@ private: SyncJournalFileRecord dbFileRecord_capi( Folder *folder, QString fileName ); SqlQuery *getSqlQuery( Folder *folder ); - void sendMessage(QLocalSocket* socket, const QString& message, bool doWait = false); + void sendMessage(QIODevice* socket, const QString& message, bool doWait = false); void broadcastMessage(const QString& verb, const QString &path, const QString &status = QString::null, bool doWait = false); - Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString& argument, QLocalSocket* socket); - Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString& argument, QLocalSocket* socket); - Q_INVOKABLE void command_SHARE(const QString& localFile, QLocalSocket* socket); + Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString& argument, QIODevice* socket); + Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString& argument, QIODevice* socket); + Q_INVOKABLE void command_SHARE(const QString& localFile, QIODevice* socket); - Q_INVOKABLE void command_VERSION(const QString& argument, QLocalSocket* socket); + Q_INVOKABLE void command_VERSION(const QString& argument, QIODevice* socket); - Q_INVOKABLE void command_SHARE_MENU_TITLE(const QString& argument, QLocalSocket* socket); + Q_INVOKABLE void command_SHARE_MENU_TITLE(const QString& argument, QIODevice* socket); QString buildRegisterPathMessage(const QString& path); - QList _listeners; - QLocalServer _localServer; + QList _listeners; + SocketApiServer _localServer; c_strlist_t *_excludes; QHash> _dbQueries; QHash> _openDbs; diff --git a/src/gui/socketapisocket_mac.h b/src/gui/socketapisocket_mac.h new file mode 100644 index 000000000..b76545351 --- /dev/null +++ b/src/gui/socketapisocket_mac.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) by Jocelyn Turcotte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifndef SOCKETAPISOCKET_OSX_H +#define SOCKETAPISOCKET_OSX_H + +#include +#include + +class SocketApiServerPrivate; +class SocketApiSocketPrivate; + +class SocketApiSocket : public QIODevice +{ + Q_OBJECT +public: + SocketApiSocket(QObject *parent, SocketApiSocketPrivate *p); + ~SocketApiSocket(); + + qint64 readData(char *data, qint64 maxlen) override; + qint64 writeData(const char *data, qint64 len) override; + + bool isSequential() const override { return true; } + qint64 bytesAvailable() const override; + bool canReadLine() const override; + +signals: + void disconnected(); + +private: + // Use Qt's p-impl system to hide objective-c types from C++ code including this file + Q_DECLARE_PRIVATE(SocketApiSocket) + QScopedPointer d_ptr; + friend class SocketApiServerPrivate; +}; + +class SocketApiServer : public QObject +{ + Q_OBJECT +public: + SocketApiServer(); + ~SocketApiServer(); + + void close(); + bool listen(const QString &name); + SocketApiSocket *nextPendingConnection(); + + static bool removeServer(const QString &) { return false; } + +signals: + void newConnection(); + +private: + Q_DECLARE_PRIVATE(SocketApiServer) + QScopedPointer d_ptr; +}; + +#endif // SOCKETAPISOCKET_OSX_H diff --git a/src/gui/socketapisocket_mac.mm b/src/gui/socketapisocket_mac.mm new file mode 100644 index 000000000..554447cf1 --- /dev/null +++ b/src/gui/socketapisocket_mac.mm @@ -0,0 +1,225 @@ +/* + * Copyright (C) by Jocelyn Turcotte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "socketapisocket_mac.h" +#import + +@protocol ChannelProtocol +- (void)sendMessage:(NSData*)msg; +@end + +@protocol RemoteEndProtocol +- (void)registerTransmitter:(id)tx; +@end + +@interface LocalEnd : NSObject +@property SocketApiSocketPrivate *wrapper; +- (instancetype)initWithWrapper:(SocketApiSocketPrivate *)wrapper; +@end + +@interface Server : NSObject +@property SocketApiServerPrivate *wrapper; +- (instancetype)initWithWrapper:(SocketApiServerPrivate *)wrapper; +- (void)registerClient:(NSDistantObject *)remoteEnd; +@end + +class SocketApiSocketPrivate +{ +public: + SocketApiSocket *q_ptr; + + SocketApiSocketPrivate(NSDistantObject *remoteEnd); + ~SocketApiSocketPrivate(); + + NSDistantObject *remoteEnd; + LocalEnd *localEnd; + QByteArray inBuffer; +}; + +class SocketApiServerPrivate +{ +public: + SocketApiServer *q_ptr; + + SocketApiServerPrivate(); + ~SocketApiServerPrivate(); + + QList pendingConnections; + NSConnection *connection; + Server *server; +}; + + +@implementation LocalEnd +- (instancetype)initWithWrapper:(SocketApiSocketPrivate *)wrapper +{ + self = [super init]; + self->_wrapper = wrapper; + return self; +} + +- (void)sendMessage:(NSData*)msg +{ + if (_wrapper) { + _wrapper->inBuffer += QByteArray::fromRawNSData(msg); + emit _wrapper->q_ptr->readyRead(); + } +} + +- (void)connectionDidDie:(NSNotification*)notification +{ +#pragma unused(notification) + if (_wrapper) + emit _wrapper->q_ptr->disconnected(); +} +@end + +@implementation Server +- (instancetype)initWithWrapper:(SocketApiServerPrivate *)wrapper +{ + self = [super init]; + self->_wrapper = wrapper; + return self; +} + +- (void)registerClient:(NSDistantObject *)remoteEnd +{ + // This saves a few mach messages that would otherwise be needed to query the interface + [remoteEnd setProtocolForProxy:@protocol(RemoteEndProtocol)]; + + SocketApiServer *server = _wrapper->q_ptr; + SocketApiSocketPrivate *socketPrivate = new SocketApiSocketPrivate(remoteEnd); + SocketApiSocket *socket = new SocketApiSocket(server, socketPrivate); + _wrapper->pendingConnections.append(socket); + emit server->newConnection(); + + [remoteEnd registerTransmitter:socketPrivate->localEnd]; +} +@end + + +SocketApiSocket::SocketApiSocket(QObject *parent, SocketApiSocketPrivate *p) + : QIODevice(parent) + , d_ptr(p) +{ + Q_D(SocketApiSocket); + d->q_ptr = this; + open(ReadWrite); +} + +SocketApiSocket::~SocketApiSocket() +{ +} + +qint64 SocketApiSocket::readData(char *data, qint64 maxlen) +{ + Q_D(SocketApiSocket); + qint64 len = std::min(maxlen, static_cast(d->inBuffer.size())); + memcpy(data, d->inBuffer.constData(), len); + d->inBuffer.remove(0, len); + return len; +} + +qint64 SocketApiSocket::writeData(const char *data, qint64 len) +{ + @try { + Q_D(SocketApiSocket); + // FIXME: The NSConnection will make this block unless the function is marked as "oneway" + // in the protocol. This isn't async and reduces our performances but this currectly avoids + // a Mach queue deadlock during requests bursts of the legacy OwnCloudFinder extension. + // Since FinderSync already runs in a separate process, blocking isn't too critical. + [d->remoteEnd sendMessage:[NSData dataWithBytesNoCopy:const_cast(data) length:len freeWhenDone:NO]]; + return len; + } @catch(NSException* e) { + // connectionDidDie can be notified too late, also interpret any sending exception as a disconnection. + emit disconnected(); + return -1; + } +} + +qint64 SocketApiSocket::bytesAvailable() const +{ + Q_D(const SocketApiSocket); + return d->inBuffer.size() + QIODevice::bytesAvailable(); +} + +bool SocketApiSocket::canReadLine() const +{ + Q_D(const SocketApiSocket); + return d->inBuffer.indexOf('\n', int(pos())) != -1 || QIODevice::canReadLine(); +} + +SocketApiSocketPrivate::SocketApiSocketPrivate(NSDistantObject *remoteEnd) + : remoteEnd(remoteEnd) + , localEnd([[LocalEnd alloc] initWithWrapper:this]) +{ + [remoteEnd retain]; + // (Ab)use our objective-c object just to catch the notification + [[NSNotificationCenter defaultCenter] addObserver:localEnd + selector:@selector(connectionDidDie:) + name:NSConnectionDidDieNotification + object:[remoteEnd connectionForProxy]]; +} + +SocketApiSocketPrivate::~SocketApiSocketPrivate() +{ + [remoteEnd release]; + // The DO vended localEnd might still be referenced by the connection + localEnd.wrapper = nil; + [localEnd release]; +} + +SocketApiServer::SocketApiServer() + : d_ptr(new SocketApiServerPrivate) +{ + Q_D(SocketApiServer); + d->q_ptr = this; +} + +SocketApiServer::~SocketApiServer() +{ +} + +void SocketApiServer::close() +{ + // Assume we'll be destroyed right after +} + +bool SocketApiServer::listen(const QString &name) +{ + Q_D(SocketApiServer); + // Set the name of the root object + return [d->connection registerName:name.toNSString()]; +} + +SocketApiSocket *SocketApiServer::nextPendingConnection() +{ + Q_D(SocketApiServer); + return d->pendingConnections.takeFirst(); +} + +SocketApiServerPrivate::SocketApiServerPrivate() +{ + // Create the connection and server object to vend over Disributed Objects + connection = [[NSConnection alloc] init]; + server = [[Server alloc] initWithWrapper:this]; + [connection setRootObject:server]; +} + +SocketApiServerPrivate::~SocketApiServerPrivate() +{ + [connection release]; + server.wrapper = nil; + [server release]; +} From 02f6dbba46a44aed3fc9550090646f13c1bf59ae Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Mon, 15 Jun 2015 14:57:33 +0200 Subject: [PATCH 02/14] shell_i: Add a FinderSync-based implementation #2340 This uses the new official API to show overlay icons and add our custom context menu entry instead of hooking directly into the Finder process and intercept drawind routines. A dummy desktopclient target is also in the project to allow debugging directly in Xcode while the official client can be started from the command line. Otherwise Xcode won't allow attaching to the debugee. Dummy icon files have been added while we get proper icon produced. We can't use the old icons since what we use for the legacy shell integration is already padded according to where the badge should appear on the full icon. --- shell_integration/MacOSX/CMakeLists.txt | 20 +- .../contents.xcworkspacedata | 3 + .../xcshareddata/OwnCloud.xccheckout | 10 +- .../FinderSyncExt/FinderSync.h | 28 + .../FinderSyncExt/FinderSync.m | 161 ++++++ .../FinderSyncExt/FinderSyncExt.entitlements | 12 + .../FinderSyncExt/Info.plist | 43 ++ .../project.pbxproj | 534 ++++++++++++++++++ .../xcschemes/FinderSyncExt.xcscheme | 105 ++++ .../desktopclient/Info.plist | 30 + .../OwnCloudFinderSync/desktopclient/main.m | 16 + .../nopadding/error.iconset/icon_32x32.png | Bin 0 -> 1816 bytes .../error_swm.iconset/icon_32x32.png | Bin 0 -> 2541 bytes .../icons/nopadding/ok.iconset/icon_32x32.png | Bin 0 -> 1825 bytes .../nopadding/ok_swm.iconset/icon_32x32.png | Bin 0 -> 2543 bytes .../nopadding/sync.iconset/icon_32x32.png | Bin 0 -> 1884 bytes .../nopadding/sync_swm.iconset/icon_32x32.png | Bin 0 -> 2592 bytes .../nopadding/warning.iconset/icon_32x32.png | Bin 0 -> 1800 bytes .../warning_swm.iconset/icon_32x32.png | Bin 0 -> 2498 bytes 19 files changed, 954 insertions(+), 8 deletions(-) create mode 100644 shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h create mode 100644 shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m create mode 100644 shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSyncExt.entitlements create mode 100644 shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/Info.plist create mode 100644 shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj create mode 100644 shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/xcshareddata/xcschemes/FinderSyncExt.xcscheme create mode 100644 shell_integration/MacOSX/OwnCloudFinderSync/desktopclient/Info.plist create mode 100644 shell_integration/MacOSX/OwnCloudFinderSync/desktopclient/main.m create mode 100644 shell_integration/icons/nopadding/error.iconset/icon_32x32.png create mode 100644 shell_integration/icons/nopadding/error_swm.iconset/icon_32x32.png create mode 100644 shell_integration/icons/nopadding/ok.iconset/icon_32x32.png create mode 100644 shell_integration/icons/nopadding/ok_swm.iconset/icon_32x32.png create mode 100644 shell_integration/icons/nopadding/sync.iconset/icon_32x32.png create mode 100644 shell_integration/icons/nopadding/sync_swm.iconset/icon_32x32.png create mode 100644 shell_integration/icons/nopadding/warning.iconset/icon_32x32.png create mode 100644 shell_integration/icons/nopadding/warning_swm.iconset/icon_32x32.png diff --git a/shell_integration/MacOSX/CMakeLists.txt b/shell_integration/MacOSX/CMakeLists.txt index d5730b1aa..b18bf080a 100644 --- a/shell_integration/MacOSX/CMakeLists.txt +++ b/shell_integration/MacOSX/CMakeLists.txt @@ -1,11 +1,25 @@ if(APPLE) -add_custom_target( mac_overlayplugin ALL - xcodebuild -workspace ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/OwnCloud.xcworkspace +add_custom_target( legacy_mac_overlayplugin ALL + xcodebuild -workspace ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/OwnCloud.xcworkspace -scheme SyncStateFinder.osax SYMROOT=${CMAKE_CURRENT_BINARY_DIR} archive - COMMENT building Mac Overlay icons) + COMMENT building Legacy Mac Overlay icons) + +# The bundle identifier and application group need to have compatible values with the client +# to be able to open a Mach port across the extension's sandbox boundary. +# Pass the info through the xcodebuild command line and make sure that the project uses +# those user-defined settings to build the plist. +add_custom_target( mac_overlayplugin ALL + xcodebuild -project ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj + -target FinderSyncExt -configuration Release SYMROOT=${CMAKE_CURRENT_BINARY_DIR} + OC_APPLICATION_NAME=${APPLICATION_NAME} + OC_APPLICATION_REV_DOMAIN=${APPLICATION_REV_DOMAIN} + COMMENT building Mac Overlay icons) INSTALL( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Release/SyncStateFinder.osax/Contents DESTINATION ${CMAKE_INSTALL_PREFIX}/Library/ScriptingAdditions/SyncStateFinder.osax/ ) +INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Release/FinderSyncExt.appex + DESTINATION ${OWNCLOUD_OSX_BUNDLE}/Contents/Plugins + USE_SOURCE_PERMISSIONS) endif(APPLE) diff --git a/shell_integration/MacOSX/OwnCloud.xcworkspace/contents.xcworkspacedata b/shell_integration/MacOSX/OwnCloud.xcworkspace/contents.xcworkspacedata index a21afc399..26fba3447 100644 --- a/shell_integration/MacOSX/OwnCloud.xcworkspace/contents.xcworkspacedata +++ b/shell_integration/MacOSX/OwnCloud.xcworkspace/contents.xcworkspacedata @@ -1,6 +1,9 @@ + + diff --git a/shell_integration/MacOSX/OwnCloud.xcworkspace/xcshareddata/OwnCloud.xccheckout b/shell_integration/MacOSX/OwnCloud.xcworkspace/xcshareddata/OwnCloud.xccheckout index 8320a7f7b..9512111fd 100644 --- a/shell_integration/MacOSX/OwnCloud.xcworkspace/xcshareddata/OwnCloud.xccheckout +++ b/shell_integration/MacOSX/OwnCloud.xcworkspace/xcshareddata/OwnCloud.xccheckout @@ -10,29 +10,29 @@ OwnCloud IDESourceControlProjectOriginsDictionary - 09EE94AA-F410-4594-AB26-5A0220DEAEC7 + D67321A19EF879CA55BF889202BA8C23AC9AA2B5 ssh://github.com/owncloud/client.git IDESourceControlProjectPath shell_integration/MacOSX/OwnCloud.xcworkspace IDESourceControlProjectRelativeInstallPathDictionary - 09EE94AA-F410-4594-AB26-5A0220DEAEC7 + D67321A19EF879CA55BF889202BA8C23AC9AA2B5 ../../.. IDESourceControlProjectURL ssh://github.com/owncloud/client.git IDESourceControlProjectVersion - 110 + 111 IDESourceControlProjectWCCIdentifier - 09EE94AA-F410-4594-AB26-5A0220DEAEC7 + D67321A19EF879CA55BF889202BA8C23AC9AA2B5 IDESourceControlProjectWCConfigurations IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey - 09EE94AA-F410-4594-AB26-5A0220DEAEC7 + D67321A19EF879CA55BF889202BA8C23AC9AA2B5 IDESourceControlWCCName client diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h new file mode 100644 index 000000000..749f84931 --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) by Jocelyn Turcotte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + + +#import +#import +#import "SyncClientProxy.h" + +@interface FinderSync : FIFinderSync +{ + SyncClientProxy *_syncClientProxy; + NSMutableSet *_registeredDirectories; + NSMutableSet *_requestedUrls; + NSString *_shareMenuTitle; +} + +@end diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m new file mode 100644 index 000000000..8c0c2c0a8 --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m @@ -0,0 +1,161 @@ +/* + * Copyright (C) by Jocelyn Turcotte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + + +#import "FinderSync.h" + + +@implementation FinderSync + +- (instancetype)init +{ + self = [super init]; + + FIFinderSyncController *syncController = [FIFinderSyncController defaultController]; + NSBundle *extBundle = [NSBundle bundleForClass:[self class]]; + // This was added to the bundle's Info.plist to get it from the build system + NSString *teamIdentifierPrefix = [extBundle objectForInfoDictionaryKey:@"TeamIdentifierPrefix"]; + + [syncController setBadgeImage:[extBundle imageForResource:@"ok.icns"] label:nil forBadgeIdentifier:@"OK"]; + [syncController setBadgeImage:[extBundle imageForResource:@"sync.icns"] label:nil forBadgeIdentifier:@"SYNC"]; + [syncController setBadgeImage:[extBundle imageForResource:@"sync.icns"] label:nil forBadgeIdentifier:@"NEW"]; + [syncController setBadgeImage:[extBundle imageForResource:@"warning.icns"] label:nil forBadgeIdentifier:@"IGNORE"]; + [syncController setBadgeImage:[extBundle imageForResource:@"error.icns"] label:nil forBadgeIdentifier:@"ERROR"]; + [syncController setBadgeImage:[extBundle imageForResource:@"ok_swm.icns"] label:nil forBadgeIdentifier:@"OK+SWM"]; + [syncController setBadgeImage:[extBundle imageForResource:@"sync_swm.icns"] label:nil forBadgeIdentifier:@"SYNC+SWM"]; + [syncController setBadgeImage:[extBundle imageForResource:@"sync_swm.icns"] label:nil forBadgeIdentifier:@"NEW+SWM"]; + [syncController setBadgeImage:[extBundle imageForResource:@"warning_swm.icns"] label:nil forBadgeIdentifier:@"IGNORE+SWM"]; + [syncController setBadgeImage:[extBundle imageForResource:@"error_swm.icns"] label:nil forBadgeIdentifier:@"ERROR+SWM"]; + + // The Mach post name needs to be prefixed with the code signing Team ID + // https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24 + NSString *serverName = [[teamIdentifierPrefix stringByAppendingString:[extBundle bundleIdentifier]] + stringByReplacingOccurrencesOfString:@".FinderSyncExt" withString:@".socketApi"]; + + _syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName]; + _registeredDirectories = [[NSMutableSet alloc] init]; + _requestedUrls = [[NSMutableSet alloc] init]; + _shareMenuTitle = nil; + + [_syncClientProxy start]; + return self; +} + +#pragma mark - Primary Finder Sync protocol methods + +- (void)endObservingDirectoryAtURL:(NSURL *)url +{ + // The user is no longer seeing the container's contents. + // At this point we know that the status of any file as a direct child of url.filePathURL + // won't be displayed. Filter our _requestedUrls to get rid of them. + NSString *observedDirectoryPath = [url.filePathURL path]; + [_requestedUrls filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { + NSURL *requestedUrl = (NSURL *)evaluatedObject; + NSString *parentDir = [[requestedUrl path] stringByDeletingLastPathComponent]; + return [parentDir isEqualToString:observedDirectoryPath]; + }]]; +} + +- (void)requestBadgeIdentifierForURL:(NSURL *)url +{ + [_requestedUrls addObject:url.filePathURL]; + + BOOL isDir; + if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory: &isDir] == NO) { + NSLog(@"ERROR: Could not determine file type of %@", [url path]); + isDir = NO; + } + + NSString* normalizedPath = [[url path] decomposedStringWithCanonicalMapping]; + [_syncClientProxy askForIcon:normalizedPath isDirectory:isDir]; +} + +#pragma mark - Menu and toolbar item support + +- (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu +{ + if (_shareMenuTitle) { + NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; + [menu addItemWithTitle:_shareMenuTitle action:@selector(shareMenuAction:) keyEquivalent:@"title"]; + + return menu; + } + return nil; +} + +- (IBAction)shareMenuAction:(id)sender +{ + NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs]; + + [items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { + NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping]; + [_syncClientProxy askOnSocket:normalizedPath query:@"SHARE"]; + }]; +} + +#pragma mark - SyncClientProxyDelegate implementation + +- (void)setResultForPath:(NSString*)path result:(NSString*)result +{ + NSString *normalizedPath = [path decomposedStringWithCanonicalMapping]; + [[FIFinderSyncController defaultController] setBadgeIdentifier:result forURL:[NSURL fileURLWithPath:normalizedPath]]; +} + +- (void)reFetchFileNameCacheForPath:(NSString*)path +{ + // This shouldn't be necessary, and will be a problem when we + // filter values of _requestedUrls even though Finder might still + // have an old status in its cache (and therefore won't re-request it) + // but will do OK until we get the socket API to re-push the status of everything needed. + [_requestedUrls enumerateObjectsUsingBlock: ^(id url, BOOL *stop) { + if ([[url path] hasPrefix:path]) + [self requestBadgeIdentifierForURL: url]; + }]; +} + +- (void)registerPath:(NSString*)path +{ + assert(_registeredDirectories); + [_registeredDirectories addObject:[NSURL fileURLWithPath:path]]; + [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories; +} + +- (void)unregisterPath:(NSString*)path +{ + [_registeredDirectories removeObject:[NSURL fileURLWithPath:path]]; + [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories; +} + +- (void)setShareMenuTitle:(NSString*)title +{ + _shareMenuTitle = title; +} + +- (void)loadIconResourcePath:(NSString*)path +{ +#pragma unused(path) +} + +- (void)connectionDidDie +{ + _shareMenuTitle = nil; + + // This will tell Finder that this extension isn't attached to any directory + // until we can reconnect to the sync client. + [_registeredDirectories removeAllObjects]; + [FIFinderSyncController defaultController].directoryURLs = nil; +} + +@end + diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSyncExt.entitlements b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSyncExt.entitlements new file mode 100644 index 000000000..20605791a --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSyncExt.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + $(TeamIdentifierPrefix)$(OC_APPLICATION_REV_DOMAIN) + + + diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/Info.plist b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/Info.plist new file mode 100644 index 000000000..36fe63e7c --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/Info.plist @@ -0,0 +1,43 @@ + + + + + TeamIdentifierPrefix + $(TeamIdentifierPrefix) + CFBundleDevelopmentRegion + en + CFBundleDisplayName + $(OC_APPLICATION_NAME) Extensions + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(OC_APPLICATION_REV_DOMAIN).$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + LSUIElement + + NSExtension + + NSExtensionAttributes + + NSExtensionPointIdentifier + com.apple.FinderSync + NSExtensionPrincipalClass + FinderSync + + NSPrincipalClass + NSApplication + + diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj new file mode 100644 index 000000000..61aead9a2 --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj @@ -0,0 +1,534 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + C2B573BA1B1CD91E00303B36 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573B91B1CD91E00303B36 /* main.m */; }; + C2B573D21B1CD94B00303B36 /* main.m in Resources */ = {isa = PBXBuildFile; fileRef = C2B573B91B1CD91E00303B36 /* main.m */; }; + C2B573DE1B1CD9CE00303B36 /* FinderSync.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573DD1B1CD9CE00303B36 /* FinderSync.m */; }; + C2B573E21B1CD9CE00303B36 /* FinderSyncExt.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C2B573D71B1CD9CE00303B36 /* FinderSyncExt.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + C2B573E91B1DA1FB00303B36 /* SyncClientProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573E81B1DA1FB00303B36 /* SyncClientProxy.m */; }; + C2B573F21B1DAD6400303B36 /* error_swm.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EA1B1DAD6400303B36 /* error_swm.iconset */; }; + C2B573F31B1DAD6400303B36 /* error.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EB1B1DAD6400303B36 /* error.iconset */; }; + C2B573F41B1DAD6400303B36 /* ok_swm.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EC1B1DAD6400303B36 /* ok_swm.iconset */; }; + C2B573F51B1DAD6400303B36 /* ok.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573ED1B1DAD6400303B36 /* ok.iconset */; }; + C2B573F61B1DAD6400303B36 /* sync_swm.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EE1B1DAD6400303B36 /* sync_swm.iconset */; }; + C2B573F71B1DAD6400303B36 /* sync.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EF1B1DAD6400303B36 /* sync.iconset */; }; + C2B573F81B1DAD6400303B36 /* warning_swm.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573F01B1DAD6400303B36 /* warning_swm.iconset */; }; + C2B573F91B1DAD6400303B36 /* warning.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573F11B1DAD6400303B36 /* warning.iconset */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + C2B573DF1B1CD9CE00303B36 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C2B573951B1CD88000303B36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C2B573D61B1CD9CE00303B36; + remoteInfo = FinderSyncExt; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + C2B573E11B1CD9CE00303B36 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + C2B573E21B1CD9CE00303B36 /* FinderSyncExt.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + C2B573B11B1CD91E00303B36 /* desktopclient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktopclient.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C2B573B51B1CD91E00303B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C2B573B91B1CD91E00303B36 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + C2B573D71B1CD9CE00303B36 /* FinderSyncExt.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FinderSyncExt.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + C2B573DA1B1CD9CE00303B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C2B573DB1B1CD9CE00303B36 /* FinderSyncExt.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = FinderSyncExt.entitlements; sourceTree = ""; }; + C2B573DC1B1CD9CE00303B36 /* FinderSync.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FinderSync.h; sourceTree = ""; }; + C2B573DD1B1CD9CE00303B36 /* FinderSync.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FinderSync.m; sourceTree = ""; }; + C2B573E71B1DA1FB00303B36 /* SyncClientProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SyncClientProxy.h; sourceTree = ""; }; + C2B573E81B1DA1FB00303B36 /* SyncClientProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncClientProxy.m; sourceTree = ""; }; + C2B573EA1B1DAD6400303B36 /* error_swm.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = error_swm.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/error_swm.iconset; sourceTree = ""; }; + C2B573EB1B1DAD6400303B36 /* error.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = error.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/error.iconset; sourceTree = ""; }; + C2B573EC1B1DAD6400303B36 /* ok_swm.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = ok_swm.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/ok_swm.iconset; sourceTree = ""; }; + C2B573ED1B1DAD6400303B36 /* ok.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = ok.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/ok.iconset; sourceTree = ""; }; + C2B573EE1B1DAD6400303B36 /* sync_swm.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = sync_swm.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/sync_swm.iconset; sourceTree = ""; }; + C2B573EF1B1DAD6400303B36 /* sync.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = sync.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/sync.iconset; sourceTree = ""; }; + C2B573F01B1DAD6400303B36 /* warning_swm.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = warning_swm.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/warning_swm.iconset; sourceTree = ""; }; + C2B573F11B1DAD6400303B36 /* warning.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = warning.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/warning.iconset; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C2B573AE1B1CD91E00303B36 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C2B573D41B1CD9CE00303B36 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C2B573941B1CD88000303B36 = { + isa = PBXGroup; + children = ( + C2B573E61B1DA1FB00303B36 /* common */, + C2B573B31B1CD91E00303B36 /* desktopclient */, + C2B573D81B1CD9CE00303B36 /* FinderSyncExt */, + C2B573B21B1CD91E00303B36 /* Products */, + ); + sourceTree = ""; + }; + C2B573B21B1CD91E00303B36 /* Products */ = { + isa = PBXGroup; + children = ( + C2B573B11B1CD91E00303B36 /* desktopclient.app */, + C2B573D71B1CD9CE00303B36 /* FinderSyncExt.appex */, + ); + name = Products; + sourceTree = ""; + }; + C2B573B31B1CD91E00303B36 /* desktopclient */ = { + isa = PBXGroup; + children = ( + C2B573B41B1CD91E00303B36 /* Supporting Files */, + ); + path = desktopclient; + sourceTree = ""; + }; + C2B573B41B1CD91E00303B36 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C2B573B51B1CD91E00303B36 /* Info.plist */, + C2B573B91B1CD91E00303B36 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C2B573D81B1CD9CE00303B36 /* FinderSyncExt */ = { + isa = PBXGroup; + children = ( + C2B573DC1B1CD9CE00303B36 /* FinderSync.h */, + C2B573DD1B1CD9CE00303B36 /* FinderSync.m */, + C2B573D91B1CD9CE00303B36 /* Supporting Files */, + ); + path = FinderSyncExt; + sourceTree = ""; + }; + C2B573D91B1CD9CE00303B36 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C2B573EA1B1DAD6400303B36 /* error_swm.iconset */, + C2B573EB1B1DAD6400303B36 /* error.iconset */, + C2B573EC1B1DAD6400303B36 /* ok_swm.iconset */, + C2B573ED1B1DAD6400303B36 /* ok.iconset */, + C2B573EE1B1DAD6400303B36 /* sync_swm.iconset */, + C2B573EF1B1DAD6400303B36 /* sync.iconset */, + C2B573F01B1DAD6400303B36 /* warning_swm.iconset */, + C2B573F11B1DAD6400303B36 /* warning.iconset */, + C2B573DA1B1CD9CE00303B36 /* Info.plist */, + C2B573DB1B1CD9CE00303B36 /* FinderSyncExt.entitlements */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C2B573E61B1DA1FB00303B36 /* common */ = { + isa = PBXGroup; + children = ( + C2B573E71B1DA1FB00303B36 /* SyncClientProxy.h */, + C2B573E81B1DA1FB00303B36 /* SyncClientProxy.m */, + ); + name = common; + path = ../common; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C2B573B01B1CD91E00303B36 /* desktopclient */ = { + isa = PBXNativeTarget; + buildConfigurationList = C2B573CC1B1CD91E00303B36 /* Build configuration list for PBXNativeTarget "desktopclient" */; + buildPhases = ( + C2B573AD1B1CD91E00303B36 /* Sources */, + C2B573AE1B1CD91E00303B36 /* Frameworks */, + C2B573AF1B1CD91E00303B36 /* Resources */, + C2B573E11B1CD9CE00303B36 /* Embed App Extensions */, + ); + buildRules = ( + ); + dependencies = ( + C2B573E01B1CD9CE00303B36 /* PBXTargetDependency */, + ); + name = desktopclient; + productName = desktopclient; + productReference = C2B573B11B1CD91E00303B36 /* desktopclient.app */; + productType = "com.apple.product-type.application"; + }; + C2B573D61B1CD9CE00303B36 /* FinderSyncExt */ = { + isa = PBXNativeTarget; + buildConfigurationList = C2B573E31B1CD9CE00303B36 /* Build configuration list for PBXNativeTarget "FinderSyncExt" */; + buildPhases = ( + C2B573D31B1CD9CE00303B36 /* Sources */, + C2B573D41B1CD9CE00303B36 /* Frameworks */, + C2B573D51B1CD9CE00303B36 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FinderSyncExt; + productName = FinderSyncExt; + productReference = C2B573D71B1CD9CE00303B36 /* FinderSyncExt.appex */; + productType = "com.apple.product-type.app-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C2B573951B1CD88000303B36 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0630; + TargetAttributes = { + C2B573B01B1CD91E00303B36 = { + CreatedOnToolsVersion = 6.3.1; + DevelopmentTeam = 9B5WD74GWJ; + }; + C2B573D61B1CD9CE00303B36 = { + CreatedOnToolsVersion = 6.3.1; + DevelopmentTeam = 9B5WD74GWJ; + SystemCapabilities = { + com.apple.ApplicationGroups.Mac = { + enabled = 1; + }; + }; + }; + }; + }; + buildConfigurationList = C2B573981B1CD88000303B36 /* Build configuration list for PBXProject "OwnCloudFinderSync" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = C2B573941B1CD88000303B36; + productRefGroup = C2B573B21B1CD91E00303B36 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C2B573B01B1CD91E00303B36 /* desktopclient */, + C2B573D61B1CD9CE00303B36 /* FinderSyncExt */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C2B573AF1B1CD91E00303B36 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C2B573D21B1CD94B00303B36 /* main.m in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C2B573D51B1CD9CE00303B36 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C2B573F91B1DAD6400303B36 /* warning.iconset in Resources */, + C2B573F31B1DAD6400303B36 /* error.iconset in Resources */, + C2B573F81B1DAD6400303B36 /* warning_swm.iconset in Resources */, + C2B573F21B1DAD6400303B36 /* error_swm.iconset in Resources */, + C2B573F71B1DAD6400303B36 /* sync.iconset in Resources */, + C2B573F41B1DAD6400303B36 /* ok_swm.iconset in Resources */, + C2B573F51B1DAD6400303B36 /* ok.iconset in Resources */, + C2B573F61B1DAD6400303B36 /* sync_swm.iconset in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C2B573AD1B1CD91E00303B36 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C2B573BA1B1CD91E00303B36 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C2B573D31B1CD9CE00303B36 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C2B573E91B1DA1FB00303B36 /* SyncClientProxy.m in Sources */, + C2B573DE1B1CD9CE00303B36 /* FinderSync.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + C2B573E01B1CD9CE00303B36 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C2B573D61B1CD9CE00303B36 /* FinderSyncExt */; + targetProxy = C2B573DF1B1CD9CE00303B36 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + C2B573991B1CD88000303B36 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Debug; + }; + C2B5739A1B1CD88000303B36 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Release; + }; + C2B573CD1B1CD91E00303B36 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = desktopclient/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + SDKROOT = macosx; + }; + name = Debug; + }; + C2B573CE1B1CD91E00303B36 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = desktopclient/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + SDKROOT = macosx; + }; + name = Release; + }; + C2B573E41B1CD9CE00303B36 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = FinderSyncExt/FinderSyncExt.entitlements; + CODE_SIGN_IDENTITY = "Developer ID Application"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = FinderSyncExt/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + OC_APPLICATION_NAME = ownCloud; + OC_APPLICATION_REV_DOMAIN = com.owncloud.desktopclient; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + C2B573E51B1CD9CE00303B36 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = FinderSyncExt/FinderSyncExt.entitlements; + CODE_SIGN_IDENTITY = "Developer ID Application"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = FinderSyncExt/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + OC_APPLICATION_NAME = ownCloud; + OC_APPLICATION_REV_DOMAIN = com.owncloud.desktopclient; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C2B573981B1CD88000303B36 /* Build configuration list for PBXProject "OwnCloudFinderSync" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C2B573991B1CD88000303B36 /* Debug */, + C2B5739A1B1CD88000303B36 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C2B573CC1B1CD91E00303B36 /* Build configuration list for PBXNativeTarget "desktopclient" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C2B573CD1B1CD91E00303B36 /* Debug */, + C2B573CE1B1CD91E00303B36 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C2B573E31B1CD9CE00303B36 /* Build configuration list for PBXNativeTarget "FinderSyncExt" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C2B573E41B1CD9CE00303B36 /* Debug */, + C2B573E51B1CD9CE00303B36 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = C2B573951B1CD88000303B36 /* Project object */; +} diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/xcshareddata/xcschemes/FinderSyncExt.xcscheme b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/xcshareddata/xcschemes/FinderSyncExt.xcscheme new file mode 100644 index 000000000..785a0da3a --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/xcshareddata/xcschemes/FinderSyncExt.xcscheme @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/desktopclient/Info.plist b/shell_integration/MacOSX/OwnCloudFinderSync/desktopclient/Info.plist new file mode 100644 index 000000000..bbaa296f4 --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/desktopclient/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + com.owncloud.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSPrincipalClass + NSApplication + + diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/desktopclient/main.m b/shell_integration/MacOSX/OwnCloudFinderSync/desktopclient/main.m new file mode 100644 index 000000000..6f580d288 --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/desktopclient/main.m @@ -0,0 +1,16 @@ +// +// main.m +// desktopclient +// +// Created by Jocelyn Turcotte on 01/06/15. +// +// + +// This is fake application bundle with the same bundle ID as the real desktop client. +// Xcode needs a wrapping application to allow the extension to be debugged. + +#import + +int main(int argc, const char * argv[]) { + return NSApplicationMain(argc, argv); +} diff --git a/shell_integration/icons/nopadding/error.iconset/icon_32x32.png b/shell_integration/icons/nopadding/error.iconset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..9541677c5fadd15554d9f908cd35d642853d5885 GIT binary patch literal 1816 zcmaJ?c~BE~7)?w##x8<);!)N>JwS3oAZakcgeXc7kt!Y|xfTeJY?>@4C=ybRmLsCY z14a=>Egr3v;xS?oqVYrpo$;zwsZeCl3IT^AC9vHf(Ed@nvpf6yzIpF`=g#IwN3HOf zJZCZvhx3S7DT=|~p!0Ee#o_S8ovY-SK}N#kkachZVvy+|T$mD;LtunjmI%c_GG+Rg zw;>)5NAOj}#vyT%NUj1_Q)NyJ)u`5DY#ffqH)>^y6bJ$3P@+l`O6mT&fdZ{KE28Y9OYA~4r7$QKQu0dqR0FB;fOhE+c z6*`p`QNbF}sVI}fX-Fsq%k*OjYVEkJMnBpntYI{xOiN=>>CTeIfD+07L)GeWv>u6p z{>k@0h4r!NT8I_{>ESe;0vlX{k292(E7UVwOC{kq*HB!lwuvM21knKswU1wG@;%oS2#tciP#4DVkWF(nzLMw@H|$Li z;MR7zau-|eRSn?k&4)-kKD#yhc)jCA;j6+IH(Jpaboh?-3}m%;Jcc6u*~*-!!yI=s z{&eGjH*tMuL3U@_jTqEG&L3XXbCWI7xbNZZl7 z$4x-9dzEN6{`0RC+ZEitNk5| zFrCG+@ju18cD1ySCN*82M)8|tqh4Mw9qcPNTS`jIW&14Vw}Xj!4*xUd4MT}WPcP|= z18#)FUMuF)-(;#L7#uYT{dV7V*x{bW*eF6S?o!qV9@Q(iWD~hD{0~LI-Z|i&VxlL6phfFTRJ)-dIGZJ=3+)BKkT7Jczzo#s0 z_3q6(e%k*VfknOHekw;$?|W11y(F{28;nPX?%2Q1wOskZv>?0a@?2ZT@nY*2PdgSB zlcpr*Fddf+<>s>TY%`fOrsnOon zDb%X-jul({ro;`tPGp@Z$M?=WQ7h#_F4^+vK-&~kN?uTJB=8ovP9~LYByG8H$aVd9 z)`Z&)4Zu?Cl=hn0ErTcayAPC-jwLHY40H4CNkbn7y`Rs71ib+^)KvMBNO1G_w|(E- zQh$NxXw|oMpoCaGj!Ey37oD3eEsSoLKWc{(TG2if?RRTIpL2S8p6n-7piM643DRWz Y!E4!Vr~OJ!Isa7=;ZdR=mrK9;8>}nS;Q#;t literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/error_swm.iconset/icon_32x32.png b/shell_integration/icons/nopadding/error_swm.iconset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..4c916621327fd4aa818849469859b0704f646598 GIT binary patch literal 2541 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 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/ok.iconset/icon_32x32.png b/shell_integration/icons/nopadding/ok.iconset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..5565e57c990b5f2ec26f56299652c601abaddaa3 GIT binary patch literal 1825 zcmaJ?c~BE~7~RM@uz0jgfugb|j;JF^HVXkVLg;y2=+H|D zv5HTBm6S}90SR#f21PNE2qhbaAug8*M@DhE@B)a#hB+)Yb#o)&NM00|$BBksUvw&( zL8azpiNvpCQ5K(GKoEK!i&a!q#4JLXxFMef$HvCGH8`9I3K3ykrz22PgwDvAP!M58 zr9q=7G`J3OE20W~Ey1T#nZ7DPtA8V_Grn#U)i9O`)w5tG+g;KGP$Kz%s8;(1Z6va= zxB333uu-~BkFl~aBfi$4qz0#ExI^iALIZ{pxIv2Jt0%gcQGgS;u>jXY!fY-ySAr@v zI=5rqONK!PIC}JW&igG7&)%lVSu23@0H1G0csLj*W!5B3LLA zMo(}>xN@x)(-9L~)myIcom}@aX!TTN5oXX7Vk)r#*FrCg=4t*T7eQe>U-IeA`(bV{R$^Z z#(Qw&iUf~QFTcWm(#IDP|JW-pg%#Y+vgeaLr&>ciH@0bJbCUayImyA@X3$KM7j@@I zGYH=3*$ej6G=Tt3^lj*aMxgzRz3Q~g+*3oTq>Jp<-+T5~2?*su_bOKSk?q&2Nb{5N zi)2N_ViInOi5pBAX*H7}1C!UY2KsE^!=nN6S>kC72BW-rhH_`>fvag; zb&l1XVA6G0W81@B8|KWP$~c|u%6IflPOS#3cziG@ce=dUiG{9V%27;x+ZEo)zAzlT zBkXKWZE2;%Qu^7(s?xbZmb3DVo|c3u?w0Xe4bEjleT%HyJ+tW@7JKhqGIrdUIGQl};(oX{ zO|a1VV%Ny8zQuwL3v8?Y-2XD^9A>ol*G7Z8<4aKX(cAsvEB=bHrkjmjzxRcSt*teA z!P|TmDs8#PJK8$W^+J1GeHD$gurODw>pnTSKc{j9m{!vR`m-WA_7j%HUX3?S=8aVO zl{f!(f+Rn6T$0b4d0FrL!_WHzG1oh%aE|pt=E!~<$5LM{n5(KgzdS=`*-GX++U*Ah zGTW{rU?^=;&D|k++xvG9dx&SS2Y~Os-e`*$LM`=`g2h*Do!J?6KW(ln&-9gDbvnm} z`i?HWPp)7E!%TAqUl-C$`@v<`AOGs_wh}j`tJ9jvE(?giXpZx4@)m|BtLBP`#C15 zv3$P9QXe4e29z|}BiPs8#SX0RQ~t6Rr?rp!bO-lOFGS}055R$g2Glh6=sl0q!3Ymak44*k>KjYxg{^WtH!=&uj0h4F~ZSe!g!WtMkd&{Tbec zYMrUWsdV(}wf7E%NK-4)rh}hc#2S{L(k5wx%_rDS*T|+jjzJcfJdrxo1 gBjy&B3C#8fPB!Rf?Mpmwb^o+dlF~)T66Iz803edzOaK4? literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/ok_swm.iconset/icon_32x32.png b/shell_integration/icons/nopadding/ok_swm.iconset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..f2cc4b9e3ff16d5aa216f69fd7adc69805de3273 GIT binary patch literal 2543 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 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/sync.iconset/icon_32x32.png b/shell_integration/icons/nopadding/sync.iconset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..7066c546bbcb1413c0b781584ef164e803198d69 GIT binary patch literal 1884 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 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/warning.iconset/icon_32x32.png b/shell_integration/icons/nopadding/warning.iconset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..8766b93c012aecc5a71328d7de001c2b11f11c00 GIT binary patch literal 1800 zcmaJ?c~BE~7~PO?DDfH(5_NP9v}%zgn~;M@2r(go66AQL5t6df*fRJA&YW6e}Wz?FPa2kJ6pp+28lgd+$4Uc2{i7 z>SGOjksl-EI~G9D`t~ zV#dd$)XG>;hM+LWV{(IZYzPARd?pmk?pZZWqYLkfa4M}CrKMlrg)B$ zDKq_h4QCy+8Axy8Umld1rF6<3a7~l_YAK$dGctg;h+VjP?uTJlSz)ogxT~WBrvfCo zzu>mI@YIyluJ#|+;H;yuuEdJ#7yX=mti^peJ}|h_Idn!se@F3zlquD9=ilIXJ{2Ez zB?~XS9QG{$7_{kgwrda39M`U0>(u|U$<|ZUjXx;k;gX-K1|@;Jw=ao2QXSr4jU$Yu zfzFP`MB2gNerSuh_KL%K%jsxok{|IE3M-p}0|N!lw&DnCgU6BEp)HX|Nay-C(0gL? z>JIPOQz=zz$z*Tu1upgy?V3yPulF@&Zgqy z72%Ho67yI+HBnMqvcNLbTIMa^(R5(*BOC`(FIB9==q1fEn!2rzE)1!psd&sa+ybg& zr?jBrfV8_W{T_a6ajz>sJ8=+qrf$MB$}4lEC_qxAXR$k{r2j@bHJz3LR2A%)U+UN` zqIDXQzv1534BS=e1b;ly&U-gFgqK}im)9Pe+tqXbG(u}V@vKTArDOosx}_r}k2bX} zjN7=VRFXf+&B+|eBeD3>5wC%-CzlVikp3TrQ zxnmv&^{Ch1NSe;|^(XuJ&34w>`}4C$9>3mL|2$dDvywvJ1Ywn@F1OoGVVIDYWvk4} x$KT`6?>LXId%N&mL&mLr$Ipl5z&5W0DscKZdafwBa*q4o6crI8|8Z4P(cdE+%~t>b literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/warning_swm.iconset/icon_32x32.png b/shell_integration/icons/nopadding/warning_swm.iconset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..e25e383b6ae08c7172e14305d844e9a5141e82a5 GIT binary patch literal 2498 zcmaJ@dpwi-A0L~!r0}C$3TudHmuH)qv6js39K(;L)p2B(XNX;zEkkbUC`vlIE4PCq z-3~g1SaYd3Nu~UXUh0I9gi2BtJJU($_s6f_^Ln1w^Zk5YpZEK7d;jsx3JUO9s%N1G zfk2k}`O<>bf5gJ41BXB~V9DRH)rPsmizx{a@+9#rF$nSC3fUmSkI#w(gFzNIVaFe! zI|Kr?jb<<K?w^XyR71#lRw`X!!7)HhtVE{CCp{2z@I{p8L^7&t* z<0Qf0KlT1k;y6Zv2*d<~aYCt>qh1`(W+9b`>?sCW5}}wO6vi%2F(^tX5ynLcMF`Ij zBEo^r;zSD;9BV%#=ybB5AWp&(a6msA1*zshM@MtX1b~JmdIKacl7}+@c;P*~0V2u8 zl>iWFfG5qG&%a)wGh9_ z_XW%Sb}cU7#A4LNU=~*UU#nhpsr9pP{3=|v@m2buK&^MNTG(D4IVcDOPVl37Fl3L} zpHwO;6c@qX0PWEVu6e5}s*_lkDXAMEK%M~JQNMB~jjY1!;I(&aK{Rz*^>=$mY?r6thJI1qJ|?%Ou4)#| zefrH5qCvy1a(I9JnM<11X$SLW)(z3@tNI83s>xoXnxErDu-Wi6`HDldj`rKHKRw(E zUtMSgPw7|bQx$l;gz#jWyL7acBtm%mczJP;F;Lp(tt~ArRot$PnRl(JI1;G=8#R`X z#VV?~hQ~~JACxmoGV+Egnfu#J%waT{Imz_|0&Qq)YRZG%>`}%G)3fKXjY(6P9t{7} z9!wgHONBbQUH|F)d3XKDWzX^Z{NmEZ%Ue^6JDSmug!JQk_fAce+FNgHoNJ#|&RD}k ziJ_TPMJhfGc{0oVM0UUs*6fEq_J_Fjx#we?!(-69%Kend;BDf~ZjoXO4p$dOmE zHB0MDI3InNmiGm}jHD0X%QOE!lg1c#Eg*{!X-ajb0EX1^=X zt&Ny6LK(t{M&CbDCM6cmBo~g%%`{V+{>-eD%RA+D>&6|rTk?0f9?V$bNTEl|NWu-Q zUbjv~zv56xj%EBnMYcV~KUw}G=kSBEw251LgLJf&NoE#S9(p|6$#aMmBhRMm3llHu zHttx(Ylc+6xPRUDHs-?lDR=Enn~WOko8LcN!`;HWZ8aCu9{(H{n|^Q!@36NypmpL6 zIF8fpuhj`SxU7;8*wy<4I=Lqo6_XZp*P(=b>C7&#DNQC`3f|csV+U=o+0uW$@~0D# zZGeWIvF@&$>=)K%w7eDiea_c(ud^GKT2j5s1UC&+YTGRLtxXt3di*>LI4cKc$Dw|CD_(}7p1zLRBj5dR!`J009>&(W z-VyV!o5D}i#SGg)cwPQnVaJB;BR*;G&tb|cvDvDL;fbBKm7SHBY+3nHoVgu2zEJDL z1HmPa!n3VxY>Z57ODl93x+9t;@P-JL)p zh)3qXb2u^k)5=g?om5tZA5=hnZ_NsF2^r!=%}uKXw|`RT^7>XcjiOqqig7*`c7XD@yXB3+?H?$dY(8mYeBDszo zIL0sUxVWs0oSa>4SKuqPIzK(cJ4(fwnnBd#Z?Z$}*)Es;HJAI^9VYBhkn9z%X z-M4y!LTQkE9zS;~r~009I^t7We*f1?W_6=-<@d(C-KN zZpN-DMY*eVt6XY}e-91Ou&d9xaAT!tmBBA(GcDobBZ;WwqKd0YXLRMZKJN^@G?o+% z#j2h?w!0ggGLTzaER)8^Hz;Rk69Xl60V{1Q^^$6?+;XFOCL3+dzt=v7AxvNX`RYq3 w8zh$ULU-S)9j$dc95Q*dtr`}(|6B^>>H6ZctzJpWg Date: Tue, 16 Jun 2015 14:25:53 +0200 Subject: [PATCH 03/14] shell_i: Use proper non-padded icons #2340 --- .../FinderSyncExt/FinderSync.m | 26 +++++--- .../project.pbxproj | 12 ---- shell_integration/icons/128x128/.DS_Store | Bin 6148 -> 0 bytes shell_integration/icons/32x32/.DS_Store | Bin 6148 -> 0 bytes shell_integration/icons/512x512/.DS_Store | Bin 6148 -> 0 bytes .../icons/nopadding/SVG/attention.svg | 32 +++++++++ .../icons/nopadding/SVG/error.svg | 36 ++++++++++ shell_integration/icons/nopadding/SVG/ok.svg | 23 +++++++ .../icons/nopadding/SVG/shared.svg | 62 ++++++++++++++++++ .../icons/nopadding/SVG/sync.svg | 55 ++++++++++++++++ .../nopadding/error.iconset/icon_128x128.png | Bin 0 -> 5541 bytes .../error.iconset/icon_128x128@2x.png | Bin 0 -> 11767 bytes .../nopadding/error.iconset/icon_16x16.png | Bin 0 -> 603 bytes .../nopadding/error.iconset/icon_16x16@2x.png | Bin 0 -> 1257 bytes .../nopadding/error.iconset/icon_256x256.png | Bin 0 -> 11689 bytes .../error.iconset/icon_256x256@2x.png | Bin 0 -> 25593 bytes .../nopadding/error.iconset/icon_32x32.png | Bin 1816 -> 1255 bytes .../nopadding/error.iconset/icon_32x32@2x.png | Bin 0 -> 2506 bytes .../error_swm.iconset/icon_32x32.png | Bin 2541 -> 0 bytes .../nopadding/ok.iconset/icon_128x128.png | Bin 0 -> 6666 bytes .../nopadding/ok.iconset/icon_128x128@2x.png | Bin 0 -> 14441 bytes .../icons/nopadding/ok.iconset/icon_16x16.png | Bin 0 -> 615 bytes .../nopadding/ok.iconset/icon_16x16@2x.png | Bin 0 -> 1243 bytes .../nopadding/ok.iconset/icon_256x256.png | Bin 0 -> 14634 bytes .../nopadding/ok.iconset/icon_256x256@2x.png | Bin 0 -> 31427 bytes .../icons/nopadding/ok.iconset/icon_32x32.png | Bin 1825 -> 1243 bytes .../nopadding/ok.iconset/icon_32x32@2x.png | Bin 0 -> 2722 bytes .../nopadding/ok_swm.iconset/icon_128x128.png | Bin 0 -> 7492 bytes .../ok_swm.iconset/icon_128x128@2x.png | Bin 0 -> 16248 bytes .../nopadding/ok_swm.iconset/icon_16x16.png | Bin 0 -> 647 bytes .../ok_swm.iconset/icon_16x16@2x.png | Bin 0 -> 1452 bytes .../nopadding/ok_swm.iconset/icon_256x256.png | Bin 0 -> 16286 bytes .../ok_swm.iconset/icon_256x256@2x.png | Bin 0 -> 35262 bytes .../nopadding/ok_swm.iconset/icon_32x32.png | Bin 2543 -> 1462 bytes .../ok_swm.iconset/icon_32x32@2x.png | Bin 0 -> 3267 bytes .../nopadding/sync.iconset/icon_128x128.png | Bin 0 -> 7701 bytes .../sync.iconset/icon_128x128@2x.png | Bin 0 -> 16662 bytes .../nopadding/sync.iconset/icon_16x16.png | Bin 0 -> 694 bytes .../nopadding/sync.iconset/icon_16x16@2x.png | Bin 0 -> 1522 bytes .../nopadding/sync.iconset/icon_256x256.png | Bin 0 -> 16662 bytes .../sync.iconset/icon_256x256@2x.png | Bin 0 -> 35628 bytes .../nopadding/sync.iconset/icon_32x32.png | Bin 1884 -> 1563 bytes .../nopadding/sync.iconset/icon_32x32@2x.png | Bin 0 -> 3459 bytes .../nopadding/sync_swm.iconset/icon_32x32.png | Bin 2592 -> 0 bytes .../warning.iconset/icon_128x128.png | Bin 0 -> 6039 bytes .../warning.iconset/icon_128x128@2x.png | Bin 0 -> 12859 bytes .../nopadding/warning.iconset/icon_16x16.png | Bin 0 -> 523 bytes .../warning.iconset/icon_16x16@2x.png | Bin 0 -> 1082 bytes .../warning.iconset/icon_256x256.png | Bin 0 -> 12859 bytes .../warning.iconset/icon_256x256@2x.png | Bin 0 -> 27637 bytes .../nopadding/warning.iconset/icon_32x32.png | Bin 1800 -> 1082 bytes .../warning.iconset/icon_32x32@2x.png | Bin 0 -> 2582 bytes .../warning_swm.iconset/icon_32x32.png | Bin 2498 -> 0 bytes 53 files changed, 224 insertions(+), 22 deletions(-) delete mode 100644 shell_integration/icons/128x128/.DS_Store delete mode 100644 shell_integration/icons/32x32/.DS_Store delete mode 100644 shell_integration/icons/512x512/.DS_Store create mode 100755 shell_integration/icons/nopadding/SVG/attention.svg create mode 100755 shell_integration/icons/nopadding/SVG/error.svg create mode 100755 shell_integration/icons/nopadding/SVG/ok.svg create mode 100755 shell_integration/icons/nopadding/SVG/shared.svg create mode 100755 shell_integration/icons/nopadding/SVG/sync.svg create mode 100755 shell_integration/icons/nopadding/error.iconset/icon_128x128.png create mode 100755 shell_integration/icons/nopadding/error.iconset/icon_128x128@2x.png create mode 100755 shell_integration/icons/nopadding/error.iconset/icon_16x16.png create mode 100755 shell_integration/icons/nopadding/error.iconset/icon_16x16@2x.png create mode 100755 shell_integration/icons/nopadding/error.iconset/icon_256x256.png create mode 100755 shell_integration/icons/nopadding/error.iconset/icon_256x256@2x.png mode change 100644 => 100755 shell_integration/icons/nopadding/error.iconset/icon_32x32.png create mode 100755 shell_integration/icons/nopadding/error.iconset/icon_32x32@2x.png delete mode 100644 shell_integration/icons/nopadding/error_swm.iconset/icon_32x32.png create mode 100755 shell_integration/icons/nopadding/ok.iconset/icon_128x128.png create mode 100755 shell_integration/icons/nopadding/ok.iconset/icon_128x128@2x.png create mode 100755 shell_integration/icons/nopadding/ok.iconset/icon_16x16.png create mode 100755 shell_integration/icons/nopadding/ok.iconset/icon_16x16@2x.png create mode 100755 shell_integration/icons/nopadding/ok.iconset/icon_256x256.png create mode 100755 shell_integration/icons/nopadding/ok.iconset/icon_256x256@2x.png mode change 100644 => 100755 shell_integration/icons/nopadding/ok.iconset/icon_32x32.png create mode 100755 shell_integration/icons/nopadding/ok.iconset/icon_32x32@2x.png create mode 100755 shell_integration/icons/nopadding/ok_swm.iconset/icon_128x128.png create mode 100755 shell_integration/icons/nopadding/ok_swm.iconset/icon_128x128@2x.png create mode 100755 shell_integration/icons/nopadding/ok_swm.iconset/icon_16x16.png create mode 100755 shell_integration/icons/nopadding/ok_swm.iconset/icon_16x16@2x.png create mode 100755 shell_integration/icons/nopadding/ok_swm.iconset/icon_256x256.png create mode 100755 shell_integration/icons/nopadding/ok_swm.iconset/icon_256x256@2x.png mode change 100644 => 100755 shell_integration/icons/nopadding/ok_swm.iconset/icon_32x32.png create mode 100755 shell_integration/icons/nopadding/ok_swm.iconset/icon_32x32@2x.png create mode 100755 shell_integration/icons/nopadding/sync.iconset/icon_128x128.png create mode 100755 shell_integration/icons/nopadding/sync.iconset/icon_128x128@2x.png create mode 100755 shell_integration/icons/nopadding/sync.iconset/icon_16x16.png create mode 100755 shell_integration/icons/nopadding/sync.iconset/icon_16x16@2x.png create mode 100755 shell_integration/icons/nopadding/sync.iconset/icon_256x256.png create mode 100755 shell_integration/icons/nopadding/sync.iconset/icon_256x256@2x.png mode change 100644 => 100755 shell_integration/icons/nopadding/sync.iconset/icon_32x32.png create mode 100755 shell_integration/icons/nopadding/sync.iconset/icon_32x32@2x.png delete mode 100644 shell_integration/icons/nopadding/sync_swm.iconset/icon_32x32.png create mode 100755 shell_integration/icons/nopadding/warning.iconset/icon_128x128.png create mode 100755 shell_integration/icons/nopadding/warning.iconset/icon_128x128@2x.png create mode 100755 shell_integration/icons/nopadding/warning.iconset/icon_16x16.png create mode 100755 shell_integration/icons/nopadding/warning.iconset/icon_16x16@2x.png create mode 100755 shell_integration/icons/nopadding/warning.iconset/icon_256x256.png create mode 100755 shell_integration/icons/nopadding/warning.iconset/icon_256x256@2x.png mode change 100644 => 100755 shell_integration/icons/nopadding/warning.iconset/icon_32x32.png create mode 100755 shell_integration/icons/nopadding/warning.iconset/icon_32x32@2x.png delete mode 100644 shell_integration/icons/nopadding/warning_swm.iconset/icon_32x32.png diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m index 8c0c2c0a8..fc67e3e5a 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m @@ -27,16 +27,22 @@ // This was added to the bundle's Info.plist to get it from the build system NSString *teamIdentifierPrefix = [extBundle objectForInfoDictionaryKey:@"TeamIdentifierPrefix"]; - [syncController setBadgeImage:[extBundle imageForResource:@"ok.icns"] label:nil forBadgeIdentifier:@"OK"]; - [syncController setBadgeImage:[extBundle imageForResource:@"sync.icns"] label:nil forBadgeIdentifier:@"SYNC"]; - [syncController setBadgeImage:[extBundle imageForResource:@"sync.icns"] label:nil forBadgeIdentifier:@"NEW"]; - [syncController setBadgeImage:[extBundle imageForResource:@"warning.icns"] label:nil forBadgeIdentifier:@"IGNORE"]; - [syncController setBadgeImage:[extBundle imageForResource:@"error.icns"] label:nil forBadgeIdentifier:@"ERROR"]; - [syncController setBadgeImage:[extBundle imageForResource:@"ok_swm.icns"] label:nil forBadgeIdentifier:@"OK+SWM"]; - [syncController setBadgeImage:[extBundle imageForResource:@"sync_swm.icns"] label:nil forBadgeIdentifier:@"SYNC+SWM"]; - [syncController setBadgeImage:[extBundle imageForResource:@"sync_swm.icns"] label:nil forBadgeIdentifier:@"NEW+SWM"]; - [syncController setBadgeImage:[extBundle imageForResource:@"warning_swm.icns"] label:nil forBadgeIdentifier:@"IGNORE+SWM"]; - [syncController setBadgeImage:[extBundle imageForResource:@"error_swm.icns"] label:nil forBadgeIdentifier:@"ERROR+SWM"]; + NSImage *ok = [extBundle imageForResource:@"ok.icns"]; + NSImage *ok_swm = [extBundle imageForResource:@"ok_swm.icns"]; + NSImage *sync = [extBundle imageForResource:@"sync.icns"]; + NSImage *warning = [extBundle imageForResource:@"warning.icns"]; + NSImage *error = [extBundle imageForResource:@"error.icns"]; + + [syncController setBadgeImage:ok label:nil forBadgeIdentifier:@"OK"]; + [syncController setBadgeImage:sync label:nil forBadgeIdentifier:@"SYNC"]; + [syncController setBadgeImage:sync label:nil forBadgeIdentifier:@"NEW"]; + [syncController setBadgeImage:warning label:nil forBadgeIdentifier:@"IGNORE"]; + [syncController setBadgeImage:error label:nil forBadgeIdentifier:@"ERROR"]; + [syncController setBadgeImage:ok_swm label:nil forBadgeIdentifier:@"OK+SWM"]; + [syncController setBadgeImage:sync label:nil forBadgeIdentifier:@"SYNC+SWM"]; + [syncController setBadgeImage:sync label:nil forBadgeIdentifier:@"NEW+SWM"]; + [syncController setBadgeImage:warning label:nil forBadgeIdentifier:@"IGNORE+SWM"]; + [syncController setBadgeImage:error label:nil forBadgeIdentifier:@"ERROR+SWM"]; // The Mach post name needs to be prefixed with the code signing Team ID // https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24 diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj index 61aead9a2..3bed4ca08 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj @@ -12,13 +12,10 @@ C2B573DE1B1CD9CE00303B36 /* FinderSync.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573DD1B1CD9CE00303B36 /* FinderSync.m */; }; C2B573E21B1CD9CE00303B36 /* FinderSyncExt.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C2B573D71B1CD9CE00303B36 /* FinderSyncExt.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; C2B573E91B1DA1FB00303B36 /* SyncClientProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573E81B1DA1FB00303B36 /* SyncClientProxy.m */; }; - C2B573F21B1DAD6400303B36 /* error_swm.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EA1B1DAD6400303B36 /* error_swm.iconset */; }; C2B573F31B1DAD6400303B36 /* error.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EB1B1DAD6400303B36 /* error.iconset */; }; C2B573F41B1DAD6400303B36 /* ok_swm.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EC1B1DAD6400303B36 /* ok_swm.iconset */; }; C2B573F51B1DAD6400303B36 /* ok.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573ED1B1DAD6400303B36 /* ok.iconset */; }; - C2B573F61B1DAD6400303B36 /* sync_swm.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EE1B1DAD6400303B36 /* sync_swm.iconset */; }; C2B573F71B1DAD6400303B36 /* sync.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EF1B1DAD6400303B36 /* sync.iconset */; }; - C2B573F81B1DAD6400303B36 /* warning_swm.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573F01B1DAD6400303B36 /* warning_swm.iconset */; }; C2B573F91B1DAD6400303B36 /* warning.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573F11B1DAD6400303B36 /* warning.iconset */; }; /* End PBXBuildFile section */ @@ -57,13 +54,10 @@ C2B573DD1B1CD9CE00303B36 /* FinderSync.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FinderSync.m; sourceTree = ""; }; C2B573E71B1DA1FB00303B36 /* SyncClientProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SyncClientProxy.h; sourceTree = ""; }; C2B573E81B1DA1FB00303B36 /* SyncClientProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncClientProxy.m; sourceTree = ""; }; - C2B573EA1B1DAD6400303B36 /* error_swm.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = error_swm.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/error_swm.iconset; sourceTree = ""; }; C2B573EB1B1DAD6400303B36 /* error.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = error.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/error.iconset; sourceTree = ""; }; C2B573EC1B1DAD6400303B36 /* ok_swm.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = ok_swm.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/ok_swm.iconset; sourceTree = ""; }; C2B573ED1B1DAD6400303B36 /* ok.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = ok.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/ok.iconset; sourceTree = ""; }; - C2B573EE1B1DAD6400303B36 /* sync_swm.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = sync_swm.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/sync_swm.iconset; sourceTree = ""; }; C2B573EF1B1DAD6400303B36 /* sync.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = sync.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/sync.iconset; sourceTree = ""; }; - C2B573F01B1DAD6400303B36 /* warning_swm.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = warning_swm.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/warning_swm.iconset; sourceTree = ""; }; C2B573F11B1DAD6400303B36 /* warning.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = warning.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/warning.iconset; sourceTree = ""; }; /* End PBXFileReference section */ @@ -134,13 +128,10 @@ C2B573D91B1CD9CE00303B36 /* Supporting Files */ = { isa = PBXGroup; children = ( - C2B573EA1B1DAD6400303B36 /* error_swm.iconset */, C2B573EB1B1DAD6400303B36 /* error.iconset */, C2B573EC1B1DAD6400303B36 /* ok_swm.iconset */, C2B573ED1B1DAD6400303B36 /* ok.iconset */, - C2B573EE1B1DAD6400303B36 /* sync_swm.iconset */, C2B573EF1B1DAD6400303B36 /* sync.iconset */, - C2B573F01B1DAD6400303B36 /* warning_swm.iconset */, C2B573F11B1DAD6400303B36 /* warning.iconset */, C2B573DA1B1CD9CE00303B36 /* Info.plist */, C2B573DB1B1CD9CE00303B36 /* FinderSyncExt.entitlements */, @@ -254,12 +245,9 @@ files = ( C2B573F91B1DAD6400303B36 /* warning.iconset in Resources */, C2B573F31B1DAD6400303B36 /* error.iconset in Resources */, - C2B573F81B1DAD6400303B36 /* warning_swm.iconset in Resources */, - C2B573F21B1DAD6400303B36 /* error_swm.iconset in Resources */, C2B573F71B1DAD6400303B36 /* sync.iconset in Resources */, C2B573F41B1DAD6400303B36 /* ok_swm.iconset in Resources */, C2B573F51B1DAD6400303B36 /* ok.iconset in Resources */, - C2B573F61B1DAD6400303B36 /* sync_swm.iconset in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/shell_integration/icons/128x128/.DS_Store b/shell_integration/icons/128x128/.DS_Store deleted file mode 100644 index 3604397ad2735244524e5fffcb0b8fada4ae4469..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO-sW-5Pe&FC{?kdh{qf~3H9VLlvWTALUZuuM_W;$v{LZkEkDB_p(oK52_y=r(VWmrRoN5187@yfOH5^t-(TY7cX?x~>N2F_Hi3GPJc z#m@jytjOM}u$O)kRkE0isonme0<76$jn#-AT>)3X6?j)b_J@Qnm>T90b?aczBLK0^ zZZ)>$M?pA=VQQF1Df5z>er zT>)2MUV)W(+mia<`}+JpPx3ogz!msY3WRz$>vs4`R$D7CC$%=AU(&^7ULH{?3|5Y{ eA*FbiuEw!M8pPBvkH{XH{|KlI9$bMRRp1SdeTf19 diff --git a/shell_integration/icons/32x32/.DS_Store b/shell_integration/icons/32x32/.DS_Store deleted file mode 100644 index 5fb443d051eb78e9bd487e0c294d80b30b369f2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%Syvg5Is}7XsJk11eaO564W0Er4_`5U>ELuw3RB9RthfM{O)5o7cLIic^r2jdLbDe#84$1OA5VN#prwUnie zbDY&Nb$^FvK(F7}r&8T$NbcIo6t$gJLf^#kO!6G@9dq^`~|9p*rQc zyA|%5|03v@&&0%Tu7E4>&lO-4`sfO{0tUqlmgQ15Boh{lJ(Zg#c8ii=r?pVjmsjgD4c{=%vf#3 cd-PyjPjo^|4YP<`q4|eEkiiF6;71ks0L0LTfB*mh diff --git a/shell_integration/icons/512x512/.DS_Store b/shell_integration/icons/512x512/.DS_Store deleted file mode 100644 index 1f45086b2ecc1d4a37510d82c924746aa1eaeb64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO-sW-5Pe&FC{?kdh{qf~2^GAD(hA~1Xb#@|Xe%m|Rtg@x=dAK&jiCb&5sPlto?bii4%MI6!hUe(wFw&$1`ZgIsN*BE`1 z=ed-piBp^`=7fHUvuT{AR}C+v3~R{n$hRCWUb%K&;%yapORuilJr%Uuz?rHw!JR0* z_!%II71=u#_R>$HN)~f5wc9^bfHhmJu^Q2%E8q&a0`CgQ{*cfGQ^P!>ZXFDI1R&Pg zt;V+eC + + + + + + + + + + + + + + + + + + + + + + diff --git a/shell_integration/icons/nopadding/SVG/error.svg b/shell_integration/icons/nopadding/SVG/error.svg new file mode 100755 index 000000000..8aa5003c5 --- /dev/null +++ b/shell_integration/icons/nopadding/SVG/error.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/shell_integration/icons/nopadding/SVG/ok.svg b/shell_integration/icons/nopadding/SVG/ok.svg new file mode 100755 index 000000000..8e5a24b1c --- /dev/null +++ b/shell_integration/icons/nopadding/SVG/ok.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + diff --git a/shell_integration/icons/nopadding/SVG/shared.svg b/shell_integration/icons/nopadding/SVG/shared.svg new file mode 100755 index 000000000..319303761 --- /dev/null +++ b/shell_integration/icons/nopadding/SVG/shared.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shell_integration/icons/nopadding/SVG/sync.svg b/shell_integration/icons/nopadding/SVG/sync.svg new file mode 100755 index 000000000..bfbaddad1 --- /dev/null +++ b/shell_integration/icons/nopadding/SVG/sync.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shell_integration/icons/nopadding/error.iconset/icon_128x128.png b/shell_integration/icons/nopadding/error.iconset/icon_128x128.png new file mode 100755 index 0000000000000000000000000000000000000000..566682b956cdddce80faf2ea56d93e245472933a GIT binary patch literal 5541 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
  • (B`Q6SS{0CiFo`3Z$ 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 TbVK%F00000NkvXXu0mjftF~>1 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/error.iconset/icon_256x256.png b/shell_integration/icons/nopadding/error.iconset/icon_256x256.png new file mode 100755 index 0000000000000000000000000000000000000000..db345f6f1c91831c9696de0d478652717937d58f GIT binary patch literal 11689 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 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/error.iconset/icon_256x256@2x.png b/shell_integration/icons/nopadding/error.iconset/icon_256x256@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..ab25f43156cd64428c2eaf51013cdc9a5eae591e GIT binary patch literal 25593 zcmX_nbwHC}*!HuH4(aZc25A9lDd`SLQ9xiIDGF?W(yd5|q<|=)Gzgo7qDXglC?KUU zU~J#R?|t9zuWiqB;*RUS&VA0gmtbnFOG(B`1^@u1zMi%@06@X7P=JI8{P_EL{2csX z3A$|?Wa0N9DAYCJKA_?5=XRf4-`CaSzWIGu_pnFb@2dd7y?lLb4a>)~y9K1L*{vG! zCi^>vLzx5=6g;j}%9!a;5`J1nCPocALiTWxc-1%Oe~T;k&q5(nLcICW+6NF#+pbh< zl24L6K6W|OdR;!ZI%@HORo+tSum03HOju2z_Almbv|hja(`n_1TDo;QA9#xZsFMpk zhq|izbopAq=~{!UC)%dYTVotd@i)=p<;}>*D(wQvn^jy%^P^8*6zaAxw50q}yTj(R zVWBUTE3-;u{KA z4kRxQ72_G&yA2--cp5twIRD1OBK`Ih0KfpG$j8%Pd}k4@djq~)5tjS6_oovRImI7G z&uPNb?ld5cG*}#A00jX2BZ<^xx`+w-h*t=J(>k^D>Br;8jlS@?kxf%4mpEL>rA~m- zWx9a1aA-FAtLmKoNulj)_@Xt>$zXwwbs<@RUP(svC0p*_MPUAyJ-w=dwY8_iZ~Bh3 zzH{Xa?K#2IibEh*hHxyA48+I;f5{$+B;B~Z5F%~gjvvvaHN0GG=paG!HbtOCbQl_=QxY75K2FK%DoJWsug^%NsRNjav z)SeJ#t5 z3IMQo{=QWlB5gb1O5}7+I0@_9I?OQ9cBBh2+4I0DoXxyX>iD*HhD19gJfNYWNshHGO>8t?FyqPvLidyGwBJL&B>&eT> za{gNAMRxzb$0Mew(=%ao*Pr7v*^j4>4<;^<8kN?;7N+IAY|-YeJt=8#$r_{NTAVvs zE$^_4t-wLO(7s)2QB+%xlndwF6;iihyhmauYq~U%F6=j;x2v-j)!Jm;>N4m2{#61G z$wBWdKVDhNpmN?g34^=Z#NSF=ghA8@FFQ{Om$rIsz8koUxXx448{8K;LeJbzNXTS6 zxDH3T@Eu6k$b;ZBrj2N=bzdt;sw*l%^< zRGPUgeE$JQ1fX&2QV#CC?UZSWD43J2=s@U?lb^xzw&QFMGK#j0`A4LN1l514cUq3q zQgHOq!qqlHC`Blt%)qe0O4{+tQmbR1Z>Gg!bMm&9Mxn%cr0)!er<%K++Z*CBH=?8G zpT+`6tOMJV=W1t2%KYU(#Mjwl?)NP1#G9!mg)8z$a}8@=v>bjRE`iNIv>?3wVe~jgA5%^fOwLGT~_v4S%uJ^#@3YV%u06&>uy<&z)cG>-TozKeZnu zZRgD(K`gf7x-LZ^4+IgNgeT>&(1!sT=TrnZ)Td$xoj;l=XM|tUl)Ndx$)Jp+JGx> zi!NqEsZgttA5~+G;<9VzqIxdGPO-d-Y`ShEWG+(Je8&;;oiu$x1&_QR15R+ZisIv{ zS#ENEzm!B0slq<`@DK*5#_!%v|FI#b#=pS`=AD&5N4sK|(VHpetgpc4Nc3IldUu?3dzS5FB^)Bq`HVur?ZOFF~VDv&{+ zCg9D&F|bC@F;4KEuPcH3i{ij(g83Lxu$#RkHgf`Tvw6(nAE!Huvo1fu3Nf0OPwLfx zcRz_kU*o)lx_|~4O-hGqhIn51V&YWx2i%|@^Y;j#0nleOd=OZDSB`rGDFe!Zd=G&E zALwvW69yQkVX$Q|wjhJ#)iDG#!Fgixl;1HZUR}M-n39W;A4n)xHT)?Ycx2wmSoD+Q=`9Nc;BIQbWk6my!Ay#GYG zgeNgpv&9vtyOGJB^L@)p?8Vd%N{8feN{l32?aL4-;cPRmVvtS#(OL2ua@KN zF9Ahj8iYQ*;nq?0jUDm11-37^HFewDpjyf2|elgBO1V;bVf5FbA}?T|CI?j_T^mj z3<nAiWdc7!+%y$&a`j zU-uN{A(BO>uE4An)?~HYYQDt!vlIXN)aLoeaxUP&RU0dyz@0B?_b-zL?(vZ{CuuPb z;(2`~oA*3B;G*2B6j$?@Kp-3fu-!HwODp?itpjs+ez9LiBUB2GIWK`DRmDD^6BBhZ z**tG|qe(u80F<|3I-{>PiROFv>V2R(BNtKtoB=O>CY3f9G$#3BN-IGxr=;(I7?9N$ zbS^U6u_hB+4Ux@9-ro*m2AV>?rJG4Hw{&9Pn1N+pGUKksj&F z+6;l1`cOB}avN(>Z2!r*Z+9v)<*Cx+bt(W5Vd>v&po|q$chb&;`Ki}-1B!N-w}`iO zLjn&_uf`Ad6MKc$Pm9+ez@|{(J43!-4OUOd?sf?-Kiv*y2CgTf#2Qf1bGZ*tjI@^k zVD0)SdU;}g@IIfZCODzuZS1}9mfShRdS_9Z7v`NjK}rDHeDq8ur99C3^Z3HNYPTSF zic=hXulhyMMeYd)5xQm-CCBw= zMSkGt6e(du9)=Ip!c$IKfLoB;St=q6O$*okNctGoaSi68aJZC9bs8|&^K(k$l^TFf zwQVqPVQ6Lnxk@{&8<@V0J$*Z?VpZYA^h1;f@$#4um}U~@Q+Yj@=cp+3-B&Ol`E6uP z3aDnpmty?Mo8#?jS5BY2tKid#UE9541+t4{RoYtFWkUsGa(dl(}rCCVvD06ghmgc)pWMO%dq* zTJ|29lL8(jA=J1cy(iBE#+MeNGCKj_lw06g+_%x$ab&{lAwidGPuPgi8LRuJPv2dS zbyowB7sHvbF)xuUL!)cYNRbf#H<*6L&RdV@Pa!}HQvyKocJzUtI0c>Q^>~>%b?kka z!Z(lUSE&I1)A$#Kk7yznztd_YkmU4?)YElN9A{=#46nrl%nS&9O#9al$KSdj2HK_D zo`AZ;jlA!sN*=$ZzsRIBlfYcGu||%+r&uU0RFVs@V1(+6WLLO&e-<3NsBY2?Bq!ndRrNeQS$ntu;I1$9mH%9} z5&9uB5RXWvq6(a|_}2=O>c1Wy8~_X_lQJ8;t-LGWOQ15a=OPdBVn&`YAk-#4U3%p4 z;5s-S9X|MwqDDP9=A=9u|KV&8d9{gq< zZfdC4jL4qL{h>T=L4Kiy1vg_Ur0E`2`cL1!{vPuM51;Dv?H2|*mn#l5!ou|U!3M~i zIj@-?Kg6pa5-|g(Ng$;>M&^(5Vm}QcG>NbD_$dq7PkH;Zk}iEKggGBcCt&wfz!pKD(l5g^WUD&-mr)D*{|5Ndwqoo6 zkpu-YuouBpHGxU1Qt=aGFVdDx^y378Y2=7u>}YGr@39})=v|frC`aK6kI0SE=)$md#> zbBX|KKunFW6mU)B2bS~Q(X&X@%hKeGZ!sJ%!QCwUaf9^kjXk^S9KK+qN6v+<{(mKz zcHmisMg~~p?c^6+R^W{2$(LpJMdz7c0zTzl!g|l7fD;D1s;WwVwatCve|4BR(+!Zh z*)=*X%Ztm7G&M4b>y-165N6;a0g--%Fx`!?`vqRI^gRmT!3(=KxM*YqS^x7YTn>@S zM@F-q_Zi*TGvVD)UN>nQI6zr4i&`wLbz~4{Y&%NA9PTS6Z62MHKC_w~*%} zLks2=Qr|#_rye*}m}74_QT+?*;RY34hOXO53x zN_s@$kk-OS=1vxH!hm%<$)WJ)yG7o#yZ~b|j%uy@Qe;T@eq>DLU(iOr8pW2AKL|xP z4{%YdFezX&0l~!OwBFQO6)5QDn)qB3qXPiyY#D=FUb5A)55YBY5M0V%j}jZ_LtE~? z#l*!x;?)44f#+zKiPE%hvrUHw>e9817pmGjsvyXPNOl&S zZ9PXMD%Dht5pS&exu@D>;^xzj-Jq!(ZhZ<|E}9JoX2kuk%R}_r5g`?Z5Uk-_O#c(a z)hxhB^tHx(qJO{go|7P>NAShrrmyT+C@`Ly#FJN?iD3d`xzg0qo@==dR0quQ2a-_Z z@R!4QSplxeonrlhM@M01znMTNqJ(Tj8kBNN9^ST7MblOp5xVc4=kC=!TQU>CY4Xy> zVy=cGDWN27e7juni#3)+ovswmadyBiT=h?~|JIXLv-u5Bpvity5v^8Dvx#5swaA5Y z;zUV4s3{7QbfZ->3)63olK7{r0RaGimJ$ZC?6{;@$6{|*3iX25-M(t~nPfS?JVAhi z{G()EGaydB_?7oS*{33L_od+Biq(iO!&cHW?DRJ1By}x{im{gi*_H*yKVW`SYNmHn zNYJro86t;F*R$i@7D3yo--{0sN@frJH5*HUCepz_JzjZawf|n3vs7<5O?hUsuOvU9 zRdd52*TG73$gcYLoktk*?SFlgD?EiZc_#gny~)?=79#F)phCS*|6*X)f2?YWPe0Io zXEcy~ZSX#Pd~Xh%l>~NRc6j=G6_uTM%(Ju98Q;fbaJ!no=Puj}vR0egi${Gd<@UZ#FIzwol!>J`I?C7FpI` zAh6s$nAw$8QMCZ=#w1R2dlI#vt>z$P+GJZ+68+HOQ* zp1xy}Jhj(XmT!NQOrImHUwY_)z(vTg9UVuo{XHHr;bgXR;IYs2=&q<#A9iY+yriK) zB2sYoBiH5NbXj-nY@fLmbN{)^#$AdHlBgZknA-9Qa~C*O=V#V-2V`$Y?Jp$fSa*gp zSjWw6FAMzCk^gb(D@2XHT}S#WJW6Nm2ibY`N|P^0#AOqO{fx@1KaDYw=r}&h*LQTH z9GNehoiJf#YMI~7Me-}mXU;uso^wr~x#`PrN&h~&2)D;DAX9jbJNa^xL-rYG2uvrs zn6{=e_mgr`DHINCAf2H!44&c|D3c`U%L!%;yH}fq*&pWTPG|tmOOuUmw_o=dw(@hf zmnA8>P}Q27+ApvNKi@A{YLR9#jna^=;eZ|fZ{@*a`Q9Sedky)c8Pc9L#4{$Vdl&k8 z>wL2;s6qQ)fH5-qYSBS{dy4BlE#fS5d1aB6AExx3%H%A1+6>!Btddr_e);{}X$A}j zjm&c*3l>(^{%K<@p`Bg&&vnn>0K@!Dpjx9rpawp-TvQXW#|Bv*L0RfOG3;&S&v;<& zLChXNp58i26N!Aq&R@m3AoZ2cwTok{vn(Kt_1>durR`f&RKM>#+#Wlj{9 zT6J%ZStcOF*rPEH@jp+KFg8`cbvahv7*%?-7YHf2(vf|g8SgEzlwf$5V`%=npxMi( zH^2aO%xGn>$*=i6-hW^k%uXt;?WkMG_05fPIX=1_Gbf*Vp87S~7i0Ulvp#uQIGb;l z62B1kC02g-6e31#Jjp)trXjCy<)lr}Y2%y(NW*noik^>oVB0r~^&L5c{<_h8YC;GV z6jzl*A%b2Ec>O4r@k-K4D>G8-sudZ{kHh)>F9$C4sE)S`K-2b-#Y~k#-;wDL%M`*=@nglB$Se2 zDY4EDhFZ0fC82LH#FQQ9(MLnVVGxc!@;U+*f@)3{3pr-iBKt?&ce*XAr2wXb6={Yo zgPtyW@|6e8P&>X`Dz9$55g*_y6Q@#6El2$E`K|0ulvMp?n-Xz0`57Z7UVtDL$L%z} z3=37C@zA-gA1d{F&|JP&2di+2iE3EzFn%lU!Fea+4QcFPmSaaE5oc?fQ~Q01>c_jB zX7mF4W;*2wp3F>(P{cZtrr3{1vEK8eTK(sUd9cpGt+>TW_P;uProl6^5mqIS1wq& zQq3Mo$Nk7LPD#8)mdw2Fv&(LwlocHVnWk?X{ zIZkcmf)SJAR8ITj(PPWO`xLn$?-FRWC6a#ec@_@6&w^VceVmo$CGEn-gK2=QQpESk zH^VgF7CgRa{q6W@Io5u9rfD|2!?slFIEV{9YUuk={rE5B$=`P8iVOuOP5z>kKXa}z z4lN<@Rt}E8-Thw2Fe<5O6fYmWR`FUtN{_hXVu;XET%mcCCVihw9VvYE>IoG5s{`md$p`oS&!hYkp@D8r|HW7cA1ADh@P+b?%C# z0e_e1bc-3sxXSFaPz@nIFC7QZSUFDWmtLs(pa{yg%Z$9*=}bz0{s{Fv$l9VI^`E#U zN{SdzL&k6{{7rc8SL{`v+PvU#{9IFZZ+lI6UM7KvGuQc|j*`27T*N1hpBDJ36)-w_ zEeV=~qhA|AJfjMH*F^m3qti-;4SlKP7|XzB<*>F{RY&I`430N)U(1M1 zKv%i(BBlPMA z{qn^`T94OQdepn!8)jmn-wCIm6Az+X>0BNLtBVSH45dGv+SYFvkmR*J_LJ`oc=s+^ z_yT`=zu{XpzWYX@#j7BWe;4_EY@UDKuo`(-wMG9=% z!-Y+lD;LqhPp@}{$Fj|@U#H$FyXW^gzw1^wX4A&emLl%!XnT=1 z4AZP7cB<2_14rF)ISr#!teE9tSN8?RbS5~dmP#2!UcIy=o_`Y3b$St|9~j~!Cj<58 zSLmkmX1;V#IQMIKz0ryPhp#O4OB7XATt)28w+o+q z9GD*yR+&}#TM^jDRgIFa+TBdJ7{9Asuid^|_I4+{@WJaKV`mp#RV{Y1>L-=EA@9GEF!xe_^SWhYnKo6`Kt|vT;231sV8=f{&x(swK`{Uaj>Wko}bx}Lyk~Q^S z7y#89bjD;_N4CRZrZo@0a^P%ur|M{SlIYP&7*)!-VtZj@ukXqEZv)@{cQ#*Pr zM5`UFK2{1mB!Vgb{*GjMdzwO8c%}l5d{VZFT8bM%&A5mCUD8t;zE*f^Zrs<5h7=I# z#pkeXnU#gQIsBcn{#H;_?O!=p#+dlbQazA{lKlM*J6A^sFsT|Q$M>Z&aK!4md=%=! zGp7IC{O!afrun$1wsvfjeU4BY$}WMsMK@bM#7|}_p5@i6_Pd`j-7mtoLS4mUK>OTg z#5ihh2+U4zO4^w<37Y=PQcKNWNoaCG#}Sv`rkc^&w5s3?Q|Z0#w-|EyOb^)@&+{Ck z@NM#&ytM*R7_iA_FPK-3{NDDpN+u;y%^Q{YqDQOGxG`D>g`Ioj|8VYv!4Mg|DmiDW zAd1W1?L^6MEv2s3-cp_^Lip6i?BFgn-#yD8Ox!6k9y_<{l)O*m0>Xh!l^y<9MKw53 z(1mZEmHATlI@~ydm}_M6l90|N17nlNgfGR1vpuJE^j%1^NKTO93cT zpIwYmqDTwlx-{H#9F(E&@pqPHngx_71l37zGvZG(2a@owWK{L*YjA8m2)<|SzfHbZ zS!RUZ|JCSPO*deH{?$g*EC%&wZWgC#pKwX{C%3=zHcA+oI?#M2FPJac{k|@0n#X`= zL0!^r%`=Zy^^tMJ4*4uPu&A5dCI# zpM2TQEE`nih+Po)pj*6IFi8uOs*vPnJbt0G6d@EpAoQVp#G}qfhRg+Awy&M1D%3S` zjZ85z%T9haHPx41t_TuuQh{>k)Al5HKE1uet8p)lI0#~h6aX*LtNTpqS5X6pg)ta~ zdu^k_LD36fm^Vo35XqBBy8*^G&IjL0X(}Su)s*RWw{@iTiOp%;5cSK{}Pfj#z_MQ3H zR97j;WD>qu4Hd1T>O;Ork$Y;bAVu9x?{nu(*PA>d5ts6s@2_qHWfa9L;uq=IDOOf- z-2yIFO%Q?L8l)ZI;&nL$lhkpo)*O*lN9Ku9&VPU?j%+KRTy)T*{{)B!ym=E#i(q9j zLY~M&!i=yMMoIX;7ATTK8}#(i{b0OAW}SmBdVi|H6k_BR&|RxiAvxS1rjU#*PcSugDLQW0w6i941pmxkRN(0zU z$P7qyqak!xO87SIk-do`m{Q<+>*gT9)B%})ex=QgLo`97G(Es0rgjW z_H<6|8Ai0sTx2w}9bS#Uxvb0E#9>;NpaG5|1AJqPjDv7x8v7u3@9cTI=vGbn?{Q~{_@>8!WM zaIsW0B>)GMQ+ndR75;qAAaV{P<(MiLRHwsRHhCQ`ZRM+#-$Di4kZ90XL2T5z*ZNU> zWluC&c!+FkY@?A=B`QUP@{BH-ia(5OQ8`&iVqG#}R$L@VPD0ejWA-NzJQ8*K$r2GU zPOU3HVsS*h(|6ejD;?hNNP`+f$Ihe}h|m!|nk%u9h>b4dUs-Im8s8c-NRgPi!f%q7 zj8=(f=Y_prfXt5z-az^9ylOG1%i}FI;gDtBFQjukQ~cvDN8h9O5n61IfYW@W;bIPE zAMt&I8u7v|67Sfs+v&h|o^MMMbE91~je+6&`#beJ5pVe@mD%R~n*=!FhVF^K_!j~f zJ>E^D->jj-1l4gN{0pBkTz^Y%UhTNnH1p!=x1Vh9TvA@9&_l?1SiP5okgU}o?$j$h z1UCN?^6P}BkA%js4R-d~FYA;7T1kZ!#GL`Jtocb=h6wtWl3D5TzY3Mvn1&6jtcMOremJFml!Od~ z8sTy9vlgM*zmI>~U5eMEp1|)Wd%m4|9Q-rBW+9dmxw5KOT$Gj$ck^@CO1Eu!6cu(h z1eOilC81lUrAr^&%4bB(d_7Gsyi0o6j8yQ$v)wv-f&bLA4f6)c_8l_iVdyO)^Z{0_ z%IKy588xpnT|%CuQ}62=KlP2@u%!UQn7ReMIMLE zNem`QlnpGM?a;`-0dhunT;Wc6nHTNH_{4M~skyL+7IKl|$L(g;YIu_$l$0Zkah%Tw z91Y97XXVG+*WeB7gASGUUMhHAXR!<$vea--18~CJ8-h3oE(EVcI^n7s{__h~`=bg+ zzS_7aYmZdoIHD~*4Yw$2S7(C2^!I((cY~%G610X+uX9tr_CK{P?yg}PFZi6XRl zq#meaHyYD06NNlD#ss{h2dBw~=Gqwx?Ri?s@4_|HvI1h}8vrpIjvC^QSI+oj`@xHt zjBc6ze>*nkU3%RmN$NqpG*ZAen_HHHB?{M-Mu5tZK`nV^FWV(NxLBnxcW=1+lNE_F zj%>b)s!#V8yf0jT=4O$P0(Stc$(sbsG>9AA=+0HiV;r5*YEE>pP{4xwlR@rG;)5hm z>CO=nbm#c=0}U>o3I5Y3e^@>6Y|;xH763GL;xJ=%iOdu?kBFe4+9J4LGS zt+3YAjz>Aj5P1kMuD+%-t4VQJD5zneSB-|8@7K`nAt%BQHvLm!CpRPV_AptWJ2PyI zTyRTreL?Dd`xQPP^#=F}uXOs^N6O5EA2?BF=I{wT+)s{WTbw7KW~$9oK_-CLuXqJX zyLXdpko1mcYdTJ54~k{IaZtOiu|G zlLK^sy*o7eYRBvikHI*ge{a2UJH*rXaOJ#xdm--zoXF$KKd#Xm0)|KnBV;^&cV}y4 zJ%UP8*Pa^}`ud41!`E0I99m3#p`8Ziq6m>%j)5Rak)-}5SMut&N3e5{91@G`WW1`e z7*nHl02lG}h-Z|(U{c5!a1R<9)1&S&tmnqv!?jAa+l%?CP84U*0V-7Z^NzIvTN1Eq zphFH2N}cQS0~tWYZD`l6e03~1VX7GtnHjC#UbDlc_7g6m4M7v22^7B(JiFS54|Wi* zQXLY@M|>4=X?{Y1=8Rn+`(e^`)#59`cn@CU_Y7P~sc&;t(jiz2J0}G#(y=(L{08j8 zu>)>ofFQVEBNYTz_t_8A>M{M+3q>51NLr)I_$I`%{;dj=d{4bc-m`8`A~CB$eez*LodKGrAtUCNYX}f8NUE^%zG!MMXiAOjb z|MAQexvv$&CoIgo&n47ZNP=;7_}EBu_-&i(0|%++B!M2kI+9e1Y5ZTaF%};=Di*IM z#ZK6loup$-Gb$H(cmX@1y6g&C^P@e+17i^2gE+3WIFuWQzBYRfNx>hmgqp9I14vSU z8oUrPWrLWajoGAucmT)&Z_IH;lzc!e1yW`85-b!I1g>nVE?iPgCS6jFu&tN-%K)(4 zU^P;vM`a6!ge)6G1xHUnfGeX8)C=C!={4`|6Xs8nQtG62*;_fM=UX*lzJzH*fgY6C zVcK&THNvIoD^XB&1r;08HyL%4AAsfn!;usyT$Ssg30M`N=kok3{9Yo2wM%O#j%ni%|7}61YIs!O||@>m0X#W%Yxe52E+lwJv7{P)y(L z{#SA|1^7&f5?L$^%)rnwyFMVQxpANnWF;R<8i~%9>A?*E^7W&<8y?YTi2r*Dr$^UK z8js20FG3l+%06H9@XK}qn6;T%7bDA6)2fC+ENaU&&~QD;I?%Q}Z@UV?8IouG%r@xTYX5f3qdCIC(o@Y|!kpc;IzB5iLq zYDkXHx}iNKaF^--R1o2G45X9}^My%`r1KrR;JCnE@oGI>wzPy&HLY}0@&K0%V9B`H-m&yOeZiiCZ?)}b5v zIz*+1OHmA3q#bxY5wiVBUl9tT&*dFvDe`DUwgF=oujZcvF%6gses=@Nfr3B)A%>JA zLcbpbg4ja&TDbr72c%9ATC^^^V89D-8bYq{dq|9?RcXqDBCo)6)#o-1kPlW@sDZYI zGvTRwxWEL$lc=cMR0v9Xh3^(SEKK!x!fh}-tc8XbuiXGN?nw|ZK|y;xHD|sv%2C07 z&k(zzs#!)iEdh1G-gw7Nj7}1TfU1c1A{Mt&OTXMU(ZWWti6ckof>=_4$10B%K}@@t z96JBoy+@5$HlX*cWrW1DKBfDg5CcWD2SN0-G$1yoSRrZ$G#UU_QFv1#4=s9n0D|P# zkbVGi1uB?}I9Y=N5_9W{H<1`EtcG$P37j+tcJ66z+{OV zA!qp&Bn^>#!uRWghFS~d#BhE0po2Z$F)~*kr5lDGqINzE4bP=5Top_Z0Z7LDE5luV zJK-H3d2S4V^$Nf;KiOMp%JNamYFVbQOP6<+N;)Jv2{^oHaC*-kI0no3HsO;uYeGL7 zYK3=*<}^V-wz%SoL0QO)(_7#U@~Zls8@KlW`oQUCw){*40*v!mOM5?{3UdVd!;|Jq zsP00@;`FX#iu3&eQ+0ELQ+fl<6G3A%bo}7sZ0xT{3kHWDTJ|59onc5GV-ZcGAB8 zN{~Qt*=_?AODTM00srH0@X0dwp^XP1rA7qSJ%9%Dt`w04y1y=vNRWWO2f_Ry0T8-5 zP?skRC|)@T1hnlvKtw(ds($SX9Tj=Vo^cKkh~2!vZwfT)nqPUcD7Z66X9zA&w}yTs zo#qq-6#GC%nU`||7UiD2k_Mpx#@);5@)t7%^S*rq$z@|8cBLTd50R!34tY$<}&Kj3_>>|Z~mYQ88pa-V~!?9(vT>GNq9P#&9!vF1HSYG8_qhVd+ z3lLE#XBrUN5UFFMb_~_OG0uTzx=6-NcEVU*Fo}dk0N?Fx&%oPU>A>U7;S4m{)qcsa;&oNCrUHvv#D*QDn?aT&G*{H3DrwHK5ByJ-N%@G6xH6!fhy=-aOnlZ`k& z8o)IpL&k=N@c+DP2nuTpJAkCvJOmmrQ1CwhS76Nnis*08wrRi)G7l9JwAHT&MG&Zz z0}LAe320yG1SA=vZz#-bd`X3a^msvnW}80(88IGpA}>=BuHMN4uf8e|4k^%bpc{VFWcMFsL0A_)y=_ts*K(BN%!s*g>* zli4Sbp1o@UTN?uP$CE_HgdmaFi;EYufN9{MY;fVy%Pw1A@zPdt@x%O?wM$Uy0ThF`PF8^tw9FPC>)Xxj! z2Nn?ee_?6}1dff+AKt@x5d7fj$IsEx=PAs{>&YmuWX>zauP5RaYnMVgLayZi$1^^t z7_Z5R<}MH^4Sx@HDA4`$=CNIj0s_a3AV|O*f35GP3-t9p=xEs8>k2%q_OhLXb)IJ> zZItSGbu+PVYQD5>AY{PRCXUqq4Joschb#2%zj>>YV*I#V#~%_T(hKAozraI0l2ma& za5g~G1woe{Me09LZv)wxIHF`h>LzzS>?DCL2f35}7-sH474+gG==_(e?ZqKEz(qci zMjkQ)<*hT;&4^VUO@^eGLGBSo83A&1RGb~UHVgj;zcxHY}2 zu5md?Lsm1}>xkOMGlT#klGAVcDhPD-zkF2>TZbr~Y_b{Lue=+JnRCLTLL@pKT~NO2 z5Hv)}7^-J+U26T34e<#6z3rS5qWa!DteXZ{am{}wh3rjs4=`#UDj4bnR{e@G{dt!U z4xV!+JRC2JsOH-Kk$6i9w5=0vV60|pVBI!UBOdKt>St#!C29uLWO(pf(& z;^W}Psn+UVghBIxoATXN9$Yb`b@AXWoc5TYi&(y{Dl&$8K#av1@BB7}|Jd)qVM)!D zotJP9w)IiD=pOTT_q>2`YQ$CoUh$|N%VvmdH&U-(@*R#IL9q^m8&4gS^8sDq`Ol=0 z-^-4}#P57eiUiW;f`9yw=M7OeRu6#omLkQWP$?uUJ1s{VG@w$dc)J?j&^H#zj@aD0b zz!p7j_iYTabaA^Ik-fUFXwWc+ZLjezhsswi{0krBT6^Hxkv8w2gZQLyuhpl!H&Spv zLWtwc(NVJ4#9C)D7D38YyUm9$p1;E9UlXZ1C-#iQ@Yej~nuo zpA<^6$PDeRr{MR9iV#IYze>rfpl+<_8oe%k!Kvwg3Gi86naER*Bf{uy^%uSPk^Q@R zN${4gbc_P+?CBFQYPxXQxYtjb=H3>gGOSeNq0>ya?a!``o%n$06&2)NPOz4}ETHvk zx##1Tk9#C*i}m)kgWeFX;zxr~jfU8XJfz*r$WVcr(gcDMv%JH6HMy7cLK&()%;@N-mNvugw_v60)Pdp_V0&@MEJEOa8*ZsEs623Zct;z@;zbemW@3Lx| z*jB6E<8zW6Nys1b;0lX^epFN%>RrXVzEFH=pwOtPt!KFKCbi(HLa%&5vbQ&FuVL&a zXD2bZnK3_I`rA`Ve~oB3>{=veZQNUs5xqn}wCu%dvGu{~ICAgaD@J!n;y8OyDLn`k z+F`mhGpS|o-}}1R5<2{7cXyHHHKy?QANM06L-*Q?_4sxsf9t%V4P}n7k&afkQfEtH zHFz7_Wz?a(t1%b3X&H2vEdgaf=RcRAYZh=ud)MQ|^rppii|~4{V0`2oN+1)m7vp(P z!{`zZp1x*Wf9@lau1Sx+<<9m&zH#TKvtSzODl|C>MH1vU&whxnBPEgZf@bzRy`3|T zCY2($%c$?4!8_r_b!`hax<>HKa&-yL3nodpsFI#U%37j6-re;+HVJ80nc3gdLq%hT zZeOTSzJ}Ph$h4vGzz5?5Wi}FVq*58aY7qunqR@cp>IqetwA}3D+W!uuyQ+1T9DP{< z*9vRCsiKSl!4S+Y%Q`u4)EEUL4nzB7C9#l|7LOci>q{mUL^nNaf9ms+n_xCRs61B|T$u#J_tGOT*4C%R^2lNkwClnHTh%AQNypd5aZxN*LP7x?J8YTRBt#*N#N zg$zgt+yP6iE-%aMq#%j8pYg@^A8XnQ2?cg1XCKmgt;V$J32Ot`AXm!03u{{B0DBrd@}Kh+vNyn=>=pcJZ-|t-06Zf8Jp3 zyhlywd1+)LNf)vR;~+Y8Mv*QP(YZ7Wp?Rc$J-PE*Q@Wc^DAjI*xmis3<33EB4yXG2 zbo!qGtvKzU3Ces}aJB6sV88vo@F|%>0#wikb?-7JO4=IQFbSr~U2#Fa^0%Pe*ogy@ z21_qbPS(oO`J%P`#SkT>>fY7;!$gd(uP}~F9+F`isI0viYdykI!AVJ1gsiD-!R3!YAV@`E&uj8}2<5_lYQ&*pjU6E26;5NapB2p3%DqmOkn=iM< z4b{a)8%TwcDZ2?OrGPmfk69T=C12@-zs_ZXfya5$SLp(ed7YMvyfg27C|K6@PkC^= zEUmbk;%NSL|8}9;MGem()p?HD_VoOGBCqk4T?9{nFi%kjFo7g%4$-0n6wc@rmah zUgF?MAAIeV{Q01a9#MlFXCYMqaH!x8bz9k_g(bbCY1{d^<_gV|+ozNFLId1XXwcct z_ET=&S82eF{x}ElDsU!)OqpOdo6O+3CYCZYUMF83^45n{ut>kow>g4lpr7gGht8JU zQ*Qkr1vDDGrp|=;-VuJGaa|qj3T6MbxWi>@^{|-b&#uK_=DG;x!@fQOZzz->y_nGFk+a5kY<;1d7O8666kZm0jB&Vk zmA5l6m;p2Cyf*66MZrWxkALz zr)fso4D1qC+N`IR!3)D@@2$c!4OQcmyLrPYedx`FH-!~c;*IA>Byoodu|25koS_|< zv@sS^1z+fTj@zdn76zk-ACMai}}&N{twckJB>BM?P$&0J~e? zx^Q1W?VN)5a%9?bRSyj$oU6TSd_g?!TfQb315T2FWC=&Z?;CeUyr{u5f(mA21!LZk z7$|7d(mSP$PohJq>%pq?et%Uy$=$C({tgUD1V~_1OW+M4W^R^XKJ?30rD zAU92t?CH0xs*s4;1p=|$Dck2KMO;MaAd7P2vCYbG=PF91k3rq&wmw;}G=qVVo2xtz z;?ioy`qrgT%IC8ub0-t4ty3(t_&?Ve@#=trBS(+smrWyJ?2q7b${gMr1z;X?BZUDA z8gbD|yYuEu3LosfHpn*D@X5(#HSM>TTJ&h2uBfj%rM2^qjh?ES`dy0N!WM3UBTDsf z38JBkPY-X}U(QDpy@9ky8gX5|AN#cQo7H=c1bOfj9xFqCe~{pL)K7svXPgchQtbSA zf38NPC?3V1W0f!Tp6pNo4sD~z1B8d>jaBd$&a#Rm>X;=%gWmzO0c}kpx6hufe|8s; zQC`XKQv2ie_bx9!gPZfs10JYVm35`bm2p{{H{_UVB9dWp5dop^S^jmW-5j zRaSP&zPLnYNJhxcPWDRNn^I;-_LY!5uX(xGy}!55_mAKG_nyZ&uQOh+GhWZ<^PRv7 z$KtdaParYpk2@JmyZA;XzlM4u6KCbr5-pLjD`;6g>hzVl~Fe~M$eJNR!FxYAJ zB5cs{x(Zj|krJ#Z8rJzujoYmHmmulH$pAGvxeUYVkt@B4i*>#_J+>TVXK4JTks458 zi-X1rU+0pz&5GWn*6>XDiy*s;H1kg!S0zb$;@8Z37X64f7likT$=mq#^UP&d@$Ih4 z>$p&LsT;LZo9h9j6UGT`XdY_M>R^Fa+I!VIQuQ>`_$^ZKHVMALE0*2NnN#7^I|)dZ zPAh1%K&ui)(&Jnie6ib*7*28&6eg9ub9yNCg&!_0ZgOqQQ=jV+VwSxG+yIh zY*a%&MnW3jNJ-|&rH^h-4bS!dt;p0R@z56;3HH9MFL$0r3611`d9j+BwKMUYjE@&- zj7-b9*|pmC{_#@8M)$8vqbaX6<)XjymV&|TE9IbJ&Q?gu7VL4l-KjZ@1W`Mkt=S#A zy$mklW*LZm4L^9#nTl4}TV~Go{U*6Ljd=Vn{SM*9w(41+cp?Ls5T)sKslz4*^`M^! zReKze$av0Rjz=NQj_z`bzz|k5zC!m^UX^XNz*L{MW7=Pr7h?m#8v%KNZ|mLvfMb`; zmpqZz{>N$JLn*!!L3xfPRtUWSzReh+hux)r%_N__0CCZj6pP%=gv6RZ2QHtE3FhT# z#@7ig%r|rv^4k&}>nr;v3_A|Z19xp@@M<=DX&H=NBIqgsT0U;c_DcS^ZdJz)zX~(Y z%BZDm_PnA?$?A{#sn$|eybD!^a0(39g0C#>n6dDw*g7W+E|K?`eVQODW(GIsn#5Ju z%(ysa4gPveI`a$OE1P>F{z$k@t%IBTEGKRgCpsUlW7}6uqWTaxm%%V9LrXnjI_7v=AETQAf0D&qvtTvfyX66(gg{^|B1xDj|!b;AQd& z3*YgNjTU0GT-Ci15#BlQ#5Vph6Mljdd_FNH`U`z4K=JUHhe!0#|J(Ivwn`$AzdtCk>t zj@`&3U<20Xy^ltFauOJF4Vz0}X%fH#47IyGBAacfBM&;Fe2(4icei{0ho?Z7xG!;l zOD4YXy;YT?ekVY()bfmMnZM+>`2(k5^-$WNQ8yCC}Gsk-Fixchha#5uFqIwQJg!o z3Ulq>w)orTX&a zVl$m73@O6{FuT_g-hcSU7)^mNt-o$1z_v^!@8#3byZ_?3SeBga^e;~85fJ&WF_Y^f z?%6!Y|L-pAm#ge_jq7XCASjyq;QN-+UFR`cr+n@OMivhZW;W%oE(*M-~w4g3s`c;oF?WO z%8iTq`4uRqQ0TpN50osf?7Xg{v{fv1;hpB2W0hF&82|v&u~i}5{%+ki2Y-s#LEL!e z>TDUC8{?pvVl_ej7GYMi&Z0=OGeBajvi{l2cG0>xwKPSQzosCT+spcwLv3-xwFdF| zvoD$XWdT!-eP`{<-O}v zP^|Whehs+dgT|1Qv0KbEVwuKOl?LEV-2B}RJY1pg*oXZjl3$#C&%$V5?cETzqdn4W zN77PGs!9FkeS!)fF+(PMe#Pus&H8hf1{~b!(8V5;VD6in)N5(kA3tj+t|_b!z*6Z69R{!1%g#V@%tEUk~oT9&<`U&@^V(moKm zr1Tlbj;Zf=!}7|t(`qG$KXJaKkYlDbEwpfRiz~>Diz58~x{xevzO8+)Sx*WfThpBy zVuUn`IC}p5F5jTmH@%E8HPYz=0^F^)W3)@1z}X^d@lKB`wAr-F?nxod+u@4=uhvqj zT&+}#q_VWs%8HnTBo^~3=l?B z%gf7SE?}U}XuQ6u(InB2qNk9e8?P{j$_W^0cTK{J7)JcP3|+)_&{rXCHY&9PL%x-% zxJ<5M-v?9sUg11v(yoSN1vyC6-aPrG6|E!pDxUY2R|Xw({^WF63YPth^+<|*ZQd@J z0~h9Yd~{2bH)mXIFV9*-E~nVy-h+iXV@aZD^WOgC7r>~*nmH;pus-T*3VQ6yUbn!j zJ@)q&m6Jzm@w@%yG?$4Ep4tTSHr0}S3gPQy1L&P&H!?0vd$ne>0lfGc_KpFS*AS({ z6A74ke+&#H*uS2lfwPlZ^W*m)<|EnE3lQqnGH*Q81w_y$g5pyQ)e>xD8G40L3b-D< zp2b2_^c~r$)n4=dz2^Q|PKVL#*xOAQ3b6Xe_dOTpmL!Sco+qK+F-f_j>`Y{Q-4P;{ z%Dl9#lVn$Ugf<*)uKy4(4((0Mn8l}HsIF)(2RBf3hVScCr=%<~I@iPqsQ$|QLSI^8 zovi)x_UHr4GMigXMKD1bfFuWT*`GJCg!AZAAMn@pv+32Rs~QQ1j1RIWlks>5yLqww&zn=6% zL5X3LmtU+-w%np`n(UtZbTAP)FxaOTOG2@YV1q)ZQ`MplBJ5IZEVt+DG3|)`J<^2t zT;-8i)hx=s)1?ZrbJ~dKIIfXTDPQt2m{J$Cv?H)jFYQllUvp&P8wFgdl-U*OKvMEB zeY_I>BC%(IM4=LTTjl$^&TdcbF$%6hM;}p;QtL`Rav^#FR^*;jU7HJWh-u7bWUsmj zSA_qn9|3u?$Y7>HrY+N+?oZ8MfZtzuTdztaE2s8-@;|-_0Khs^HPfJJ57chdBgRwo z2l%Y#|L^NFOqwJ1p(Xe?@>QYo2E2>99lJ_$QVF{^^D!qo^TUU%++7{q53+RIXkQ&0 zb{@2uELe7f?~SypaN(uB$803dt<#{b&fNnuFGGu5qxIkYzT995a20cE3$oU0Psq$X zawkLo)E9Y_CY6(7VDk0JhjZP(T}^FHR&Vk{3cIf!30sTv;8J!1F+PJQKh%D=ex9bt zcm+E*N|xuuW#7Om!LE0KWY`|=RrWjYbLy?=!oWN0Tg|C7$W7drjy`Cuf()D^lt|Fx;|rzmG&;`;_1C78O- z5x6ou;B3PA*V?X%D`K=c{}XbC)>5?p2|jcMD+Su1cq{LWb^`zAqcOX31tXUeF!?}V zZE4gp@0mjGX!8*{IBh&V^6gIt##6Zd zPEJ}VgaQ{jDGfG^iSod@cYP+c+$VVZyX6ORhZ%==ZP}-ba+$a^y?OzbXeA3OWvqf9 zh1Pu3OzOry@CHv5IeYUIUM!ZmqdSMcqO{+A)}sZ@(-qVYKd1D+_ZMuvj`5!#A+?)q zbC&4>dMKdms$aq-a}1a)1`W??qL348gfs)zOWeu42{86hEUPJ4|n0>7jO zu42;c0&a}#-oGLNIuaVU{bY_2B1Ue!Yy#&@$wd?)=L6GTN*~HJWTh#x z1=FIF>DF0u3h5GkEx{!91QPG;INUz-57nG#_aOx2!!lrBSytb}N`aN;@DDi$-R^I& zmOWn2Ubtzi@(v86DXPe_HsiZNe|%&#)UaZmo}h0CzBIc$HiM__Z`%|5AOn?6+@R2ybn$$hY!pw78b+4LVvQ zM-W>MAF=C!7hCw3L-(0wVF_uFsiH+#j1V8nk0H#=$i@AzUqT=J^=+ zgiiFnkewbR=J z4=v>FT~|-+4%bvUbg{{3dd$MN6j@^xYG3o?R!1%fhyWa3;uuBDt(#yh%IV#tm;M{0 z&4~{{W|RH9++p=oe(@oRmJ*@Ts7MGXS|A6a8@Tw0nYB35CvJRGZcL@wG}diQwAyt^ z%-o{VVV@6dI$qUqDa$Pt2RFVZwQs}dsb&Pu-|qgrzn8pa{>;k0DVT*L*q?VxqhSH2 zoKfUE#fnAc%*d*qN`Cb%^Nh{B604dsW2q)o+EdTQzCwy^*m z!b(BQLxAfsv@czJ$v}tpgzR6pNhAPd@{@5tSd_kdCl;lK{H4(Iu2bWGqDdug4gtD} zuYk{&0W|kNL#Hb|hB=nAj0=op4iz*h-_TV(@5dm%cbV^tPy;@Y`T=i+Tw#_HxE8Go z|2!il+JMDjomrEghvu$}P~uNYKsnV>QMbb5&p;;H490=X$O5ji2`S}0aXQob0{Hz9 znEnk;xN<~%-deu&+w3)IQP?G&FbBZ;3G}_ku9RcfAIKuE!+w1&_BC%i4HWQ562gOI z`U(r#>r>#dFpPOA^dYe60CglQZ10V-BcDT=+9$KoP*c^$fB`QLWNmo<^Zw-(w%GGA zOunv;ksg_Ag!B+bNVgadAaaxRVmbG~6Ufmc$(+RuCA`zUr#TT)=QX-Qo0nfiy)Vr= zxe21;KRuTHM*#rVCZ3%z;}fl^%}ndAEipEd((r~1;C~(R2InK%qBe-&&L1kyLh`Mj zm(|uBKW@Nf_FFc<*PnRhJ7D4eQ};S|7-ui}!LVv=NuxR@e^zqfzTwUMvXSRM<1-tR*8%a};ijzSPy31uf{kcMm`1q3gOVR-}EeZDt601Sl zz&kDm{{cP6w0ZjD^3<11qkg(c7Ju#yCH~2J11A0}AooabaC{81?=ISp1)s3qla<0q~g? zrZV6D+KrQ%+x2KeKeC`T5H)8Q#(PZ(aQLcsWA2M%cKlMkrSu65^YbBgsOb12#I?*7 zt#4s?A<-He{5mC{c|@!T+-m)jPIcuk_bp4=Jk10p-{x!&)af?kzDbYOd!#%hH@bNl^`7!{d8|1j=39nY5c#SUYkI1 znBmGP#gu~aGM?%wg*BZ>v?m(0Tm`h;o;38LTE^MhOQWzrhrBi))c4Z82`}L1Im|T3 z7(6r#oX1<_>0Prg7S%xp4N?A|9k`j;Z zJeWYH^W1rssu_CSa@EWL;8cn`bs|v2T+TjWKb+@}ezk z*<;V9Q^3{X>)#c?P2!ku&bO?QunFnQgR0jsf}zhgO6Y{4WHe3serMUwJ8hwP-QgTo zkc+@@pn^s-E1SH`NZ&;IlZcP5X#^Q4pv=~Lc)3m;BMmO7(fhca@0Lgi@ZAVq`ibW4 z!$bEUUHqOe_Vd&q!LPoDmTPf)bN!CJIIAZabry~OfWTEZ*ni=%l4u|e|Fc2h0U*)3y zs|7g7zmM{k%Plr55uV1=wwOP_<#(rp#w=$eWyaQB;OBaj1msUW@WmL7+0dAb`~G%I{kedm~}i(h%hh`@Th2{khPA z9Pr8ArL*#FkL`xV{ipKjPJUqw^v1kmoxq0n6VXYC)<{W=xkG4AWrQ*( zM3R^MZ*h-rCo5ezUBl^wGo4L=kJODJq8H%ZW1uzLvd}lUT|qCGsrhL z=VW6sc&QE*Khts_XSGQC1-unpiRd`lHS`LZS`ax17P~_4<_rWu(StpN&e*V0(*n0{ zMY2uNw$SM4aQ=@>=VGuddQC ze!P|nLG^CAdHf**cj8fMJrm^S-$N%a&GygIHAA=R96W=8e(DI8*G>rQxB0sG^!n=3 zQKz6UHLYhrI>7+|m;`0g-Si_xC$~DHLD9l^KCWFBGYVw2F5Yq~{f-tvxNk-@J7VGJw z%1h*U&WuA))@(TTV#>p#Czxx6Y@Tu!jAm9Ei5kj$C(zcCh@dnHOQ~E>ty4_5lrE?YGaq6g(U7bo{x})in4q`t&d5S7EYg|~g4r-Nrgq^bO3^}uNANQ;^ z7@!2o<-j*pf=Wxx%q(?c4n!9E%M=bMM1GiHAIV$>MtIqY`M&t;bm6pCOlT1H4B_r& zp^kyFK9c7Px%l@xmAl}ZMY9B$bWy&v+dlpRq>Ir3&@g<3j>d@GSPJcTi!>e90ZYpDUfVklgFB|oUJj!0+N-u*+gue`LJo$N;dM@e~*ezPa0#uu;JQp>6SMw=W8 zK{?YxJt)7i77&q5J;8{Jc!7jYk)!U^(dAvGD9JfIOAEYq6_r@U0D9x-D>S+KyB}K! zuQ#(tCyx69ve3ns8cDINrahi^#DF-+&*G_ zikBE$K8zU_23r932a%iFfuFsog@R;a?ICQY1=ezN#uzWB5nk>8WcaOJD47w;_ms%Z zUMeTqLug-fA1^2TeTG*_Ad3B`aqj?==X;HO(Kfj3Pv26pdTE$EwJuzxMWt13pa`z< zKPi7F>QZy5AX5P{s;3DBR_L24?>lr4UK3{}aS8u3di*jnEzGQTeX~>RGtZ} z@_{Doh$+7vS=N24=vA*NtkE?)cxNy7iyI`o=+i1HRWKf-y^kI%n@P;jaA zFR3A5Tb!CqlQp+Xi(Hm%flOz2B$h-He#CM~J1#yt-k~@;0K+-irS+i*Ll!f2jfwhM zfWDk?+QkFu3#A`e^4L} zwf&X$acni(6kuB5{TcxaH>QmG7>)g*HXXq***q6YGXn3L`64**_vL9Irdla5P~**n z$3uQrAd1O5QY%Zdu-i0cIJnWjifx%+=))zt$7%cKFQ;QNKWn!?<8=>d*d^iRH_|sS o=8Z?rD3`|*N{3pkZ_B_D{mgd{ZyHcs2!KC*9TV-3S`Lx_2h?f0T>t<8 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/error.iconset/icon_32x32.png b/shell_integration/icons/nopadding/error.iconset/icon_32x32.png old mode 100644 new mode 100755 index 9541677c5fadd15554d9f908cd35d642853d5885..f53cb6d88009295a5ed494fa2b49470c7d5c36ab GIT binary patch delta 1208 zcmV;p1V{Ur4(ADw90dS#dV2JcAt!$XiAh93RCwCdm& z8>+M=fKb_?E96keMO6%azG?7HR;$eJAwAq1@iET~GQgj8X{3JHW1Qc9|}k${4b zDkW)+?eRSBJUHLPPHoS1Y>EZqO4eBR%>8}$oO93pMyCPCI}aFRe3?}hWrlyRk0wcS zUqY0}e+rSWD9TO#x*R9TJYO%o`McO0WL{+e@T8(Db4i>WH*{T?hGrVNM!KpIi%IWc zD1~uEB8Rkz7aFHi@n`eK;HVw64nyRj<14S#0V{$w%Zg?VQ zl^?T^(=4Puuz=>OmkdoEFBO06+@N5Ykc!g?L=4;zkwIWadF zU}XfL;E;1!*ty6ljN*Yn>Ajr$MDicz1|u!*b^!=IsvQN_{gB{W>UMvbicdVn*HH?z z-q8uv965z?vLAVp>Sl>T(lYm?3}&TF9(r^2bEtJ$wfojR^!)rcWR2~ni1T>ywHL{| zewhs6WZ?Rq{WSZ@0vWbV-tAj-Zu$rny-G)+fq`w^2*SCvv7y7E$zY#%!=pH%^1jIw zj-hcLk9~BG%>9Q*7=?cfeCE?HQ#giM9;Nbw2PlrSsRIj4umPP#hm);dW$Be))+lt{ z?cX#b*C9gaCVzjJW;b7Tmyp|A%nXp(jDMiY0wC46-R38R2x`?=VktH zhvy@=?|fjU?7!R5bkib5Q-{_eQRqI-^L_($fNDp+3h|d8V+q<6@vw4%5HN)E4xd(^zJi3CM*HiPu&P!?{Gx0&Ty|Vkg`Kb9{}EQuOc_6 zO^19sW69v#VV&V#VIYS+FlXwFx9oX6BcLf_71kNJwS3oAZakcgeXc7kt!Y|xfTeJY?>@4C=ybRmLsCY z14a=>Egr3v;xS?oqVYrpo$;zwsZeCl3IT^AC9vHf(Ed@nvpf6yzIpF`=g#IwN3HOf zJZCZvhx3S7DT=|~p!0Ee#o_S8ovY-SK}N#kkachZVvy+|T$mD;LtunjmI%c_GG+Rg zw;>)5NAOj}#vyT%NUj1_Q)NyJ)u`5DY#ffqH)>^y6bJ$3P@+l`O6mT&fdZ{KE28Y9OYA~4r7$QKQu0dqR0FB;fOhE+c z6*`p`QNbF}sVI}fX-Fsq%k*OjYVEkJMnBpntYI{xOiN=>>CTeIfD+07L)GeWv>u6p z{>k@0h4r!NT8I_{>ESe;0vlX{k292(E7UVwOC{kq*HB!lwuvM21knKswU1wG@;%oS2#tciP#4DVkWF(nzLMw@H|$Li z;MR7zau-|eRSn?k&4)-kKD#yhc)jCA;j6+IH(Jpaboh?-3}m%;Jcc6u*~*-!!yI=s z{&eGjH*tMuL3U@_jTqEG&L3XXbCWI7xbNZZl7 z$4x-9dzEN6{`0RC+ZEitNk5| zFrCG+@ju18cD1ySCN*82M)8|tqh4Mw9qcPNTS`jIW&14Vw}Xj!4*xUd4MT}WPcP|= z18#)FUMuF)-(;#L7#uYT{dV7V*x{bW*eF6S?o!qV9@Q(iWD~hD{0~LI-Z|i&VxlL6phfFTRJ)-dIGZJ=3+)BKkT7Jczzo#s0 z_3q6(e%k*VfknOHekw;$?|W11y(F{28;nPX?%2Q1wOskZv>?0a@?2ZT@nY*2PdgSB zlcpr*Fddf+<>s>TY%`fOrsnOon zDb%X-jul({ro;`tPGp@Z$M?=WQ7h#_F4^+vK-&~kN?uTJB=8ovP9~LYByG8H$aVd9 z)`Z&)4Zu?Cl=hn0ErTcayAPC-jwLHY40H4CNkbn7y`Rs71ib+^)KvMBNO1G_w|(E- zQh$NxXw|oMpoCaGj!Ey37oD3eEsSoLKWc{(TG2if?RRTIpL2S8p6n-7piM643DRWz Y!E4!Vr~OJ!Isa7=;ZdR=mrK9;8>}nS;Q#;t diff --git a/shell_integration/icons/nopadding/error.iconset/icon_32x32@2x.png b/shell_integration/icons/nopadding/error.iconset/icon_32x32@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..f9938e510835cc1d6aef23a7c141ffe70bb9f56f GIT binary patch literal 2506 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 UrR=a?0RR9107*qoM6N<$f?Gw-jsO4v literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/error_swm.iconset/icon_32x32.png b/shell_integration/icons/nopadding/error_swm.iconset/icon_32x32.png deleted file mode 100644 index 4c916621327fd4aa818849469859b0704f646598..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2541 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 diff --git a/shell_integration/icons/nopadding/ok.iconset/icon_128x128.png b/shell_integration/icons/nopadding/ok.iconset/icon_128x128.png new file mode 100755 index 0000000000000000000000000000000000000000..787303f63186fda5affd8ee7b979ed6452bc036c GIT binary patch literal 6666 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 UU-uD>qyPW_07*qoM6N<$f+MB*u>b%7 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/ok.iconset/icon_128x128@2x.png b/shell_integration/icons/nopadding/ok.iconset/icon_128x128@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..932ef7c5e2f6e2c5a0062319c46e723a3edd3765 GIT binary patch literal 14441 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 B8Iu42 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/ok.iconset/icon_16x16@2x.png b/shell_integration/icons/nopadding/ok.iconset/icon_16x16@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..11ba28b92b69ef12dcb64bda1ea6fa1aae5dee13 GIT binary patch literal 1243 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 OfU<(R{AXE<(EkCjgD(dF literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/ok.iconset/icon_256x256@2x.png b/shell_integration/icons/nopadding/ok.iconset/icon_256x256@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..ca2b2c4bc8ea82f9157711e148ee571f3b2d79f6 GIT binary patch literal 31427 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 diff --git a/shell_integration/icons/nopadding/ok.iconset/icon_32x32@2x.png b/shell_integration/icons/nopadding/ok.iconset/icon_32x32@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..f1eb1b2675bb2cf3f063db807e912fc335da2669 GIT binary patch literal 2722 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 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/ok_swm.iconset/icon_16x16@2x.png b/shell_integration/icons/nopadding/ok_swm.iconset/icon_16x16@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..4d5da8c47a85dd1ff3fa01606edbc8e24dd4cdea GIT binary patch literal 1452 zcmV;d1ylNoP)AvCF$6fM)T zF_eru!>NP=h}}ef0})A-On^^K;D-a6UNf!g61`MlKO#LtgGSag&J&4S{i31xfX^rJ z_Ns6+SRIWhxCow*MPgP?6Ee?)1jlz?R@lu- z)P}Cc_S^R4r`FAT3J)GwhCw3@p{4SXNE?|N(!`4tuQmBA7zvBh!h$R3jx&aWEe)+j z^UpNA1H&lXassX&CFy-b2=Vau+?{sVP{}DX3;!jF(zX(z2#ith?!cd(jB7)!j-sp{?oYW@}?0m zD+65y3)XHr)B;_1iuT|rFeYhXxujS$ad|CPbrq$P3#q7ZNtUkchw4x}a3ogGc??VH z@9@_3CC_5(hwE{2@HlEh)3LO^88$2&{^?zMCL#2=??D*Tw9O`tz-+GVV@EbsiFq>` z@bJQ|oEBJa_4uX#JLrKdq?RqaOrP%S&Pr~n zxvs{;wZm99|4HAM-u~ug>^uHC0s#x5Ncw`xGh=$#MR!lFH7LWrFMZ$h1%(w*wKOi) z5NswGG&iJX;jOQ>`!42Uv-HZ9NX08)8aVb`MH#l zz@DQ!;J7wc&3P2>-SsUN*EJ)Vh!T-#VeXqYO#1(44On1oWzlQ$gQ{@l?UO4af9ik& z;u_UnmD7X{lZuHhnTSOZXr?;M0d-XnT;_!NRc8$$Z} z5DdfmZgG+)1#%CgK? z!8LoAz?SDAWpPB#^z~+_5Z%uNswoG0>ZaDJrS<|=j6)j85|L%s-h-7lZt#eVAKmfO z*x0rLT4J#O|Cy&BteEqtZ^DZk6jQkx)RNkSkJT8ppf4(A9ecmrb69{^U74_why? z?fC?HFjip6=yyXewc@5TZLU$|ibqZ9muw)b!r=)=g8PVKM>xM>i#W6~ePSq?Bm%KC zfzKUl&;MJtd{hse%#z*fZ|Bb&6BXuo!SNoWPI&Hk@B$4K6p1#sk2LWHX`(ikir>I4 zNSB(T3Q;ofKz!KgjSY}*x6&0t^6;AI%J^xc5>30000 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/ok_swm.iconset/icon_256x256.png b/shell_integration/icons/nopadding/ok_swm.iconset/icon_256x256.png new file mode 100755 index 0000000000000000000000000000000000000000..0f0ad4853bd73fb94bebe498e3078d38d972851f GIT binary patch literal 16286 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 UYZt|WYXATM07*qoM6N<$g6{=>bpQYW literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/ok_swm.iconset/icon_256x256@2x.png b/shell_integration/icons/nopadding/ok_swm.iconset/icon_256x256@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..27bd0858761fdbe70e6303af5762d5208b0cf754 GIT binary patch literal 35262 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 B2r~cx literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/ok_swm.iconset/icon_32x32.png b/shell_integration/icons/nopadding/ok_swm.iconset/icon_32x32.png old mode 100644 new mode 100755 index f2cc4b9e3ff16d5aa216f69fd7adc69805de3273..7c0013e83daa83044f063c2edd652481c9c75f44 GIT binary patch delta 1416 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 diff --git a/shell_integration/icons/nopadding/ok_swm.iconset/icon_32x32@2x.png b/shell_integration/icons/nopadding/ok_swm.iconset/icon_32x32@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..388f75ea909c6b8f79e5d750a770559b489ba3ab GIT binary patch literal 3267 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 BAxr=O literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/sync.iconset/icon_128x128.png b/shell_integration/icons/nopadding/sync.iconset/icon_128x128.png new file mode 100755 index 0000000000000000000000000000000000000000..63fcd4ab3d96f2eaaa0a3e09649d146effe9f902 GIT binary patch literal 7701 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 P00000NkvXXu0mjfh>*dm literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/sync.iconset/icon_128x128@2x.png b/shell_integration/icons/nopadding/sync.iconset/icon_128x128@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..ad0129603a613c89eefd2edb7fb1281ca4cb7525 GIT binary patch literal 16662 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 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/sync.iconset/icon_16x16@2x.png b/shell_integration/icons/nopadding/sync.iconset/icon_16x16@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..c204ba962a4b4739ccb6cbf13bf195cd4d13c69d GIT binary patch literal 1522 zcmVzo{$)0AP^ECh!{*jpEM>KBK1LH0ZoX+Cs9c%pe9%&5(uFI zq6mhD7-l9U4 z%WIu&+m*24a~>*4DT;EFcP+MMH}QJ3=fu{oiR`OP0QgaYe#nj2=w3ao2RwewuS3;U zaNT)_&tPRNWQ`0mLun&pW;}fM1J`%akU#Is00NiLs|FkP>)w!0^_BWnRo5oOmX*mO z)oWO0B4sI14w0?xG-PnX1~^_T%1^3SD-TwLyb}d>GbGuWFfB71rx7o>v7xwtFXrkm z{z$;@D+?%i03+2iWE=fM1~s#sMw~5bfOyW8bN;emz!&l3L5@_v0i!25L?ag6&4f}A z^Q#1|8qaf^V)IgTK}~_3wVX9oQ<3Bvj{+2mb37(!NC(3)Z$uM%-VzEE-tr(Z;i`U}Vx~CakmW2%elTDaVd2AI;%?op&tuMn+JSm}YR^^<_ zQ7Z;Mi+VV@cikv(@%p!m?P$Kx=G>2r+BxMo+3+%Eg{h&WN!pXJa-nkeBZgoJrh}c! zaJRw_MYTc$gm!_H4Z}gNvnEu((|8&^^eiKZ$d)+TDC6^wwl2Zq1=F1OhEf^4vilgG zm|c#q&;A1@uu;cz;6`7Tx6`!1oEJAR!>#f~1A3wN67vH)mSbjB#F-ytjCUP8gWD9G zNn_JAKz=DzMX#B~iBs31bG~riRCuU?jnB_R?_dfiFSXOkJI*P5KbuUOWTsW9SS#t( zO>|Y!0G^&d)tR5WavKdtE+XOg3fANRb3&RzqTuXo{B(*JYI4Wx1!%IkpQ9%%1Ni3sh; z!=++Lc*kFinroi0SmH!|G&M^i@L)h%-eBuP)65>$XoRU^htc6}8n^Ry!RvGh?XW z=Bsx$F%%w$fiWT!MzrAv9v-ux%n8h{fks^^+nrBqvD^i46VMLL-=g@dUyP9cqq zS69wOki;wY_@|(R;@bP>v&qNwYEM-d^4-UEX~3EGXY=!-f^_jNXsR6 z48;>xI%zg_AKS97XgL;2EyhWsXBsVLrog_pp3Bo)YzXb!K50fbHI#J$FD%D-b;RoT zA7n%FT1i#JTTI;%QeDaV@J9W7EPcER)fFW?X|_|2&-`%{Ur=xzWM+WJ(KU&240iNO zlloQn@hzM0uO*`)-t6d>%qc{YsCX>~T&yLVJWc7OXv-(XhsmV)G@BG3r~lXI`F{co Y08=*#=lv9PX#fBK07*qoM6N<$f_?AQmH+?% literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/sync.iconset/icon_256x256.png b/shell_integration/icons/nopadding/sync.iconset/icon_256x256.png new file mode 100755 index 0000000000000000000000000000000000000000..ad0129603a613c89eefd2edb7fb1281ca4cb7525 GIT binary patch literal 16662 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 UD8c>c761SM07*qoM6N<$f@$T?r~m)} literal 1884 zcmaJ?drT8|96u=Xkb-0g1yMQcqBx=LwNTn>DbRxAC?eo1+oad_3RT-X+hY_ITEU@i z8u1Ae6yk#KXq_?YM5?Jn5dkL}bhzOgoG2CIc-7vn2yTDKE_b=#_nXh>^L<`!Yih~@ zpNUf@0s!EXv`~^pzCq{X=?(xcZUu|vq!ElKtiaPz6`l+05Fk#8$`LS017{&=2&~Lo z*MQ6i0Ltg;j1~9_X|h0pY8bE+!!T$tk_`a!MFtF3tU+*4j%2B|LVC|73msG|h4kf7 zQkE1GBiZVO`8p&$KP5wvzed4V(nT@ge1m`#&>%Pr8Z>LQdVxVmAJG+%XQ!J<2S*_I z8X^5dQY)mXpcvI5U=)KBp}YWGqLa~d zN|hi@k~kWRw1o6*9LEGqW^QgSBbUQKby-Y^&*wWe*z5=r5uwl1;;Mfh0+UbW($%Rx1Tj{OC9iKVF=`i;IMy1WsHc#N|cvBO$H?5=+F< zV_XTU$k8BLe2l9kxzX`#enMgtZ(Oc(88jFfS%T=)-y+IH9jXCGiWaEH&n0eLy-}|6 z6S<7m5kD@MNhZT|j`lyJ9&?fV<2-&`T+;Zs`iPd?cOALd!>!Bm$nT{&NfMV~_+4%% z2oZ%XlgCm9ChXy~**!eL#5+y0jgZoRACaX}DgjTB%JG~Mx5xEfWe!1mT{oEN)s=j5 zV&VHCtL*TsdfTLIVJz_54WGM9uM{z^)J(s9xNGt^b8a*>WAE;sxg1oN+R)xx^|*bx zxmna5UcRh0=3>G2x>~TfY-_mn$bua`6gR)CSoMAX;tn8CJ{fZLXB^3oJ+UcW7r;rq zZn12mqRraO!I#hOdLD9JGG|SrQG2+GdfDqR^~`Qx&a=F1))pJa!h7;!D`~L|TV$z< zryusQqSXboHx`9q$Pq%Z5=N@zBA_&xG=siyK}bJA6tY?DGth>7tpCM{rU_F3m&pCT zi9XefRjxJhV8e`nnEnYzj)sXSrNe!#ZN%cTBaL?A-P_?2MoX$Pjh5M6MOCMJ4h~T>?5kIXTRTr%g8no* z8ZccvU~Q{11{K!T7HqHfj@@{)^6YEk`lOzqKxI42v)2r5`g^ME(UYghg?YmGEn$?P z2j4w={^UiB45l1=-(u?t4Y5Ami!@t0R<(Qh$tIV1P!9Snn8y0cw8Z0fjYjhI&K=h} zC*ONk%Jr7}FJ-G#oq>-2PMZ1d2Ho@Pt!FD7g%{SlB}7C`q;H{=^YWy30=+8++|HWq z@WA_FnzcXFXdj&LLx!K2elcpx3W7{#uU_gR|Y2xTIjgtIiKltxpPagD$x7gB=bSG1S#W?E1pj z&wR5zOy2#X|8+afohAznt6#Zy2HNaZ(qeQ_3YxCjUbTqa1HqU(Z<;W#`{v}z!aW-| zm+gN1>W8*T$9B7yKe!)vmD=kaGS|P@?e^y0*bOhA(=b7n0+tWh`?gS|tl=}3BXik4enTvo-|(zlM)o&8qD%jpZS?ws2ayr*U`*fl&L zXkX|BYwFKkREh^}E=yzP)QZj?-?4gY>CWw$i*6j#oi`mn6=>}UrFInB-Rt`@-IrBA z46U9<{1_B$FhzLH3iK^OPj?s#&YfuLY`GNTakSjq&m_G`-}U7KZjm@Sp=|4c%>+@E t*_wMf9>?YYH zknpA<8uIWpLZc3GK&=o^aB6EiN;`uOYE{aNul`|uQaT;`fEkSsii%9_P()imRLE3C zjRvs{h#|ZL4Dw1sNJw_G@4Nk-Z|^2IVK=#(Y(iT4ImcypZ|?oh?>mp*Ip4P?8sw2x zv0^-4W$)?f~;pG9yU66eJcalXS$tsiCVkWayBC}*BlgUITbM)~MHA0a{ zgw(J~Y9LH%P^EAnMB!k#=VOjQUW2>!SMs5gJTdtczsX+fZ{S< zUW6><_cDT-J=X!Vm*{*B6;|MF^iT*vQyVKG_XCJf$^d}!X?S@XQo-+S=?dAi(}sN2 zd$R`bnGW)|`(ht$hWrR1bPN~(P5Dib>-fDr$1!BS-k;duZT7_8egwI>FAz)y%>Es* z^0+c>q?0UZRvOMD*b$(PCU-1lvL(#z%^mtm&@aZ!{=Y$a9y~{$t2Qb$9g?>IE!wY2 zwb`p+cg*laE?1+=WHw_RWRqZwW{Wf!?r>+K>|p98&;Oejo&%nelh*HvaEc1L1El(b z6b7pcg`<4M21V^)iJZaQFx#*Oh78sy*)2lkG>f3j{T;sM&cMiVU}`*`U`oL7wlm4$ zROcL>(Z6UPj)*)R_ONm)O)1KwS!EOHs?t0v8tiGV;!`8U4!h!8WF>UXo$j_slD$ zyKgF{iK8--x<`FOJH5QVmR7&Nm%;$k0*YfFzGvHPNt{sETfUh-lNAj-Rh;^tlUQHK0HV^Sc8Ii)YBR`@=IK0 zCRi*#?%{)IwZmk60o5}La8>7GZqM{KX-0-s<2h{c|!h1H;n>2-0=IGk~Vjv zw$6i@sl+OD<>PXXlU3q|JYtOS2x{ zNXPNa2(K!#io`Av>b+6KI{6Wlk$xSTu1h;DKx<=h>nUG>A~pmZ!>I1SAQHTaXGl%Y|UZLGS_&yf;oAPYZ9kgl?EJF5$htYiy@$ z?|Ylt;QN&i&7h}V-9{d+O#5&-f18hdXF6h?r2=lR#Vnv)w6QQ5o74E}vswAEqOU$V zE1~ko0MO#)pU~Ew$4EhLw`JL3s+Mj6iw!pd074x>`lR-x0BQOaW9j*arqj4CR@XVk z=cd!kzrLO}e^F0&+*nAJ|JY6KU8E=BVBLHz9ZZw+HJv|=1;N_|jkH^h=UbS52#DTx z{lzrrnu&2Qsos5p%I|xNK8MM(kJ|Fmg>}V?)+V?fr#ZeR4gOtI*Fe*L`Ifjxyisx0 z1X}*9YsJ6EWIH-pWv>Ie2_1Dh#{ifq0B{)R`rL?MsGh#7EbjL;wWsK|$KNME=4Bt1 zo>U#dljOe%;NN_YL?FaSVzx^mqg_XxjzVX%*0&d}Yi?i~rMDrEpBBt1rqMa)2EF5_ z-86s67CcQN+ekpI|DZ(*19&@y=7kw z!DsKexis$g4?kB;9sZCYEd$~5Ou-do6+fQ!z_O1Ma|KZ4P{>$Mly#K43a%NfaftxH zDjS|y*c+tFE*e1>jmtdO@4t=)F`0>!l!BHKEJ4b&(WkqP)5l+(NI;ep3L=x33~rg| zbS>e36^Z9qjQ8$UtZv0s-2)%z^gZ96p=aONL5gBYDxUOJ(5RjuR@qL)6I^kx$#q4m zRTV{-5i8c|TEqYpiVv;EiXNt(jCLawer!bz)gC!RwTDkrD^g;#wnskBN~2T_PFq1R zz17cDP;P?zN^;cFcxOp60zj9pg$zKB_|TyLhXYX_{`$LhVx?7>Ij1#FbE#}-#EKjr zQtdN$UrpCsGK$*qes6sX?S-Y)ecJ-?n`zJCX2C860KTvxSJvs8!&f__DaB;ae>Zk;hC1 zQ&qAn%qcSxR0ZQOIzj%M4iq_IM0Uw$rmX+~gdb_qL9#_Alf^tB@O8R6jL_^>J9@OK) zfk1CBfubxWC}$^6$fEo)=_rjI^hVWwssSg6u4Kv#O<0GpGJn+C82kXza#*t(Bc;4f zS2xd$kEP^^6k=#~q6q)(saZ7p>i9)aEh6(HmDOa!WKz{m=Q9E!q~3sbXu^Vd6aw(e z3PuQp{{F86B-@Mut%f^UV>AB+pUEjQcjee~zJGsPJxPjjH`D35kC(G9N^nAkZ(v0#Y-;tz{qBm3 zN6_49lPC;k7zwK>Sb9{Rc-9S5#>Z8@{!|<7*w;X2kTS!yOqZ^848T_LA$-f|ggFj0 zwj|zk{qv$}--5#QU;MhfE1y5G$abwIrF zRWhS59TjEf&rzrSi+h^twTE?;k!CTV=`Y7qV{J!HFZ9&fRu~_`Kb3Il!fl zbM@H{=UoFId|yP~kSKi#Q8K4=TmQ-13g})>w>^AqyXzU}D6q4-+~IU}iqJ zp4NTzP0E$u0-v9;=$~TclU1H|33O$xO8q(GZg;U?G

    %7NS~G*f!(@d`3%FeL*HrU1g**>9fbYC&+A)qTA#1 zecx>I=ymBfQgT3o3m4vqL~Qu!4TW_7t(Q{q#H^(5ap-s(y|QVKXuWY_8j=GN8UQId zA|uWQwfV&nnE__Y_F%?-<&=_ zM`Sb*Qg%p6_@6HTAc7RZL;^YvV1r3>>hmCW#0D#EMqzW_4oT@iSU`htOipld?j(W1 z@u?0GY|SQ7Ux*)6$iv{6oDPJ)kajq_uM-%Sj%c#4g&`4KL`!(mg=1J1BeUrhm&!DxG;Y@8wZFB l^T*S@05Yt9KK~y91^}vJLv;_zG)4db002ovPDHLkV1fj#7X2!-znW0^;VXF;uWiSZMu&HL0rAEnI-(gPX(#*slgqk8{586nZ zOV{0OPO{R;V_ZTCQHSJyNww9+rcpG!XSCIxKThZOyzle=zTfBb`FyW`yy;t5-KM!vzos{Gli?_?rXEKdQ#{Dd+-2n0%akzg>$6UOan z0li=_xPv%^tzt7b(*!aJp0|L(t0i&+8wT_8QOkLP-JlBKgOOsXH*VsF76*uh-ni|q zOd?ZG2cyIr;uYX`@vIO*{BD7V5a+WF@KVzZ0uoTg1JsgesgkDl#=Y028SV>i0uFc& zQSJ7|{h1V-xdouh6d>S=r#K6UBoaWS;z=&9R4T~{AQMSs0@3hLok=brLoXCD5l$=IafIO8<5h9aCf9&FxD49y8jFQO# z`gc@d4U;DjOBWp9yk{_(v_Pp+#ghuaK!!KYpn(^Qg)}PBaNGkZ{_Yo4XW{y3aSg_&)d!`9eODM3`xtkRIShu#4rKU+ zsC)T(2=alGxqM$Z#@GqqH_rY7X^BWm#YB8P%-lCqm`946PKCNFW6%-K&Dfr>JPgcu zaRWLvfEy|wy>}E^jgNo|PacP$*W)%&X!(Qr!Ykvw$>XK2P#tn!KLg)3H*?1dArb{> zWJAdmb^a}~{@t5GBVhlz>UXblQm{`wE$9VY(Q*@PW$k2C-}bTyBj&J> zoj6RQ=N%=dVSG#*8jrkr314JhCok$*mNo>F<06GZ;pEhm?ldW~u4xFur=z!K+Vis9W>V*gss^(-#YnnET>L^6&+47T4PJoA=9pEFlkrHiS%ZTBieh1u;A0s;JAqgHusE$yx5or#(e`;K)@P%;*iABJ5wpp6~Zb+6yGA7;%o zJ%iZN2n)cBP5$G|q47}HXy4zExn$lFG$U#D)e*b+dmb8DN2_jb;9c3>ZK4en{oJ%g zbnOM155&11XIg5JJOAL9ur|Qav6(C6*H4<}P19F#jI7^g*|o5igk$hNe|(=(aHw!( z(2}$2{G+pH-yV4&OSy`-w_nDdG8Bm!tVx8oGBVNQs4}=1p*exVVYk^P-}>kCdWt4e zS&ywrn!g1^acT}Wc0O!8f?~5{Dk_O`xty50ErhJs&*Y3u3~ABPwa6EP1%>GF>S**R zX;VsNIceX_R94;3!ewwbQ%CsKiXXnd-{p8BC=r@Zv-5C0YKL2!QGDTQ8n2=ESF{!3 zl&NSc8O`@{LUXipBK38LlOuB%3ekl%^L+PC+uBLp1DS?1ae5GOsP1{z(EQMK2o^jG z+eNyr&GOs0<7C|fpZy~(m&(xomnKO1Io%KYi^HMN8G6q(8da^8|VI8Pw z!*uq|jr0Z8aC~#Z*z8)KvEw~fc*32Isu?4=+iHhyXkH*tm!Geny<2dp8D?C;aV9ZM zw!W@@P!qJ%H)_Tt)7(D8VZ~8DbG}Nz=fl!Sykb8pKD=t{dY}_yg*n4}VhoNLhaJ{N zEi!4GT-LE_!{M#CuYKQ0H{*zDJ!hQOtf~!JA&y<3SH)e0#pOI}KvpY(r3O zdZLF5!82yN(o1qr6cm8%t^K=Q^7#iiLMS$^CH?I3o}~xp?N;|9R+a|^_WTrTU$Q-M z(|yOQ^cC8~B9Eu}`s%u%`WDU6+Zh?J%5UEZ&AHF&{I#m8a&6g{sT-&k4k^v(R~(PI2;XS4i5!?8Tf z@z%Rpqc1GO2`!_8wn}Yt5E;yl-}OnllINXc8_nu z@KxbaNe^%4h(bav*_Dw^a=_CRk!FOl1ah*k1#j+3?MF7`NW)8Q4m1_jZel5a&&6io zic8zqPY3+qfBNaJ2>^Xk=*nyjjm)$Q)>dd)G-~6p&-HuarrQ`h;!>du7H&KBd_iD9C`{PrS zPkry6eB;PtlXSJjKjeoXXTD(VYgz!f#v$#^Cwq)GF*-$tmHBcSOH1`!5cqb>ufx=7wB6u;^M} zNADM$uoqQb?nu|*uLl=iX$F-3_DTEMIIv9~7@ z74+!I%GNeYV^fpT0TT9AXjt6`R*Ago@8b)4{vEmf2T)KFkgck#%}9uApYHPPpX%`J gjX1Qw`ODMO@N9O!57-owweZgt=+9zYUe8Va50e~Q!vFvP diff --git a/shell_integration/icons/nopadding/warning.iconset/icon_128x128.png b/shell_integration/icons/nopadding/warning.iconset/icon_128x128.png new file mode 100755 index 0000000000000000000000000000000000000000..e3bd13c9263cf224e078efe630775f51fa0f3d8e GIT binary patch literal 6039 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 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/warning.iconset/icon_16x16.png b/shell_integration/icons/nopadding/warning.iconset/icon_16x16.png new file mode 100755 index 0000000000000000000000000000000000000000..32711ed098541e80364c802fec0340a5db4f41d4 GIT binary patch literal 523 zcmV+m0`&cfP))lQd0JZLLM5 ziXRpfj_vabX~ z!@jfVbRa&n7!FBZp{H@9-lC_w&rWaXz(`IAxRLc1>&*b18PWw5dxhVt+8w7h=KFZixdgIX}Sm+;NlwjG89(NCi2 zwY*)Z1(Qm)69NW?Z*~ro+BJ9I5g4!nrtKUU)NGbreOF+9*v_wVN)U8&@_NTJw-N#? zQ6I@ljVfQ7x&5~3tNqGILP*?RsDUcIW-l%8Ca@9`W`<SWz~a|2s^7InLh#3;?Ox+u`lKRVV-e N002ovPDHLkV1jZ~>k$9| literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/warning.iconset/icon_16x16@2x.png b/shell_integration/icons/nopadding/warning.iconset/icon_16x16@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..c09f8558ff280a147342cf0a51214cd3c7279697 GIT binary patch literal 1082 zcmV-A1jYM_P)sLt-?t-~;GNx5l{g1GqBAmByuO*=TfQjIWItwI(1YzMq65<YSmD2X%o)B8r zqiZo9My;H@&EGfAUd&9Y#MKnQU0$!akT2LrLybPGwJl&aG@Dcx@)9TG{Dz^FN?0@< z&rs~kTqZG<_39q&j%CU7oXE(M5@2whu8Ye&%M%+qL!zT6BmRCai>zTjV+}*II zYfodcYMweNHZ4)ghgv?FrLxwn`hHW*+PeWF-;tadCot;WXm=-7616@K_U zdW}8LvB3Zf%u)i_=h$j0tKAF;noWMM@WY3DwG0ffz;qHoheMOWK3CaVKEF<>pOp!~ zhkGgp7MNfI+J_FO+GI?I%>5j%@WW>+CIA+gU<>qiv{%*U!abj?Y_h_i%O;$Ass<+5 zs6ZIK-Hq8)Simb?#h7P?1x_^_EbcPMafg#--Hyk$94x5@3Sujy!LMpBG&!{$~O5*f8Z%La~-$F@f{kRRsc)W>e#3TQJ5xcVIBV7Em$} z6Yk14$4Bj0SrdvmK6gj8gYDdWC?5hEobKEU53~4O+W#AFbOI zq2nh99G|LA1Pjd4aSVwB^$G!LD@?@ZPHdD55*|xgI|0N71FWJfEBy=78R``RvR0r# zNTbougQaVXR|IQkj@a`G`OH6w(o8x}FCOaDuxckCOg7C-yrkWSU4% AssI20 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/warning.iconset/icon_256x256.png b/shell_integration/icons/nopadding/warning.iconset/icon_256x256.png new file mode 100755 index 0000000000000000000000000000000000000000..f43f5fe171b749ccccab43c6010168eb1c4749ce GIT binary patch literal 12859 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 literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/warning.iconset/icon_256x256@2x.png b/shell_integration/icons/nopadding/warning.iconset/icon_256x256@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..e6c5ad1e848f543057ecc5f3e8fc7a784bac329c GIT binary patch literal 27637 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 bjV2TUuwg1U``r>~0Q_mF=>Av#)HeKobGYLK literal 0 HcmV?d00001 diff --git a/shell_integration/icons/nopadding/warning.iconset/icon_32x32.png b/shell_integration/icons/nopadding/warning.iconset/icon_32x32.png old mode 100644 new mode 100755 index 8766b93c012aecc5a71328d7de001c2b11f11c00..c09f8558ff280a147342cf0a51214cd3c7279697 GIT binary patch delta 1032 zcmV+j1o!)h4!Q`C90dS#dV2JcAt-;`Nkl9HVl=Yg1L#V(#<=nWxH86-#-(f7Xmn$YuZHI zW7V~hp3T$C$Mw{I`X2erddHVdCu!hXFNF^#$-hFcn#9QSDf{E_^cX*m&f=OV)eQ>{ z&m3i6?77CrvedQjbO{dg`Gd40a>}{4m?1V8U=@9$SbD5>EI6&k#`Ngec5@Ye_&j=z zJvcjLsCY*b!1}503 zKp4Hop;Dg>Bd3mku`#ZyhyOus?t)SuH9 zV>94x5@3Sujy!LMpBG&!{$~O5*f8Z%La~-$F@f{kRRsc)W>e#3TQJ5xcVIBV7Em$} z6Yk14$4Bj0SrdvmK6gj8gHubpKYrbKTyPCbyym(hY1>;3hO2WRx58=r$QX&G}&Ls{G6X6KnzX z3IPcXry5}Vn@#VBH&Xb(Rob=x3Xj_ypQ?ccCfLq4gnEU5goaZJFf6F=`=1y#s3lbhnSvz5Y(a(dWYm8R}YiEww^9uRQKZ(*zI!`Yi>eR4mCm&2U%}l(c-G}6&$8k+j>QrDQD5PJat0000QL5t6df*fRJA&YW6e}Wz?FPa2kJ6pp+28lgd+$4Uc2{i7 z>SGOjksl-EI~G9D`t~ zV#dd$)XG>;hM+LWV{(IZYzPARd?pmk?pZZWqYLkfa4M}CrKMlrg)B$ zDKq_h4QCy+8Axy8Umld1rF6<3a7~l_YAK$dGctg;h+VjP?uTJlSz)ogxT~WBrvfCo zzu>mI@YIyluJ#|+;H;yuuEdJ#7yX=mti^peJ}|h_Idn!se@F3zlquD9=ilIXJ{2Ez zB?~XS9QG{$7_{kgwrda39M`U0>(u|U$<|ZUjXx;k;gX-K1|@;Jw=ao2QXSr4jU$Yu zfzFP`MB2gNerSuh_KL%K%jsxok{|IE3M-p}0|N!lw&DnCgU6BEp)HX|Nay-C(0gL? z>JIPOQz=zz$z*Tu1upgy?V3yPulF@&Zgqy z72%Ho67yI+HBnMqvcNLbTIMa^(R5(*BOC`(FIB9==q1fEn!2rzE)1!psd&sa+ybg& zr?jBrfV8_W{T_a6ajz>sJ8=+qrf$MB$}4lEC_qxAXR$k{r2j@bHJz3LR2A%)U+UN` zqIDXQzv1534BS=e1b;ly&U-gFgqK}im)9Pe+tqXbG(u}V@vKTArDOosx}_r}k2bX} zjN7=VRFXf+&B+|eBeD3>5wC%-CzlVikp3TrQ zxnmv&^{Ch1NSe;|^(XuJ&34w>`}4C$9>3mL|2$dDvywvJ1Ywn@F1OoGVVIDYWvk4} x$KT`6?>LXId%N&mL&mLr$Ipl5z&5W0DscKZdafwBa*q4o6crI8|8Z4P(cdE+%~t>b diff --git a/shell_integration/icons/nopadding/warning.iconset/icon_32x32@2x.png b/shell_integration/icons/nopadding/warning.iconset/icon_32x32@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..082881a2535c2ded3c8294836cc1544f9fa706c0 GIT binary patch literal 2582 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 Date: Tue, 16 Jun 2015 15:29:43 +0200 Subject: [PATCH 04/14] shell_i: Don't load the old extension in >= 10.10 #2340 --- src/gui/owncloudgui.cpp | 46 ++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 087d82991..17a899480 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -107,29 +107,33 @@ ownCloudGui::ownCloudGui(Application *parent) : void ownCloudGui::setupOverlayIcons() { #ifdef Q_OS_MAC - const QLatin1String finderExtension("/Library/ScriptingAdditions/SyncStateFinder.osax"); - if(QFile::exists(finderExtension) ) { - QString aScript = QString::fromUtf8("tell application \"Finder\"\n" - " try\n" - " «event OWNCload»\n" - " end try\n" - "end tell\n"); + // Make sure that we only send the load event to the legacy plugin when + // using OS X <= 10.9 since 10.10 starts using the new FinderSync one. + if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_10) { + const QLatin1String finderExtension("/Library/ScriptingAdditions/SyncStateFinder.osax"); + if (QFile::exists(finderExtension)) { + QString aScript = QString::fromUtf8("tell application \"Finder\"\n" + " try\n" + " «event OWNCload»\n" + " end try\n" + "end tell\n"); - QString osascript = "/usr/bin/osascript"; - QStringList processArguments; - // processArguments << "-l" << "AppleScript"; + QString osascript = "/usr/bin/osascript"; + QStringList processArguments; + // processArguments << "-l" << "AppleScript"; - QProcess p; - p.start(osascript, processArguments); - p.write(aScript.toUtf8()); - p.closeWriteChannel(); - p.waitForReadyRead(-1); - QByteArray result = p.readAll(); - QString resultAsString(result); // if appropriate - qDebug() << "Laod Finder Overlay-Plugin: " << resultAsString << ": " << p.exitCode() - << (p.exitCode() != 0 ? p.errorString() : QString::null); - } else { - qDebug() << finderExtension << "does not exist! Finder Overlay Plugin loading failed"; + QProcess p; + p.start(osascript, processArguments); + p.write(aScript.toUtf8()); + p.closeWriteChannel(); + p.waitForReadyRead(-1); + QByteArray result = p.readAll(); + QString resultAsString(result); // if appropriate + qDebug() << "Laod Finder Overlay-Plugin: " << resultAsString << ": " << p.exitCode() + << (p.exitCode() != 0 ? p.errorString() : QString::null); + } else { + qDebug() << finderExtension << "does not exist! Finder Overlay Plugin loading failed"; + } } #endif } From db38bf4a0c9666212a6416221f4700f93366f0e1 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Tue, 16 Jun 2015 16:03:38 +0200 Subject: [PATCH 05/14] shell_i: Avoid always rebuilding SyncStateFinder.osax #2482 The archive buildaction causes this. Use the default build while forcing the Release configuration instead. In both cases the result will end up in SYMROOT. --- shell_integration/MacOSX/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell_integration/MacOSX/CMakeLists.txt b/shell_integration/MacOSX/CMakeLists.txt index b18bf080a..d36647fbf 100644 --- a/shell_integration/MacOSX/CMakeLists.txt +++ b/shell_integration/MacOSX/CMakeLists.txt @@ -2,7 +2,7 @@ if(APPLE) add_custom_target( legacy_mac_overlayplugin ALL xcodebuild -workspace ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/OwnCloud.xcworkspace - -scheme SyncStateFinder.osax SYMROOT=${CMAKE_CURRENT_BINARY_DIR} archive + -scheme SyncStateFinder.osax -configuration Release SYMROOT=${CMAKE_CURRENT_BINARY_DIR} COMMENT building Legacy Mac Overlay icons) # The bundle identifier and application group need to have compatible values with the client From a4f519eaeb7b5bf652c2308fc02817d92585e056 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Tue, 16 Jun 2015 16:25:18 +0200 Subject: [PATCH 06/14] Reduce the amount of rebuild needed after a git commit Since GIT_SHA1 would need to be updated in config.h, all files including it would be rebuilt by make. Reduce the number of files to rebuild by moving this variable to version.h instead. --- config.h.in | 1 - src/cmd/cmd.cpp | 1 - src/gui/updater/ocupdater.cpp | 1 - src/libsync/version.h.in | 2 ++ 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/config.h.in b/config.h.in index 601dc6be3..1f2e145ef 100644 --- a/config.h.in +++ b/config.h.in @@ -7,7 +7,6 @@ #cmakedefine CRASHREPORTER_EXECUTABLE "@CRASHREPORTER_EXECUTABLE@" -#cmakedefine GIT_SHA1 "@GIT_SHA1@" #cmakedefine APPLICATION_DOMAIN @APPLICATION_DOMAIN@ #cmakedefine THEME_CLASS @THEME_CLASS@ #cmakedefine THEME_INCLUDE @THEME_INCLUDE@ diff --git a/src/cmd/cmd.cpp b/src/cmd/cmd.cpp index 0d6d57852..d24418d83 100644 --- a/src/cmd/cmd.cpp +++ b/src/cmd/cmd.cpp @@ -34,7 +34,6 @@ #include "theme.h" #include "netrcparser.h" -#include "version.h" #include "config.h" #ifdef Q_OS_WIN32 diff --git a/src/gui/updater/ocupdater.cpp b/src/gui/updater/ocupdater.cpp index 04c2ef5c5..d201fcf8b 100644 --- a/src/gui/updater/ocupdater.cpp +++ b/src/gui/updater/ocupdater.cpp @@ -13,7 +13,6 @@ */ #include "theme.h" -#include "version.h" #include "configfile.h" #include "utility.h" #include "accessmanager.h" diff --git a/src/libsync/version.h.in b/src/libsync/version.h.in index 29740f11a..c05c224c1 100644 --- a/src/libsync/version.h.in +++ b/src/libsync/version.h.in @@ -15,6 +15,8 @@ #ifndef VERSION_H #define VERSION_H +#cmakedefine GIT_SHA1 "@GIT_SHA1@" + #define MIRALL_STRINGIFY(s) MIRALL_TOSTRING(s) #define MIRALL_TOSTRING(s) #s From c98bcc8e9e4e14f7288d5be09701cf96f932f29a Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Tue, 16 Jun 2015 16:37:04 +0200 Subject: [PATCH 07/14] OS X: Fix a few warnings --- src/gui/folder.cpp | 1 + src/gui/folderwatcher_mac.cpp | 6 +++++- src/libsync/utility.cpp | 1 + src/libsync/utility_mac.cpp | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index f51bfbbc1..382a9cdde 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -553,6 +553,7 @@ void Folder::slotWatchedPathChanged(const QString& path) // the sync is doing to filter out our own changes. bool ownChange = false; #ifdef Q_OS_MAC + Q_UNUSED(path) // On OSX the folder watcher does not report changes done by our // own process. Therefore nothing needs to be done here! #else diff --git a/src/gui/folderwatcher_mac.cpp b/src/gui/folderwatcher_mac.cpp index 99077542a..9a165387f 100644 --- a/src/gui/folderwatcher_mac.cpp +++ b/src/gui/folderwatcher_mac.cpp @@ -47,11 +47,15 @@ static void callback( const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) { + Q_UNUSED(streamRef) + Q_UNUSED(eventFlags) + Q_UNUSED(eventIds) + qDebug() << "FolderWatcherPrivate::callback by OS X"; QStringList paths; CFArrayRef eventPaths = (CFArrayRef)eventPathsVoid; - for (int i = 0; i < numEvents; ++i) { + for (int i = 0; i < static_cast(numEvents); ++i) { CFStringRef path = reinterpret_cast(CFArrayGetValueAtIndex(eventPaths, i)); QString qstring; diff --git a/src/libsync/utility.cpp b/src/libsync/utility.cpp index 42da7d6c3..618291140 100644 --- a/src/libsync/utility.cpp +++ b/src/libsync/utility.cpp @@ -183,6 +183,7 @@ void Utility::setLaunchOnStartup(const QString &appName, const QString& guiName, qint64 Utility::freeDiskSpace(const QString &path, bool *ok) { #if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) || defined(Q_OS_FREEBSD_KERNEL) || defined(Q_OS_NETBSD) + Q_UNUSED(ok) struct statvfs stat; statvfs(path.toUtf8().data(), &stat); return (qint64) stat.f_bavail * stat.f_frsize; diff --git a/src/libsync/utility_mac.cpp b/src/libsync/utility_mac.cpp index d8f87eaf8..2397f7cc8 100644 --- a/src/libsync/utility_mac.cpp +++ b/src/libsync/utility_mac.cpp @@ -69,6 +69,7 @@ bool hasLaunchOnStartup_private(const QString &) void setLaunchOnStartup_private(const QString &appName, const QString& guiName, bool enable) { + Q_UNUSED(appName) Q_UNUSED(guiName) QString filePath = QDir(QCoreApplication::applicationDirPath()+QLatin1String("/../..")).absolutePath(); CFStringRef folderCFStr = CFStringCreateWithCString(0, filePath.toUtf8().data(), kCFStringEncodingUTF8); From e111e11dabc965215591e498648610776983047e Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Tue, 16 Jun 2015 18:22:52 +0200 Subject: [PATCH 08/14] shell_i: Add english labels --- .../FinderSyncExt/FinderSync.m | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m index fc67e3e5a..fc4a687cc 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m @@ -33,16 +33,16 @@ NSImage *warning = [extBundle imageForResource:@"warning.icns"]; NSImage *error = [extBundle imageForResource:@"error.icns"]; - [syncController setBadgeImage:ok label:nil forBadgeIdentifier:@"OK"]; - [syncController setBadgeImage:sync label:nil forBadgeIdentifier:@"SYNC"]; - [syncController setBadgeImage:sync label:nil forBadgeIdentifier:@"NEW"]; - [syncController setBadgeImage:warning label:nil forBadgeIdentifier:@"IGNORE"]; - [syncController setBadgeImage:error label:nil forBadgeIdentifier:@"ERROR"]; - [syncController setBadgeImage:ok_swm label:nil forBadgeIdentifier:@"OK+SWM"]; - [syncController setBadgeImage:sync label:nil forBadgeIdentifier:@"SYNC+SWM"]; - [syncController setBadgeImage:sync label:nil forBadgeIdentifier:@"NEW+SWM"]; - [syncController setBadgeImage:warning label:nil forBadgeIdentifier:@"IGNORE+SWM"]; - [syncController setBadgeImage:error label:nil forBadgeIdentifier:@"ERROR+SWM"]; + [syncController setBadgeImage:ok label:@"Up to date" forBadgeIdentifier:@"OK"]; + [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC"]; + [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW"]; + [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE"]; + [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR"]; + [syncController setBadgeImage:ok_swm label:@"Shared" forBadgeIdentifier:@"OK+SWM"]; + [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC+SWM"]; + [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW+SWM"]; + [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE+SWM"]; + [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR+SWM"]; // The Mach post name needs to be prefixed with the code signing Team ID // https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24 From 85938ab1f15c78d86aa80ba1853674810a3afe9e Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Wed, 17 Jun 2015 16:08:57 +0200 Subject: [PATCH 09/14] shell_i: Remove the ICON_PATH socket API message #2340 It was only used on OS X and couldn't be used by the FinderSync extension since that one runs in a sandbox. So use the same system to load images in the legacy extension by shipping them in the extension bundle instead of the owncloud.app bundle. This is also given that the legacy extension needs padded icons while the FinderSync one needs unpadded icons. --- .../MacOSX/OwnCloudFinder/ContentManager.h | 2 +- .../MacOSX/OwnCloudFinder/ContentManager.m | 23 +++++---- .../MacOSX/OwnCloudFinder/IconCache.h | 4 +- .../MacOSX/OwnCloudFinder/IconCache.m | 50 ++----------------- .../OwnCloudFinder.xcodeproj/project.pbxproj | 38 ++++++++++++-- .../MacOSX/OwnCloudFinder/RequestManager.m | 5 -- .../FinderSyncExt/FinderSync.m | 7 +-- .../MacOSX/common/SyncClientProxy.m | 4 -- shell_integration/icons/CMakeLists.txt | 12 ----- src/gui/socketapi.cpp | 10 ---- 10 files changed, 53 insertions(+), 102 deletions(-) diff --git a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.h b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.h index a3956adc4..763226bbc 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.h +++ b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.h @@ -42,6 +42,6 @@ - (void)reFetchFileNameCacheForPath:(NSString*)path; - (void)repaintAllWindows; -- (void)loadIconResourcePath:(NSString*)path; +- (void)loadIconResources; @end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m index 94d90efec..3a9660d33 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m +++ b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m @@ -33,6 +33,7 @@ static ContentManager* sharedInstance = nil; _oldFileNamesCache = [[NSMutableDictionary alloc] init]; _fileIconsEnabled = TRUE; _hasChangedContent = TRUE; + [self loadIconResources]; } return self; @@ -61,20 +62,20 @@ static ContentManager* sharedInstance = nil; return sharedInstance; } -- (void)loadIconResourcePath:(NSString*)path +- (void)loadIconResources { - NSString *base = path; + NSBundle *extBundle = [NSBundle bundleForClass:[self class]]; - _icnOk = [[IconCache sharedInstance] registerIcon:[base stringByAppendingPathComponent:@"ok.icns"]]; - _icnSync = [[IconCache sharedInstance] registerIcon:[base stringByAppendingPathComponent:@"sync.icns"]]; - _icnWarn = [[IconCache sharedInstance] registerIcon:[base stringByAppendingPathComponent:@"warning.icns"]]; - _icnErr = [[IconCache sharedInstance] registerIcon:[base stringByAppendingPathComponent:@"error.icns"]]; - _icnOkSwm = [[IconCache sharedInstance] registerIcon:[base stringByAppendingPathComponent:@"ok_swm.icns"]]; - _icnSyncSwm = [[IconCache sharedInstance] registerIcon:[base stringByAppendingPathComponent:@"sync_swm.icns"]]; - _icnWarnSwm = [[IconCache sharedInstance] registerIcon:[base stringByAppendingPathComponent:@"warning_swm.icns"]]; - _icnErrSwm = [[IconCache sharedInstance] registerIcon:[base stringByAppendingPathComponent:@"error_swm.icns"]]; + _icnOk = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"ok.icns"]]; + _icnSync = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"sync.icns"]]; + _icnWarn = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"warning.icns"]]; + _icnErr = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"error.icns"]]; + _icnOkSwm = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"ok_swm.icns"]]; + _icnSyncSwm = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"sync_swm.icns"]]; + _icnWarnSwm = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"warning_swm.icns"]]; + _icnErrSwm = [[IconCache sharedInstance] registerIcon:[extBundle imageForResource:@"error_swm.icns"]]; - // NSLog(@"Icon ok identifier: %d from %@", [_icnOk intValue], [base stringByAppendingString:@"ok.icns"]); + // NSLog(@"Icon ok: %@ identifier: %d from bundle %@", [extBundle imageForResource:@"ok.icns"], [_icnOk intValue], extBundle); } - (void)enableFileIcons:(BOOL)enable diff --git a/shell_integration/MacOSX/OwnCloudFinder/IconCache.h b/shell_integration/MacOSX/OwnCloudFinder/IconCache.h index d07f4d161..1b451d85c 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/IconCache.h +++ b/shell_integration/MacOSX/OwnCloudFinder/IconCache.h @@ -17,13 +17,11 @@ @interface IconCache : NSObject { int _currentIconId; NSMutableDictionary* _iconIdDictionary; - NSMutableDictionary* _iconPathDictionary; } + (IconCache*)sharedInstance; - (NSImage*)getIcon:(NSNumber*)iconId; -- (NSNumber*)registerIcon:(NSString*)path; -- (void)unregisterIcon:(NSNumber*)iconId; +- (NSNumber*)registerIcon:(NSImage*)image; @end \ No newline at end of file diff --git a/shell_integration/MacOSX/OwnCloudFinder/IconCache.m b/shell_integration/MacOSX/OwnCloudFinder/IconCache.m index 3e8cd0342..f8370b5f7 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/IconCache.m +++ b/shell_integration/MacOSX/OwnCloudFinder/IconCache.m @@ -25,7 +25,6 @@ static IconCache* sharedInstance = nil; if (self) { _iconIdDictionary = [[NSMutableDictionary alloc] init]; - _iconPathDictionary = [[NSMutableDictionary alloc] init]; _currentIconId = 0; } @@ -35,7 +34,6 @@ static IconCache* sharedInstance = nil; - (void)dealloc { [_iconIdDictionary release]; - [_iconPathDictionary release]; sharedInstance = nil; [super dealloc]; @@ -60,57 +58,15 @@ static IconCache* sharedInstance = nil; return image; } -- (NSNumber*)registerIcon:(NSString*)path +- (NSNumber*)registerIcon:(NSImage*)image { - if (path == nil) - { - return [NSNumber numberWithInt:-1]; - } + _currentIconId++; - NSImage* image = [[NSImage alloc]initWithContentsOfFile:path]; - - if (image == nil) - { - NSLog(@"%@ Could not load %@", NSStringFromSelector(_cmd), path); - return [NSNumber numberWithInt:-1]; - } - - NSNumber* iconId = [_iconPathDictionary objectForKey:path]; - - if (iconId == nil) - { - _currentIconId++; - - iconId = [NSNumber numberWithInt:_currentIconId]; - - [_iconPathDictionary setObject:iconId forKey:path]; - } + NSNumber* iconId = [NSNumber numberWithInt:_currentIconId]; [_iconIdDictionary setObject:image forKey:iconId]; - [image release]; return iconId; } -- (void)unregisterIcon:(NSNumber*)iconId -{ - NSString* path = @""; - - for (NSString* key in _iconPathDictionary) - { - NSNumber* value = [_iconPathDictionary objectForKey:key]; - - if ([value isEqualToNumber:iconId]) - { - path = key; - - break; - } - } - - [_iconPathDictionary removeObjectForKey:path]; - - [_iconIdDictionary removeObjectForKey:iconId]; -} - @end diff --git a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj index cd2a61ce2..d23f22971 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj @@ -20,6 +20,14 @@ 8C99F6941622D145002D2135 /* IconCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C99F6931622D145002D2135 /* IconCache.m */; }; 8D576314048677EA00EA77CD /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */; }; 8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8D5B49A704867FD3000E48DA /* InfoPlist.strings */; }; + C220057B1B31B04C00A4FB37 /* error_swm.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005731B31B04C00A4FB37 /* error_swm.icns */; }; + C220057C1B31B04C00A4FB37 /* error.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005741B31B04C00A4FB37 /* error.icns */; }; + C220057D1B31B04C00A4FB37 /* ok_swm.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005751B31B04C00A4FB37 /* ok_swm.icns */; }; + C220057E1B31B04C00A4FB37 /* ok.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005761B31B04C00A4FB37 /* ok.icns */; }; + C220057F1B31B04C00A4FB37 /* sync_swm.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005771B31B04C00A4FB37 /* sync_swm.icns */; }; + C22005801B31B04C00A4FB37 /* sync.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005781B31B04C00A4FB37 /* sync.icns */; }; + C22005811B31B04C00A4FB37 /* warning_swm.icns in Resources */ = {isa = PBXBuildFile; fileRef = C22005791B31B04C00A4FB37 /* warning_swm.icns */; }; + C22005821B31B04C00A4FB37 /* warning.icns in Resources */ = {isa = PBXBuildFile; fileRef = C220057A1B31B04C00A4FB37 /* warning.icns */; }; C2B573831B1CD5AE00303B36 /* SyncClientProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573821B1CD5AE00303B36 /* SyncClientProxy.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; /* End PBXBuildFile section */ @@ -48,6 +56,14 @@ 8C99F6931622D145002D2135 /* IconCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IconCache.m; sourceTree = ""; }; 8D576316048677EA00EA77CD /* SyncStateFinder.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SyncStateFinder.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 8D576317048677EA00EA77CD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C22005731B31B04C00A4FB37 /* error_swm.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = error_swm.icns; path = ../../icons/icns/error_swm.icns; sourceTree = ""; }; + C22005741B31B04C00A4FB37 /* error.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = error.icns; path = ../../icons/icns/error.icns; sourceTree = ""; }; + C22005751B31B04C00A4FB37 /* ok_swm.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = ok_swm.icns; path = ../../icons/icns/ok_swm.icns; sourceTree = ""; }; + C22005761B31B04C00A4FB37 /* ok.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = ok.icns; path = ../../icons/icns/ok.icns; sourceTree = ""; }; + C22005771B31B04C00A4FB37 /* sync_swm.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = sync_swm.icns; path = ../../icons/icns/sync_swm.icns; sourceTree = ""; }; + C22005781B31B04C00A4FB37 /* sync.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = sync.icns; path = ../../icons/icns/sync.icns; sourceTree = ""; }; + C22005791B31B04C00A4FB37 /* warning_swm.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = warning_swm.icns; path = ../../icons/icns/warning_swm.icns; sourceTree = ""; }; + C220057A1B31B04C00A4FB37 /* warning.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = warning.icns; path = ../../icons/icns/warning.icns; sourceTree = ""; }; C2B573811B1CD5AE00303B36 /* SyncClientProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SyncClientProxy.h; sourceTree = ""; }; C2B573821B1CD5AE00303B36 /* SyncClientProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncClientProxy.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -94,6 +110,14 @@ 089C167CFE841241C02AAC07 /* Resources */ = { isa = PBXGroup; children = ( + C22005731B31B04C00A4FB37 /* error_swm.icns */, + C22005741B31B04C00A4FB37 /* error.icns */, + C22005751B31B04C00A4FB37 /* ok_swm.icns */, + C22005761B31B04C00A4FB37 /* ok.icns */, + C22005771B31B04C00A4FB37 /* sync_swm.icns */, + C22005781B31B04C00A4FB37 /* sync.icns */, + C22005791B31B04C00A4FB37 /* warning_swm.icns */, + C220057A1B31B04C00A4FB37 /* warning.icns */, 8D576317048677EA00EA77CD /* Info.plist */, 8D5B49A704867FD3000E48DA /* InfoPlist.strings */, ); @@ -179,7 +203,7 @@ attributes = { LastUpgradeCheck = 0460; }; - buildConfigurationList = 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "SyncStateFinder" */; + buildConfigurationList = 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "OwnCloudFinder" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 1; @@ -189,7 +213,7 @@ French, German, ); - mainGroup = 089C166AFE841209C02AAC07 /* SyncStateFinder` */; + mainGroup = 089C166AFE841209C02AAC07 /* SyncStateFinder */; projectDirPath = ""; projectRoot = ""; targets = ( @@ -203,7 +227,15 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C220057E1B31B04C00A4FB37 /* ok.icns in Resources */, + C22005821B31B04C00A4FB37 /* warning.icns in Resources */, + C220057F1B31B04C00A4FB37 /* sync_swm.icns in Resources */, + C220057C1B31B04C00A4FB37 /* error.icns in Resources */, 8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */, + C22005801B31B04C00A4FB37 /* sync.icns in Resources */, + C220057D1B31B04C00A4FB37 /* ok_swm.icns in Resources */, + C220057B1B31B04C00A4FB37 /* error_swm.icns in Resources */, + C22005811B31B04C00A4FB37 /* warning_swm.icns in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -318,7 +350,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "SyncStateFinder" */ = { + 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "OwnCloudFinder" */ = { isa = XCConfigurationList; buildConfigurations = ( 1DEB911F08733D790010E9CD /* Debug */, diff --git a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m index 6d5ae1b8c..fad200244 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m +++ b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m @@ -129,11 +129,6 @@ static RequestManager* sharedInstance = nil; _shareMenuTitle = title; } -- (void)loadIconResourcePath:(NSString*)path -{ - [[ContentManager sharedInstance] loadIconResourcePath:path]; -} - - (void)connectionDidDie { // NSLog(@"Socket DISconnected! %@", [err localizedDescription]); diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m index fc4a687cc..0ffc7c550 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m @@ -44,7 +44,7 @@ [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE+SWM"]; [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR+SWM"]; - // The Mach post name needs to be prefixed with the code signing Team ID + // The Mach port name needs to be prefixed with the code signing Team ID // https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24 NSString *serverName = [[teamIdentifierPrefix stringByAppendingString:[extBundle bundleIdentifier]] stringByReplacingOccurrencesOfString:@".FinderSyncExt" withString:@".socketApi"]; @@ -148,11 +148,6 @@ _shareMenuTitle = title; } -- (void)loadIconResourcePath:(NSString*)path -{ -#pragma unused(path) -} - - (void)connectionDidDie { _shareMenuTitle = nil; diff --git a/shell_integration/MacOSX/common/SyncClientProxy.m b/shell_integration/MacOSX/common/SyncClientProxy.m index aab9b7510..b801e0084 100644 --- a/shell_integration/MacOSX/common/SyncClientProxy.m +++ b/shell_integration/MacOSX/common/SyncClientProxy.m @@ -119,10 +119,6 @@ } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"UNREGISTER_PATH"] ) { NSString *path = [chunks objectAtIndex:1]; [_delegate unregisterPath:path]; - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"ICON_PATH"] ) { - // FIXME: Should also go away once we move icons into the bundle - NSString *path = [chunks objectAtIndex:1]; - [_delegate loadIconResourcePath:path]; } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"SHARE_MENU_TITLE"] ) { [_delegate setShareMenuTitle:[chunks objectAtIndex:1]]; } else { diff --git a/shell_integration/icons/CMakeLists.txt b/shell_integration/icons/CMakeLists.txt index 04174d529..5e2c958df 100644 --- a/shell_integration/icons/CMakeLists.txt +++ b/shell_integration/icons/CMakeLists.txt @@ -1,12 +1,3 @@ - -# Install the Mac icon container into the Mac Bundle. -if( BUILD_OWNCLOUD_OSX_BUNDLE AND NOT BUILD_LIBRARIES_ONLY ) - set (ICON_DIR ${OWNCLOUD_OSX_BUNDLE}/Contents/Resources/icons) - - file(GLOB mac_icons "icns/*icns") - install(FILES ${mac_icons} DESTINATION ${ICON_DIR}) -endif() - if( UNIX AND NOT APPLE ) SET(ICON_DIR ${DATADIR}/icons/hicolor) @@ -22,6 +13,3 @@ if( UNIX AND NOT APPLE ) ENDFOREACH(size) endif() - - - diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index aecb2c87e..41c506911 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -177,16 +177,6 @@ void SocketApi::slotNewConnection() _listeners.append(socket); -#ifdef Q_OS_MAC - // We want to tell our location so it can load the icons - // e.g. "/Users/guruz/woboq/owncloud/client/buildmirall/owncloud.app/Contents/MacOS/" - QString iconPath = qApp->applicationDirPath() + "/../Resources/icons/"; - if (!QDir(iconPath).exists()) { - DEBUG << "Icon path " << iconPath << " does not exist, did you forget make install?"; - } - broadcastMessage(QLatin1String("ICON_PATH"), iconPath ); -#endif - foreach( Folder *f, FolderMan::instance()->map() ) { QString message = buildRegisterPathMessage(f->path()); sendMessage(socket, message); From 03206272636c63702270ee6b4714801b15d12d29 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Fri, 19 Jun 2015 16:49:21 +0200 Subject: [PATCH 10/14] shell_i: Use a more specific name for ContentManager Since we use that class to lookup the NSBundle using bundleForClass use a more specific name to avoid any clash with any othe liferay extension. I couldn't figure out from the documentation if that is only resolved using the class name, but found some warnings on stackoverflow and better be safe than sorry for what it costs. --- .../MacOSX/OwnCloudFinder/ContentManager.h | 4 ++-- .../MacOSX/OwnCloudFinder/ContentManager.m | 6 +++--- shell_integration/MacOSX/OwnCloudFinder/FinderHook.m | 2 +- .../MacOSX/OwnCloudFinder/IconOverlayHandlers.m | 6 +++--- .../MacOSX/OwnCloudFinder/RequestManager.m | 10 +++++----- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.h b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.h index 763226bbc..82d3b52c4 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.h +++ b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.h @@ -14,7 +14,7 @@ #import -@interface ContentManager : NSObject +@interface OwnCloudFinderContentManager : NSObject { NSMutableDictionary* _fileNamesCache; NSMutableDictionary* _oldFileNamesCache; @@ -31,7 +31,7 @@ NSNumber *_icnErrSwm; } -+ (ContentManager*)sharedInstance; ++ (OwnCloudFinderContentManager*)sharedInstance; - (void)enableFileIcons:(BOOL)enable; - (NSNumber*)iconByPath:(NSString*)path isDirectory:(BOOL)isDir; diff --git a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m index 3a9660d33..73f017beb 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m +++ b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m @@ -20,9 +20,9 @@ #import "RequestManager.h" #import "IconCache.h" -static ContentManager* sharedInstance = nil; +static OwnCloudFinderContentManager* sharedInstance = nil; -@implementation ContentManager +@implementation OwnCloudFinderContentManager - init { self = [super init]; @@ -49,7 +49,7 @@ static ContentManager* sharedInstance = nil; [super dealloc]; } -+ (ContentManager*)sharedInstance ++ (OwnCloudFinderContentManager*)sharedInstance { @synchronized(self) { diff --git a/shell_integration/MacOSX/OwnCloudFinder/FinderHook.m b/shell_integration/MacOSX/OwnCloudFinder/FinderHook.m index 4c487762c..788c16e86 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/FinderHook.m +++ b/shell_integration/MacOSX/OwnCloudFinder/FinderHook.m @@ -95,7 +95,7 @@ static BOOL installed = NO; // NSLog(@"SyncStateFinder: uninstalling"); - [[ContentManager sharedInstance] dealloc]; + [[OwnCloudFinderContentManager sharedInstance] dealloc]; [[IconCache sharedInstance] dealloc]; diff --git a/shell_integration/MacOSX/OwnCloudFinder/IconOverlayHandlers.m b/shell_integration/MacOSX/OwnCloudFinder/IconOverlayHandlers.m index 0b4f414d1..7db375ea6 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/IconOverlayHandlers.m +++ b/shell_integration/MacOSX/OwnCloudFinder/IconOverlayHandlers.m @@ -33,7 +33,7 @@ isDir = NO; } - NSNumber* imageIndex = [[ContentManager sharedInstance] iconByPath:[url path] isDirectory:isDir]; + NSNumber* imageIndex = [[OwnCloudFinderContentManager sharedInstance] iconByPath:[url path] isDirectory:isDir]; //NSLog(@"1 The icon index is %d", [imageIndex intValue]); if ([imageIndex intValue] > 0) @@ -76,7 +76,7 @@ isDir = NO; } - NSNumber* imageIndex = [[ContentManager sharedInstance] iconByPath:[url path] isDirectory:isDir]; + NSNumber* imageIndex = [[OwnCloudFinderContentManager sharedInstance] iconByPath:[url path] isDirectory:isDir]; //NSLog(@"2 The icon index is %d %@ %@", [imageIndex intValue], [url path], isDir ? @"isDir" : @""); if ([imageIndex intValue] > 0) @@ -157,7 +157,7 @@ isDir = NO; } - NSNumber* imageIndex = [[ContentManager sharedInstance] iconByPath:[url path] isDirectory:isDir]; + NSNumber* imageIndex = [[OwnCloudFinderContentManager sharedInstance] iconByPath:[url path] isDirectory:isDir]; //NSLog(@"3 The icon index is %d", [imageIndex intValue]); if ([imageIndex intValue] > 0) diff --git a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m index fad200244..ba450f423 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m +++ b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m @@ -101,27 +101,27 @@ static RequestManager* sharedInstance = nil; { // The client will broadcast all changes, do not fill the cache for paths that Finder didn't ask for. if ([_requestedPaths containsObject:path]) { - [[ContentManager sharedInstance] setResultForPath:path result:result]; + [[OwnCloudFinderContentManager sharedInstance] setResultForPath:path result:result]; } } - (void)reFetchFileNameCacheForPath:(NSString*)path { [_requestedPaths removeAllObjects]; - [[ContentManager sharedInstance] reFetchFileNameCacheForPath:path]; + [[OwnCloudFinderContentManager sharedInstance] reFetchFileNameCacheForPath:path]; } - (void)registerPath:(NSString*)path { NSNumber *one = [NSNumber numberWithInt:1]; [_registeredPathes setObject:one forKey:path]; - [[ContentManager sharedInstance] repaintAllWindows]; + [[OwnCloudFinderContentManager sharedInstance] repaintAllWindows]; } - (void)unregisterPath:(NSString*)path { [_registeredPathes removeObjectForKey:path]; - [[ContentManager sharedInstance] repaintAllWindows]; + [[OwnCloudFinderContentManager sharedInstance] repaintAllWindows]; } - (void)setShareMenuTitle:(NSString*)title @@ -139,7 +139,7 @@ static RequestManager* sharedInstance = nil; [_requestedPaths removeAllObjects]; // clear the caches in conent manager - ContentManager *contentman = [ContentManager sharedInstance]; + OwnCloudFinderContentManager *contentman = [OwnCloudFinderContentManager sharedInstance]; [contentman clearFileNameCache]; [contentman repaintAllWindows]; } From 121577340ac8be79fdbf4332ed71c349469f6157 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Mon, 22 Jun 2015 13:54:29 +0200 Subject: [PATCH 11/14] shell_i: Cleanup and build fix Don't use absolute paths for resources --- .../OwnCloudFinderSync.xcodeproj/project.pbxproj | 10 +++++----- shell_integration/MacOSX/common/SyncClientProxy.h | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj index 3bed4ca08..80ac3abf7 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj @@ -54,11 +54,11 @@ C2B573DD1B1CD9CE00303B36 /* FinderSync.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FinderSync.m; sourceTree = ""; }; C2B573E71B1DA1FB00303B36 /* SyncClientProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SyncClientProxy.h; sourceTree = ""; }; C2B573E81B1DA1FB00303B36 /* SyncClientProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncClientProxy.m; sourceTree = ""; }; - C2B573EB1B1DAD6400303B36 /* error.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = error.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/error.iconset; sourceTree = ""; }; - C2B573EC1B1DAD6400303B36 /* ok_swm.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = ok_swm.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/ok_swm.iconset; sourceTree = ""; }; - C2B573ED1B1DAD6400303B36 /* ok.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = ok.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/ok.iconset; sourceTree = ""; }; - C2B573EF1B1DAD6400303B36 /* sync.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = sync.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/sync.iconset; sourceTree = ""; }; - C2B573F11B1DAD6400303B36 /* warning.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = warning.iconset; path = /Users/joce/dev/client/shell_integration/MacOSX/OwnCloudFinder/FinderSyncExt/../../../icons/nopadding/warning.iconset; sourceTree = ""; }; + C2B573EB1B1DAD6400303B36 /* error.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = error.iconset; path = ../../icons/nopadding/error.iconset; sourceTree = SOURCE_ROOT; }; + C2B573EC1B1DAD6400303B36 /* ok_swm.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = ok_swm.iconset; path = ../../icons/nopadding/ok_swm.iconset; sourceTree = SOURCE_ROOT; }; + C2B573ED1B1DAD6400303B36 /* ok.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = ok.iconset; path = ../../icons/nopadding/ok.iconset; sourceTree = SOURCE_ROOT; }; + C2B573EF1B1DAD6400303B36 /* sync.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = sync.iconset; path = ../../icons/nopadding/sync.iconset; sourceTree = SOURCE_ROOT; }; + C2B573F11B1DAD6400303B36 /* warning.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = warning.iconset; path = ../../icons/nopadding/warning.iconset; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ diff --git a/shell_integration/MacOSX/common/SyncClientProxy.h b/shell_integration/MacOSX/common/SyncClientProxy.h index 0e0ab475e..aaaa294b6 100644 --- a/shell_integration/MacOSX/common/SyncClientProxy.h +++ b/shell_integration/MacOSX/common/SyncClientProxy.h @@ -21,7 +21,6 @@ - (void)registerPath:(NSString*)path; - (void)unregisterPath:(NSString*)path; - (void)setShareMenuTitle:(NSString*)title; -- (void)loadIconResourcePath:(NSString*)path; - (void)connectionDidDie; @end From 9797782682c66c2c48265485e5ccd950042e8eb6 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Mon, 22 Jun 2015 13:53:05 +0200 Subject: [PATCH 12/14] shell_i: Build the extension with the '-' identity by default This allows developers to build and run the extension by default. Official packages bundles will be re-signed after the build, we The SocketApi prefix can be set at configure time through cmake and should match the key that will be used to sign the whole .app bundle (including the embedded FindexSync .appex bundle). --- CMakeLists.txt | 4 ++++ config.h.in | 2 +- shell_integration/MacOSX/CMakeLists.txt | 9 ++++++--- .../MacOSX/OwnCloudFinder/ContentManager.m | 6 +++--- .../MacOSX/OwnCloudFinder/FinderHook.m | 4 ++-- .../MacOSX/OwnCloudFinder/Info.plist | 2 ++ .../MacOSX/OwnCloudFinder/MenuManager.m | 4 ++-- .../OwnCloudFinder.xcodeproj/project.pbxproj | 4 ++++ .../MacOSX/OwnCloudFinder/RequestManager.h | 4 ++-- .../MacOSX/OwnCloudFinder/RequestManager.m | 17 ++++++++++------- .../FinderSyncExt/FinderSync.m | 16 ++++++++++++---- .../FinderSyncExt/FinderSyncExt.entitlements | 2 +- .../OwnCloudFinderSync/FinderSyncExt/Info.plist | 4 ++-- .../project.pbxproj | 14 ++++++-------- src/gui/socketapi.cpp | 10 +++++----- 15 files changed, 62 insertions(+), 40 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b4ea5569f..7d7ac5ac9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,10 @@ if(OWNCLOUD_5XX_NO_BLACKLIST) add_definitions(-DOWNCLOUD_5XX_NO_BLACKLIST=1) endif() +if(APPLE) + set( SOCKETAPI_TEAM_IDENTIFIER_PREFIX "" CACHE STRING "SocketApi prefix (including a following dot) that must match the codesigh key's TeamIdentifier/Organizational Unit" ) +endif() + #### find libs #find_package(Qt4 4.7.0 COMPONENTS QtCore QtGui QtXml QtNetwork QtTest QtWebkit REQUIRED ) #if( UNIX AND NOT APPLE ) # Fdo notifications diff --git a/config.h.in b/config.h.in index 1f2e145ef..a3e04ed75 100644 --- a/config.h.in +++ b/config.h.in @@ -5,7 +5,7 @@ #cmakedefine WITH_QTKEYCHAIN 1 #cmakedefine WITH_CRASHREPORTER #cmakedefine CRASHREPORTER_EXECUTABLE "@CRASHREPORTER_EXECUTABLE@" - +#define SOCKETAPI_TEAM_IDENTIFIER_PREFIX "@SOCKETAPI_TEAM_IDENTIFIER_PREFIX@" #cmakedefine APPLICATION_DOMAIN @APPLICATION_DOMAIN@ #cmakedefine THEME_CLASS @THEME_CLASS@ diff --git a/shell_integration/MacOSX/CMakeLists.txt b/shell_integration/MacOSX/CMakeLists.txt index d36647fbf..b7d602ad3 100644 --- a/shell_integration/MacOSX/CMakeLists.txt +++ b/shell_integration/MacOSX/CMakeLists.txt @@ -1,9 +1,11 @@ if(APPLE) add_custom_target( legacy_mac_overlayplugin ALL - xcodebuild -workspace ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/OwnCloud.xcworkspace - -scheme SyncStateFinder.osax -configuration Release SYMROOT=${CMAKE_CURRENT_BINARY_DIR} - COMMENT building Legacy Mac Overlay icons) + xcodebuild -workspace ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/OwnCloud.xcworkspace + -scheme SyncStateFinder.osax -configuration Release SYMROOT=${CMAKE_CURRENT_BINARY_DIR} + OC_APPLICATION_REV_DOMAIN=${APPLICATION_REV_DOMAIN} + OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX=${SOCKETAPI_TEAM_IDENTIFIER_PREFIX} + COMMENT building Legacy Mac Overlay icons) # The bundle identifier and application group need to have compatible values with the client # to be able to open a Mach port across the extension's sandbox boundary. @@ -14,6 +16,7 @@ add_custom_target( mac_overlayplugin ALL -target FinderSyncExt -configuration Release SYMROOT=${CMAKE_CURRENT_BINARY_DIR} OC_APPLICATION_NAME=${APPLICATION_NAME} OC_APPLICATION_REV_DOMAIN=${APPLICATION_REV_DOMAIN} + OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX=${SOCKETAPI_TEAM_IDENTIFIER_PREFIX} COMMENT building Mac Overlay icons) INSTALL( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Release/SyncStateFinder.osax/Contents diff --git a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m index 73f017beb..e6bbc450e 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m +++ b/shell_integration/MacOSX/OwnCloudFinder/ContentManager.m @@ -142,7 +142,7 @@ static OwnCloudFinderContentManager* sharedInstance = nil; } NSString* normalizedPath = [path decomposedStringWithCanonicalMapping]; - if (![[RequestManager sharedInstance] isRegisteredPath:normalizedPath isDirectory:isDir]) { + if (![[OwnCloudFinderRequestManager sharedInstance] isRegisteredPath:normalizedPath isDirectory:isDir]) { return [NSNumber numberWithInt:0]; } @@ -154,7 +154,7 @@ static OwnCloudFinderContentManager* sharedInstance = nil; // Set 0 into the cache, meaning "don't have an icon, but already requested it" [_fileNamesCache setObject:result forKey:normalizedPath]; // start the async call - [[RequestManager sharedInstance] askForIcon:normalizedPath isDirectory:isDir]; + [[OwnCloudFinderRequestManager sharedInstance] askForIcon:normalizedPath isDirectory:isDir]; } if ([result intValue] == 0) { // Show the old state while we wait for the new one @@ -223,7 +223,7 @@ static OwnCloudFinderContentManager* sharedInstance = nil; } MenuManager* menuManager = [MenuManager sharedInstance]; - RequestManager* requestManager = [RequestManager sharedInstance]; + OwnCloudFinderRequestManager* requestManager = [OwnCloudFinderRequestManager sharedInstance]; if ([[window className] isEqualToString:@"TBrowserWindow"]) { diff --git a/shell_integration/MacOSX/OwnCloudFinder/FinderHook.m b/shell_integration/MacOSX/OwnCloudFinder/FinderHook.m index 788c16e86..dfb6197e6 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/FinderHook.m +++ b/shell_integration/MacOSX/OwnCloudFinder/FinderHook.m @@ -51,7 +51,7 @@ static BOOL installed = NO; // NSLog(@"SyncStateFinder: installing SyncState Shell extension"); - [RequestManager sharedInstance]; + [OwnCloudFinderRequestManager sharedInstance]; // Icons [self hookMethod:@selector(drawImage:) inClass:@"IKImageBrowserCell" toCallToTheNewMethod:@selector(OCIconOverlayHandlers_IKImageBrowserCell_drawImage:)]; // 10.7 & 10.8 & 10.9 (Icon View arrange by name) @@ -99,7 +99,7 @@ static BOOL installed = NO; [[IconCache sharedInstance] dealloc]; - [[RequestManager sharedInstance] dealloc]; + [[OwnCloudFinderRequestManager sharedInstance] dealloc]; // Icons [self hookMethod:@selector(OCIconOverlayHandlers_drawImage:) inClass:@"TIconViewCell" toCallToTheNewMethod:@selector(drawImage:)]; // 10.7 & 10.8 & 10.9 diff --git a/shell_integration/MacOSX/OwnCloudFinder/Info.plist b/shell_integration/MacOSX/OwnCloudFinder/Info.plist index a274600e1..f64f59a8c 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/Info.plist +++ b/shell_integration/MacOSX/OwnCloudFinder/Info.plist @@ -2,6 +2,8 @@ + SocketApiPrefix + $(OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX)$(OC_APPLICATION_REV_DOMAIN) CFBundleDevelopmentRegion English CFBundleExecutable diff --git a/shell_integration/MacOSX/OwnCloudFinder/MenuManager.m b/shell_integration/MacOSX/OwnCloudFinder/MenuManager.m index 740b34e29..1fd9e0a53 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/MenuManager.m +++ b/shell_integration/MacOSX/OwnCloudFinder/MenuManager.m @@ -73,7 +73,7 @@ static MenuManager* sharedInstance = nil; - (void)addItemsToMenu:(TContextMenu*)menu forFiles:(NSArray*)files { - RequestManager *requestManager = [RequestManager sharedInstance]; + OwnCloudFinderRequestManager *requestManager = [OwnCloudFinderRequestManager sharedInstance]; NSString *shareItemTitle = [requestManager shareItemTitle]; if (!shareItemTitle || shareItemTitle.length == 0) { return; @@ -157,7 +157,7 @@ static MenuManager* sharedInstance = nil; - (void)menuItemClicked:(id)param { - [[RequestManager sharedInstance] menuItemClicked:[param representedObject]]; + [[OwnCloudFinderRequestManager sharedInstance] menuItemClicked:[param representedObject]]; } - (NSArray*)pathsForNodes:(const struct TFENodeVector*)nodes diff --git a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj index d23f22971..1ef53506d 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/OwnCloudFinder/OwnCloudFinder.xcodeproj/project.pbxproj @@ -285,6 +285,8 @@ GCC_OPTIMIZATION_LEVEL = 0; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Library/Bundles"; + OC_APPLICATION_REV_DOMAIN = com.owncloud.desktopclient; + OC_TEAM_IDENTIFIER_PREFIX = ""; PRODUCT_NAME = SyncStateFinder; WRAPPER_EXTENSION = bundle; }; @@ -302,6 +304,8 @@ GCC_MODEL_TUNING = G5; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Library/Bundles"; + OC_APPLICATION_REV_DOMAIN = com.owncloud.desktopclient; + OC_TEAM_IDENTIFIER_PREFIX = ""; PRODUCT_NAME = SyncStateFinder; WRAPPER_EXTENSION = bundle; }; diff --git a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.h b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.h index 136e4fd26..f3924fac6 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.h +++ b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.h @@ -16,7 +16,7 @@ #import "RequestManager.h" #import "SyncClientProxy.h" -@interface RequestManager : NSObject +@interface OwnCloudFinderRequestManager : NSObject { SyncClientProxy *_syncClientProxy; @@ -29,7 +29,7 @@ @property (nonatomic, retain) NSString* filterFolder; -+ (RequestManager*)sharedInstance; ++ (OwnCloudFinderRequestManager*)sharedInstance; - (BOOL)isRegisteredPath:(NSString*)path isDirectory:(BOOL)isDir; - (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir; diff --git a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m index ba450f423..79be861cf 100644 --- a/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m +++ b/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m @@ -16,19 +16,22 @@ #import "IconCache.h" #import "RequestManager.h" -#define READ_TAG 2422 +static OwnCloudFinderRequestManager* sharedInstance = nil; -static RequestManager* sharedInstance = nil; - -@implementation RequestManager +@implementation OwnCloudFinderRequestManager - (id)init { if ((self = [super init])) { // For the sake of allowing both the legacy and the FinderSync extensions to work with the same - // client build, use the same server name including the Team ID even though we won't be signing the bundle. - NSString *serverName = @"9B5WD74GWJ.com.owncloud.desktopclient.socketApi"; + // client build, use the same server name including the Team ID even though we won't be sandboxed. + NSBundle *extBundle = [NSBundle bundleForClass:[self class]]; + // This was added to the bundle's Info.plist to get it from the build system + NSString *socketApiPrefix = [extBundle objectForInfoDictionaryKey:@"SocketApiPrefix"]; + NSString *serverName = [socketApiPrefix stringByAppendingString:@".socketApi"]; + // NSLog(@"OwnCloudFinderRequestManager serverName %@", serverName); + _syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName]; _registeredPathes = [[NSMutableDictionary alloc] init]; @@ -53,7 +56,7 @@ static RequestManager* sharedInstance = nil; [super dealloc]; } -+ (RequestManager*)sharedInstance ++ (OwnCloudFinderRequestManager*)sharedInstance { @synchronized(self) { diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m index 0ffc7c550..689cf750b 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m @@ -25,7 +25,7 @@ FIFinderSyncController *syncController = [FIFinderSyncController defaultController]; NSBundle *extBundle = [NSBundle bundleForClass:[self class]]; // This was added to the bundle's Info.plist to get it from the build system - NSString *teamIdentifierPrefix = [extBundle objectForInfoDictionaryKey:@"TeamIdentifierPrefix"]; + NSString *socketApiPrefix = [extBundle objectForInfoDictionaryKey:@"SocketApiPrefix"]; NSImage *ok = [extBundle imageForResource:@"ok.icns"]; NSImage *ok_swm = [extBundle imageForResource:@"ok_swm.icns"]; @@ -44,10 +44,18 @@ [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE+SWM"]; [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR+SWM"]; - // The Mach port name needs to be prefixed with the code signing Team ID + // The Mach port name needs to: + // - Be prefixed with the code signing Team ID + // - Then infixed with the sandbox App Group + // - The App Group itself must be a prefix of (or equal to) the application bundle identifier + // We end up in the official signed client with: 9B5WD74GWJ.com.owncloud.desktopclient.socketApi + // With ad-hoc signing (the '-' signing identity) we must drop the Team ID. + // When the code isn't sandboxed (e.g. the OC client or the legacy overlay icon extension) + // the OS doesn't seem to put any restriction on the port name, so we just follow what + // the sandboxed App Extension needs. // https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24 - NSString *serverName = [[teamIdentifierPrefix stringByAppendingString:[extBundle bundleIdentifier]] - stringByReplacingOccurrencesOfString:@".FinderSyncExt" withString:@".socketApi"]; + NSString *serverName = [socketApiPrefix stringByAppendingString:@".socketApi"]; + // NSLog(@"FinderSync serverName %@", serverName); _syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName]; _registeredDirectories = [[NSMutableSet alloc] init]; diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSyncExt.entitlements b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSyncExt.entitlements index 20605791a..5d2a36d31 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSyncExt.entitlements +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSyncExt.entitlements @@ -6,7 +6,7 @@ com.apple.security.application-groups - $(TeamIdentifierPrefix)$(OC_APPLICATION_REV_DOMAIN) + $(OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX)$(OC_APPLICATION_REV_DOMAIN) diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/Info.plist b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/Info.plist index 36fe63e7c..88bb87e91 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/Info.plist +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/Info.plist @@ -2,8 +2,8 @@ - TeamIdentifierPrefix - $(TeamIdentifierPrefix) + SocketApiPrefix + $(OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX)$(OC_APPLICATION_REV_DOMAIN) CFBundleDevelopmentRegion en CFBundleDisplayName diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj index 80ac3abf7..709298637 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj @@ -312,8 +312,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; @@ -362,8 +361,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -405,8 +403,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = FinderSyncExt/FinderSyncExt.entitlements; - CODE_SIGN_IDENTITY = "Developer ID Application"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; @@ -432,6 +429,7 @@ MTL_ENABLE_DEBUG_INFO = YES; OC_APPLICATION_NAME = ownCloud; OC_APPLICATION_REV_DOMAIN = com.owncloud.desktopclient; + OC_TEAM_IDENTIFIER_PREFIX = ""; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -458,8 +456,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = FinderSyncExt/FinderSyncExt.entitlements; - CODE_SIGN_IDENTITY = "Developer ID Application"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -479,6 +476,7 @@ MTL_ENABLE_DEBUG_INFO = NO; OC_APPLICATION_NAME = ownCloud; OC_APPLICATION_REV_DOMAIN = com.owncloud.desktopclient; + OC_TEAM_IDENTIFIER_PREFIX = ""; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SDKROOT = macosx; diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index 41c506911..411458ee8 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -16,6 +16,7 @@ #include "socketapi.h" +#include "config.h" #include "configfile.h" #include "folderman.h" #include "folder.h" @@ -84,11 +85,10 @@ SocketApi::SocketApi(QObject* parent) // See issue #2388 // + Theme::instance()->appName(); } else if (Utility::isMac()) { - // This much match the code signing Team setting of the extension - // FIXME: Hardcoded for now, but if we want to allow builds to be - // signed by third party Apple Developer accounts, we'll have to - // allow changing this through the build system. - socketPath = "9B5WD74GWJ." APPLICATION_REV_DOMAIN ".socketApi"; + // This must match the code signing Team setting of the extension + // Example for developer builds (with ad-hoc signing identity): "" "com.owncloud.desktopclient" ".socketApi" + // Example for official signed packages: "9B5WD74GWJ." "com.owncloud.desktopclient" ".socketApi" + socketPath = SOCKETAPI_TEAM_IDENTIFIER_PREFIX APPLICATION_REV_DOMAIN ".socketApi"; } else if( Utility::isLinux() || Utility::isBSD() ) { QString runtimeDir; #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) From 18efc5eb6550cd9a2acb27d0d96c7a9fdfca5106 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Mon, 22 Jun 2015 13:53:56 +0200 Subject: [PATCH 13/14] shell_i: Add a way to fail the build if the signing key doesn't match This tries to catch error at build time instead of having to check the OS X console for errors afterward. --- admin/osx/sign_app.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/admin/osx/sign_app.sh b/admin/osx/sign_app.sh index 22f4dffd4..b60ec6176 100755 --- a/admin/osx/sign_app.sh +++ b/admin/osx/sign_app.sh @@ -1,12 +1,18 @@ #!/bin/sh -xe -[ "$#" -lt 2 ] && echo "Usage: sign_app.sh " && exit +[ "$#" -lt 2 ] && echo "Usage: sign_app.sh " && exit src_app="$1" identity="$2" +team_identifier="$3" -codesign -s "$identity" --force --verbose=4 --deep "$src_app" +codesign -s "$identity" --force --preserve-metadata=entitlements --verbose=4 --deep "$src_app" # Verify the signature spctl -a -t exec -vv $src_app codesign -dv $src_app + +# Validate that the key used for signing the binary matches the expected TeamIdentifier +# needed to pass the SocketApi through the sandbox +codesign -dv $src_app 2>&1 | grep "TeamIdentifier=$team_identifier" +exit $? \ No newline at end of file From 360a0eeee1dc6208140364361b4abf5720181fb1 Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Mon, 29 Jun 2015 15:16:43 +0200 Subject: [PATCH 14/14] OS X overlays deploy.sh: Update output --- shell_integration/MacOSX/deploy.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shell_integration/MacOSX/deploy.sh b/shell_integration/MacOSX/deploy.sh index fde7d3406..ebeeba25e 100644 --- a/shell_integration/MacOSX/deploy.sh +++ b/shell_integration/MacOSX/deploy.sh @@ -1,4 +1,10 @@ #!/bin/sh + +echo "Not used anymore, please do (from build dir) (this is <= 10.9 only)" +echo sudo cp -r ./shell_integration/MacOSX/Release/SyncStateFinder.osax /Library/ScriptingAdditions/ +echo killall Finder +exit 1 + SELFPATH=`dirname $0` # osascript $SELFPATH/unload.scpt