- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// Created by Jonathon Staff on 10/21/14.
|
||||
// Copyright (c) 2014 nplexity, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPP.h"
|
||||
#import "XMPPModule.h"
|
||||
#import "TURNSocket.h"
|
||||
#import "GCDAsyncSocket.h"
|
||||
|
||||
@class XMPPIDTracker;
|
||||
|
||||
typedef NS_OPTIONS(uint8_t, XMPPFileTransferStreamMethod) {
|
||||
XMPPFileTransferStreamMethodBytestreams = 1 << 0, // If set, SOCKS5 connections allowed
|
||||
XMPPFileTransferStreamMethodIBB = 1 << 1, // If set, IBB connections allowed
|
||||
// XMPPFileTransferStreamMethodJingle = 1 << 2 // If set, Jingle connections allowed
|
||||
// Note that Jingle is not yet implemented
|
||||
};
|
||||
|
||||
/**
|
||||
* This class defines common elements of the file transfer process that apply to
|
||||
* both outgoing and incoming transfers.
|
||||
*
|
||||
* You'll find more detailed documentation in each of the implementation files.
|
||||
*
|
||||
* By default, the stream-method priority is as follows:
|
||||
*
|
||||
* 1. SOCKS5 Direct Connection (http://xmpp.org/extensions/xep-0065.html#direct)
|
||||
* 2. SOCKS5 Mediated (http://xmpp.org/extensions/xep-0065.html#mediated)
|
||||
* 3. IBB (http://xmpp.org/extensions/xep-0047.html)
|
||||
*/
|
||||
@interface XMPPFileTransfer : XMPPModule <GCDAsyncSocketDelegate> {
|
||||
XMPPFileTransferStreamMethod _streamMethods;
|
||||
XMPPIDTracker *_idTracker;
|
||||
GCDAsyncSocket *_asyncSocket;
|
||||
NSMutableArray *_streamhosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* The streamID ("sid") for the file transfer.
|
||||
*/
|
||||
@property (nonatomic, copy) NSString *sid;
|
||||
|
||||
/**
|
||||
* Use this to disable file transfers via direct connection.
|
||||
*
|
||||
* If set to YES, SOCKS5 transfers will only take place if there is a proxy that
|
||||
* works. If set to NO, SOCKS5 transfers will attempt a direct connection first
|
||||
* and fall back to a proxy if the direct connection doesn't work.
|
||||
*
|
||||
* The default value is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL disableDirectTransfers;
|
||||
|
||||
/**
|
||||
* Use this to disable file transfers via SOCKS5.
|
||||
*
|
||||
* If set to YES, SOCKS5 transfers will not be used. This means that the
|
||||
* recipient must support IBB transfers or the transfer will fail. If set to NO,
|
||||
* a SOCKS5 connection will be attempted first, since this should be the
|
||||
* preferred method of transfer.
|
||||
*
|
||||
* The default value is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL disableSOCKS5;
|
||||
|
||||
/**
|
||||
* Use this to disable file transfers via IBB.
|
||||
*
|
||||
* If set to YES, IBB transfers will not be used. This means that the
|
||||
* recipient must support SOCKS5 transfers or the transfer will fail. If set to
|
||||
* NO, a SOCKS5 connection will be attempted first, since this should be the
|
||||
* preferred method of transfer.
|
||||
*
|
||||
* The default value is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL disableIBB;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by Jonathon Staff on 10/21/14.
|
||||
// Copyright (c) 2014 nplexity, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
#import "XMPPFileTransfer.h"
|
||||
|
||||
|
||||
@implementation XMPPFileTransfer
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// Created by Jonathon Staff on 10/21/14.
|
||||
// Copyright (c) 2014 nplexity, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPPFileTransfer.h"
|
||||
|
||||
@class XMPPIQ;
|
||||
|
||||
@interface XMPPIncomingFileTransfer : XMPPFileTransfer
|
||||
|
||||
/**
|
||||
* (Optional)
|
||||
*
|
||||
* Specifies whether or not file transfers should automatically be accepted. If
|
||||
* set to YES, you will be notified of an incoming Stream Initiation Offer, but
|
||||
* it will be accepted for you.
|
||||
*
|
||||
* The default value is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL autoAcceptFileTransfers;
|
||||
|
||||
/**
|
||||
* Sends a response to the file transfer initiator accepting the Stream
|
||||
* Initiation offer. It will automatically determine the best transfer method
|
||||
* (either SOCKS5 or IBB) based on what the sender offers as options.
|
||||
*
|
||||
* If you've set autoAcceptFileTransfers to YES, this method will be invoked for
|
||||
* you automatically.
|
||||
*
|
||||
* @param offer IQ stanza representing the SI offer (this should be provided by
|
||||
* the delegate to you).
|
||||
*/
|
||||
- (void)acceptSIOffer:(XMPPIQ *)offer;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - XMPPIncomingFileTransferDelegate
|
||||
|
||||
@protocol XMPPIncomingFileTransferDelegate
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Implement this method to receive notifications of a failed incoming file
|
||||
* transfer.
|
||||
*
|
||||
* @param sender XMPPIncomingFileTransfer object invoking this delegate method.
|
||||
* @param error NSError containing more details of the failure.
|
||||
*/
|
||||
- (void)xmppIncomingFileTransfer:(XMPPIncomingFileTransfer *)sender
|
||||
didFailWithError:(NSError *)error;
|
||||
|
||||
/**
|
||||
* Implement this method to receive notification of an incoming Stream
|
||||
* Initiation offer. Keep in mind that if you haven't set
|
||||
* autoAcceptFileTransfers to YES, then it will be your responsibility to call
|
||||
* acceptSIOffer: using the sender and offer provided to you.
|
||||
*
|
||||
* @param sender XMPPIncomingFileTransfer object invoking this delegate method.
|
||||
* @param offer IQ stanza containing a Stream Initiation offer.
|
||||
*/
|
||||
- (void)xmppIncomingFileTransfer:(XMPPIncomingFileTransfer *)sender
|
||||
didReceiveSIOffer:(XMPPIQ *)offer;
|
||||
|
||||
/**
|
||||
* Implement this method to receive notifications of a successful incoming file
|
||||
* transfer. It will only be invoked if all of the data is received
|
||||
* successfully.
|
||||
*
|
||||
* @param sender XMPPIncomingFileTransfer object invoking this delegate method.
|
||||
* @param data NSData for you to handle (probably save this or display it).
|
||||
* @param named Name of the file you just received.
|
||||
*/
|
||||
- (void)xmppIncomingFileTransfer:(XMPPIncomingFileTransfer *)sender
|
||||
didSucceedWithData:(NSData *)data
|
||||
named:(NSString *)name;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,960 @@
|
||||
//
|
||||
// Created by Jonathon Staff on 10/21/14.
|
||||
// Copyright (c) 2014 nplexity, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
#import "XMPPIncomingFileTransfer.h"
|
||||
#import "XMPPConstants.h"
|
||||
#import "XMPPLogging.h"
|
||||
#import "idn-int.h"
|
||||
#import "NSNumber+XMPP.h"
|
||||
#import "NSData+XMPP.h"
|
||||
|
||||
#if DEBUG
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // XMPP_LOG_LEVEL_VERBOSE | XMPP_LOG_FLAG_TRACE;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Tags for _asyncSocket handling.
|
||||
*/
|
||||
#define SOCKS_TAG_WRITE_METHOD 101
|
||||
#define SOCKS_TAG_READ_METHOD 102
|
||||
#define SOCKS_TAG_WRITE_CONNECT 103
|
||||
#define SOCKS_TAG_READ_REPLY 104
|
||||
#define SOCKS_TAG_READ_ADDRESS 105
|
||||
#define SOCKS_TAG_READ_DATA 106
|
||||
|
||||
#define TIMEOUT_WRITE -1
|
||||
#define TIMEOUT_READ 5.0
|
||||
|
||||
// XMPP Incoming File Transfer State
|
||||
typedef NS_ENUM(int, XMPPIFTState) {
|
||||
XMPPIFTStateNone,
|
||||
XMPPIFTStateWaitingForSIOffer,
|
||||
XMPPIFTStateWaitingForStreamhosts,
|
||||
XMPPIFTStateConnectingToStreamhosts,
|
||||
XMPPIFTStateConnected,
|
||||
XMPPIFTStateWaitingForIBBOpen,
|
||||
XMPPIFTStateWaitingForIBBData
|
||||
};
|
||||
|
||||
NSString *const XMPPIncomingFileTransferErrorDomain = @"XMPPIncomingFileTransferErrorDomain";
|
||||
|
||||
@interface XMPPIncomingFileTransfer () {
|
||||
XMPPIFTState _transferState;
|
||||
|
||||
XMPPJID *_senderJID;
|
||||
|
||||
NSString *_streamhostsQueryId;
|
||||
NSString *_streamhostUsed;
|
||||
|
||||
NSMutableData *_receivedData;
|
||||
NSString *_receivedFileName;
|
||||
NSUInteger _totalDataSize;
|
||||
NSUInteger _receivedDataSize;
|
||||
|
||||
dispatch_source_t _ibbTimer;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation XMPPIncomingFileTransfer
|
||||
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithDispatchQueue:(dispatch_queue_t)queue
|
||||
{
|
||||
self = [super initWithDispatchQueue:queue];
|
||||
if (self) {
|
||||
_transferState = XMPPIFTStateNone;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard deconstructor.
|
||||
*/
|
||||
- (void)dealloc
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (_transferState != XMPPIFTStateNone) {
|
||||
XMPPLogWarn(@"%@: Deallocating prior to completion or cancellation.", THIS_FILE);
|
||||
}
|
||||
|
||||
if (_ibbTimer)
|
||||
dispatch_source_cancel(_ibbTimer);
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(_ibbTimer);
|
||||
#endif
|
||||
_ibbTimer = NULL;
|
||||
|
||||
if (_asyncSocket.delegate == self) {
|
||||
[_asyncSocket setDelegate:nil delegateQueue:NULL];
|
||||
[_asyncSocket disconnect];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Public Methods
|
||||
|
||||
/**
|
||||
* Public facing method for accepting a SI offer. If autoAcceptFileTransfers is
|
||||
* set to YES, this method will do nothing, since the internal method is invoked
|
||||
* automatically.
|
||||
*
|
||||
* @see sendSIOfferAcceptance:
|
||||
*/
|
||||
- (void)acceptSIOffer:(XMPPIQ *)offer
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (!_autoAcceptFileTransfers) {
|
||||
[self sendSIOfferAcceptance:offer];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Private Methods
|
||||
|
||||
/**
|
||||
* This method will send the device's identity in response to a `disco#info`
|
||||
* query. In our case, we will send something close the following:
|
||||
*
|
||||
* <iq type="result"
|
||||
* id="purplea2da8fc9"
|
||||
* from="mephisto@sanctuary.org/kurast"
|
||||
* to="baal@sanctuary.org/worldstonechamber">
|
||||
* <query xmlns="http://jabber.org/protocol/disco#info">
|
||||
* <identity category="client" type="phone"/>
|
||||
* <feature var="http://jabber.org/protocol/si"/>
|
||||
* <feature var="http://jabber.org/protocol/si/profile/file-transfer"/>
|
||||
* <feature var="http://jabber.org/protocol/bytestreams"/>
|
||||
* <feature var="http://jabber.org/protocol/ibb"/>
|
||||
* </query>
|
||||
* </iq>
|
||||
*
|
||||
* This tells the requester who they're dealing with and which transfer types
|
||||
* we support. If there's a better way than hard-coding these values, I'm open
|
||||
* to suggestions.
|
||||
*/
|
||||
- (void)sendIdentity:(XMPPIQ *)request
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
XMPPIQ *iq = [XMPPIQ iqWithType:@"result"
|
||||
to:request.from
|
||||
elementID:request.elementID];
|
||||
[iq addAttributeWithName:@"from" stringValue:xmppStream.myJID.full];
|
||||
|
||||
NSXMLElement
|
||||
*query = [NSXMLElement elementWithName:@"query" xmlns:XMPPDiscoInfoNamespace];
|
||||
|
||||
NSXMLElement *identity = [NSXMLElement elementWithName:@"identity"];
|
||||
[identity addAttributeWithName:@"category" stringValue:@"client"];
|
||||
[identity addAttributeWithName:@"type" stringValue:@"ios-osx"];
|
||||
[query addChild:identity];
|
||||
|
||||
NSXMLElement *feature = [NSXMLElement elementWithName:@"feature"];
|
||||
[feature addAttributeWithName:@"var" stringValue:XMPPSINamespace];
|
||||
[query addChild:feature];
|
||||
|
||||
NSXMLElement *feature1 = [NSXMLElement elementWithName:@"feature"];
|
||||
[feature1 addAttributeWithName:@"var" stringValue:XMPPSIProfileFileTransferNamespace];
|
||||
[query addChild:feature1];
|
||||
|
||||
if (!self.disableSOCKS5) {
|
||||
NSXMLElement *feature2 = [NSXMLElement elementWithName:@"feature"];
|
||||
[feature2 addAttributeWithName:@"var" stringValue:XMPPBytestreamsNamespace];
|
||||
[query addChild:feature2];
|
||||
}
|
||||
|
||||
if (!self.disableIBB) {
|
||||
NSXMLElement *feature3 = [NSXMLElement elementWithName:@"feature"];
|
||||
[feature3 addAttributeWithName:@"var" stringValue:XMPPIBBNamespace];
|
||||
[query addChild:feature3];
|
||||
}
|
||||
|
||||
[iq addChild:query];
|
||||
[xmppStream sendElement:iq];
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will send an IQ stanza accepting the SI offer. We need to choose
|
||||
* which 'stream-method' we prefer to use. For now, we will be using IBB as the
|
||||
* 'stream-method', but SOCKS5 is preferable.
|
||||
*
|
||||
* Take a look at XEP-0096 Examples 2 and 4 for more details.
|
||||
*/
|
||||
- (void)sendSIOfferAcceptance:(XMPPIQ *)offer
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
// Store the sender's JID
|
||||
_senderJID = offer.from;
|
||||
|
||||
// Store the sid for later use
|
||||
NSXMLElement *inSi = offer.childElement;
|
||||
self.sid = [inSi attributeStringValueForName:@"id"];
|
||||
|
||||
// Store the size of the incoming data for later use
|
||||
NSXMLElement *inFile = [inSi elementForName:@"file"];
|
||||
_totalDataSize = [inFile attributeUnsignedIntegerValueForName:@"size"];
|
||||
|
||||
// Store the name of the file for later use
|
||||
_receivedFileName = [inFile attributeStringValueForName:@"name"];
|
||||
|
||||
// Outgoing
|
||||
XMPPIQ *iq = [XMPPIQ iqWithType:@"result"
|
||||
to:offer.from
|
||||
elementID:offer.elementID];
|
||||
|
||||
|
||||
NSXMLElement *si = [NSXMLElement elementWithName:@"si" xmlns:XMPPSINamespace];
|
||||
|
||||
NSXMLElement *feature = [NSXMLElement elementWithName:@"feature"
|
||||
xmlns:XMPPFeatureNegNamespace];
|
||||
|
||||
NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:data"];
|
||||
[x addAttributeWithName:@"type" stringValue:@"submit"];
|
||||
|
||||
NSXMLElement *field = [NSXMLElement elementWithName:@"field"];
|
||||
[field addAttributeWithName:@"var" stringValue:@"stream-method"];
|
||||
|
||||
NSXMLElement *value = [NSXMLElement elementWithName:@"value"];
|
||||
|
||||
// Prefer SOCKS5 if it's not disabled.
|
||||
if (!self.disableSOCKS5) {
|
||||
[value setStringValue:XMPPBytestreamsNamespace];
|
||||
_transferState = XMPPIFTStateWaitingForStreamhosts;
|
||||
} else {
|
||||
[value setStringValue:XMPPIBBNamespace];
|
||||
_transferState = XMPPIFTStateWaitingForIBBOpen;
|
||||
}
|
||||
|
||||
[field addChild:value];
|
||||
[x addChild:field];
|
||||
[feature addChild:x];
|
||||
[si addChild:feature];
|
||||
[iq addChild:si];
|
||||
|
||||
[xmppStream sendElement:iq];
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - IBB Methods
|
||||
|
||||
/**
|
||||
* This method will send an IQ stanza accepting the IBB request. See XEP-0047
|
||||
* Example 2 for more details.
|
||||
*/
|
||||
- (void)sendIBBAcceptance:(XMPPIQ *)request
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
XMPPIQ *iq = [XMPPIQ iqWithType:@"result"
|
||||
to:request.from
|
||||
elementID:request.elementID];
|
||||
[xmppStream sendElement:iq];
|
||||
|
||||
// Prepare to receive data
|
||||
_receivedData = [NSMutableData new];
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible for reading the incoming data from the IQ stanza
|
||||
* and writing it to the member variable '_receivedData'. After successfully
|
||||
* reading the data, a response (XEP-0047 Example 7) will be sent back to the
|
||||
* sender.
|
||||
*/
|
||||
- (void)processReceivedIBBDataIQ:(XMPPIQ *)received
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
// Handle the scenario that the transfer is cancelled.
|
||||
[self resetIBBTimer:20];
|
||||
|
||||
// Handle incoming data
|
||||
NSXMLElement *dataElem = received.childElement;
|
||||
NSData
|
||||
*temp = [[NSData alloc] initWithBase64EncodedString:dataElem.stringValue options:0];
|
||||
[_receivedData appendData:temp];
|
||||
|
||||
// According the base64 encoding, it takes up 4/3 n bytes of space, so
|
||||
// we need to find the size of the data before base64.
|
||||
_receivedDataSize += (3 * dataElem.stringValue.length) / 4;
|
||||
|
||||
XMPPLogVerbose(@"Downloaded %lu/%lu bytes in IBB transfer.",
|
||||
(unsigned long) _receivedDataSize, (unsigned long) _totalDataSize);
|
||||
|
||||
if (_receivedDataSize < _totalDataSize) {
|
||||
// Send ack response
|
||||
XMPPIQ *iq = [XMPPIQ iqWithType:@"result"
|
||||
to:received.from
|
||||
elementID:received.elementID];
|
||||
[xmppStream sendElement:iq];
|
||||
} else {
|
||||
// We're finished!
|
||||
XMPPLogInfo(@"Finished downloading IBB data.");
|
||||
[self transferSuccess];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Util Methods
|
||||
|
||||
/**
|
||||
* This method determines whether or not the IQ stanza is a `disco#info`
|
||||
* request. Should be in the following form:
|
||||
*
|
||||
* <iq xmlns="jabber:client"
|
||||
* from="baal@sanctuary.org/worldstonechamber"
|
||||
* to="mephisto@sanctuary.org/kurast"
|
||||
* type="get"
|
||||
* id="purplea2da8fc9">
|
||||
* <query xmlns="http://jabber.org/protocol/disco#info"/>
|
||||
* </iq>
|
||||
*/
|
||||
- (BOOL)isDiscoInfoIQ:(XMPPIQ *)iq
|
||||
{
|
||||
if (!iq) return NO;
|
||||
NSXMLElement *query = iq.childElement;
|
||||
return query != nil && [query.xmlns isEqualToString:XMPPDiscoInfoNamespace];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method determines whether or not the the IQ stanza is a Stream
|
||||
* Initiation Offer (XEP-0096 Examples 1 and 3).
|
||||
*/
|
||||
- (BOOL)isSIOfferIQ:(XMPPIQ *)iq
|
||||
{
|
||||
if (!iq) return NO;
|
||||
if (![iq.type isEqualToString:@"set"]) return NO;
|
||||
|
||||
NSXMLElement *si = iq.childElement;
|
||||
if (!si || ![si.xmlns isEqualToString:XMPPSINamespace]) return NO;
|
||||
|
||||
NSXMLElement *file = (DDXMLElement *) [si childAtIndex:0];
|
||||
if (!file || ![file.xmlns isEqualToString:XMPPSIProfileFileTransferNamespace]) return NO;
|
||||
|
||||
NSXMLElement *feature = (DDXMLElement *) [si childAtIndex:1];
|
||||
return !(!feature || ![feature.xmlns isEqualToString:XMPPFeatureNegNamespace]);
|
||||
|
||||
// Maybe there should be further verification, but I think this should be
|
||||
// plenty...
|
||||
}
|
||||
|
||||
/**
|
||||
* This method determines whether or not the IQ stanza is an IBB session request
|
||||
* (XEP-0047 Example 1).
|
||||
*/
|
||||
- (BOOL)isIBBOpenRequestIQ:(XMPPIQ *)iq
|
||||
{
|
||||
if (!iq) return NO;
|
||||
if (![iq.type isEqualToString:@"set"]) return NO;
|
||||
|
||||
NSXMLElement *open = iq.childElement;
|
||||
return !(!open || ![open.xmlns isEqualToString:XMPPIBBNamespace]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method determines whether or not the IQ stanza is an IBB data stanza
|
||||
* (XEP-0047 Example 6).
|
||||
*/
|
||||
- (BOOL)isIBBDataIQ:(XMPPIQ *)iq
|
||||
{
|
||||
if (!iq) return NO;
|
||||
if (![iq.type isEqualToString:@"set"]) return NO;
|
||||
|
||||
NSXMLElement *data = iq.childElement;
|
||||
return !(!data || ![data.xmlns isEqualToString:XMPPIBBNamespace]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method determines whether or not the IQ stanza contains a list of
|
||||
* streamhosts as shown in XEP-0065 Example 12.
|
||||
*/
|
||||
- (BOOL)isStreamhostsListIQ:(XMPPIQ *)iq
|
||||
{
|
||||
if (!iq) return NO;
|
||||
if (![iq.type isEqualToString:@"set"]) return NO;
|
||||
|
||||
NSXMLElement *query = iq.childElement;
|
||||
if (!query || ![[query attributeStringValueForName:@"sid"] isEqualToString:self.sid]) return NO;
|
||||
|
||||
return [query elementsForName:@"streamhost"].count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the SHA1 hash as per XEP-0065.
|
||||
*
|
||||
* The [address] MUST be SHA1(SID + Initiator JID + Target JID) and the output
|
||||
* is hexadecimal encoded (not binary).
|
||||
*
|
||||
* Because this is an incoming file transfer, we are always the target.
|
||||
*/
|
||||
- (NSData *)sha1Hash
|
||||
{
|
||||
NSString *hashMe =
|
||||
[NSString stringWithFormat:@"%@%@%@", self.sid, _senderJID.full, xmppStream.myJID.full];
|
||||
NSData *hashRaw = [[hashMe dataUsingEncoding:NSUTF8StringEncoding] xmpp_sha1Digest];
|
||||
NSData *hash = [[hashRaw xmpp_hexStringValue] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
XMPPLogVerbose(@"%@: hashMe : %@", THIS_FILE, hashMe);
|
||||
XMPPLogVerbose(@"%@: hashRaw: %@", THIS_FILE, hashRaw);
|
||||
XMPPLogVerbose(@"%@: hash : %@", THIS_FILE, hash);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called to clean up everything when the transfer fails.
|
||||
*/
|
||||
- (void)failWithReason:(NSString *)causeOfFailure
|
||||
error:
|
||||
(NSError *)error
|
||||
{
|
||||
XMPPLogTrace();
|
||||
XMPPLogInfo(@"Incoming file transfer failed because: %@", causeOfFailure);
|
||||
|
||||
if (!error && causeOfFailure) {
|
||||
NSDictionary *errInfo = @{NSLocalizedDescriptionKey : causeOfFailure};
|
||||
error = [NSError errorWithDomain:XMPPIncomingFileTransferErrorDomain
|
||||
code:-1
|
||||
userInfo:errInfo];
|
||||
}
|
||||
|
||||
_transferState = XMPPIFTStateNone;
|
||||
[multicastDelegate xmppIncomingFileTransfer:self didFailWithError:error];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when the transfer is successfully completed. It
|
||||
* handles resetting variables for another transfer and alerts the delegate of
|
||||
* the transfer completion.
|
||||
*/
|
||||
- (void)transferSuccess
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
[self cancelIBBTimer];
|
||||
|
||||
[multicastDelegate xmppIncomingFileTransfer:self
|
||||
didSucceedWithData:_receivedData
|
||||
named:_receivedFileName];
|
||||
[self cleanUp];
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to reset the system for receiving new files.
|
||||
*/
|
||||
- (void)cleanUp
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (_asyncSocket) {
|
||||
[_asyncSocket setDelegate:nil];
|
||||
[_asyncSocket disconnect];
|
||||
_asyncSocket = nil;
|
||||
}
|
||||
|
||||
_streamMethods &= 0;
|
||||
_transferState = XMPPIFTStateNone;
|
||||
_senderJID = nil;
|
||||
_streamhostsQueryId = nil;
|
||||
_streamhostUsed = nil;
|
||||
_receivedData = nil;
|
||||
_receivedFileName = nil;
|
||||
_totalDataSize = 0;
|
||||
_receivedDataSize = 0;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Timeouts
|
||||
|
||||
/**
|
||||
* Resets the IBB timer that will cause the transfer to formally fail if an IBB
|
||||
* data IQ stanza isn't received within the timeout.
|
||||
*/
|
||||
- (void)resetIBBTimer:(NSTimeInterval)timeout
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked on incorrect queue.");
|
||||
|
||||
if (_ibbTimer == NULL) {
|
||||
_ibbTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, moduleQueue);
|
||||
|
||||
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC);
|
||||
|
||||
dispatch_source_set_timer(_ibbTimer, tt, DISPATCH_TIME_FOREVER, 1);
|
||||
dispatch_resume(_ibbTimer);
|
||||
} else {
|
||||
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC);
|
||||
dispatch_source_set_timer(_ibbTimer, tt, DISPATCH_TIME_FOREVER, 1);
|
||||
}
|
||||
|
||||
dispatch_source_set_event_handler(_ibbTimer, ^{
|
||||
@autoreleasepool {
|
||||
NSString *errMsg = @"The IBB transfer timed out. It's likely that the sender canceled the"
|
||||
@" transfer or has gone offline.";
|
||||
[self failWithReason:errMsg error:nil];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)cancelIBBTimer
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked on incorrect queue.");
|
||||
|
||||
if (_ibbTimer) {
|
||||
dispatch_source_cancel(_ibbTimer);
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(_ibbTimer);
|
||||
#endif
|
||||
_ibbTimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - XMPPStreamDelegate
|
||||
|
||||
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
|
||||
{
|
||||
if (_transferState == XMPPIFTStateNone && [self isDiscoInfoIQ:iq]) {
|
||||
[self sendIdentity:iq];
|
||||
_transferState = XMPPIFTStateWaitingForSIOffer;
|
||||
return YES;
|
||||
}
|
||||
|
||||
if ((_transferState == XMPPIFTStateNone || _transferState == XMPPIFTStateWaitingForSIOffer)
|
||||
&& [self isSIOfferIQ:iq]) {
|
||||
// Alert the delegate that we've received a stream initiation offer
|
||||
[multicastDelegate xmppIncomingFileTransfer:self didReceiveSIOffer:iq];
|
||||
|
||||
if (_autoAcceptFileTransfers) {
|
||||
[self sendSIOfferAcceptance:iq];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (_transferState == XMPPIFTStateWaitingForStreamhosts && [self isStreamhostsListIQ:iq]) {
|
||||
[self attemptStreamhostsConnection:iq];
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (_transferState == XMPPIFTStateWaitingForIBBOpen && [self isIBBOpenRequestIQ:iq]) {
|
||||
[self sendIBBAcceptance:iq];
|
||||
_transferState = XMPPIFTStateWaitingForIBBData;
|
||||
|
||||
// Handle the scenario that the transfer is cancelled.
|
||||
[self resetIBBTimer:20];
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (_transferState == XMPPIFTStateWaitingForIBBData && [self isIBBDataIQ:iq]) {
|
||||
[self processReceivedIBBDataIQ:iq];
|
||||
}
|
||||
|
||||
return iq != nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - GCDAsyncSocketDelegate
|
||||
|
||||
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
|
||||
{
|
||||
XMPPLogVerbose(@"%@: didConnectToHost:%@ port:%d", THIS_FILE, host, port);
|
||||
|
||||
[self socks5WriteMethod];
|
||||
_transferState = XMPPIFTStateConnected;
|
||||
}
|
||||
|
||||
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
|
||||
{
|
||||
XMPPLogVerbose(@"%@: didReadData:%@ withTag:%ld", THIS_FILE, data, tag);
|
||||
|
||||
switch (tag) {
|
||||
case SOCKS_TAG_READ_METHOD:
|
||||
[self socks5ReadMethod:data];
|
||||
break;
|
||||
case SOCKS_TAG_READ_REPLY:
|
||||
[self socks5ReadReply:data];
|
||||
case SOCKS_TAG_READ_ADDRESS:
|
||||
[_asyncSocket readDataToLength:_totalDataSize
|
||||
withTimeout:TIMEOUT_READ
|
||||
tag:SOCKS_TAG_READ_DATA];
|
||||
break;
|
||||
case SOCKS_TAG_READ_DATA:
|
||||
// Success!
|
||||
_receivedData = [data mutableCopy];
|
||||
[self transferSuccess];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
|
||||
{
|
||||
XMPPLogVerbose(@"%@: didWriteDataWithTag:%ld", THIS_FILE, tag);
|
||||
|
||||
switch (tag) {
|
||||
case SOCKS_TAG_WRITE_METHOD:
|
||||
[_asyncSocket readDataToLength:2 withTimeout:TIMEOUT_READ tag:SOCKS_TAG_READ_METHOD];
|
||||
break;
|
||||
case SOCKS_TAG_WRITE_CONNECT:
|
||||
[_asyncSocket readDataToLength:5 withTimeout:TIMEOUT_READ
|
||||
tag:SOCKS_TAG_READ_REPLY];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (_transferState == XMPPIFTStateConnected) {
|
||||
[self failWithReason:@"Socket disconnected before transfer complete." error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - SOCKS5
|
||||
|
||||
/**
|
||||
* This method attempts a connection to each of the streamhosts provided until
|
||||
* either a connection is established or there are no more streamhosts. In the
|
||||
* latter case, an error stanza is sent to the sender.
|
||||
*
|
||||
* @see socket:didConnectToHost:port:
|
||||
*/
|
||||
- (void)attemptStreamhostsConnection:(XMPPIQ *)iq
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
_streamhostsQueryId = iq.elementID;
|
||||
_transferState = XMPPIFTStateConnectingToStreamhosts;
|
||||
_asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:moduleQueue];
|
||||
|
||||
// Since we've already validated our IQ stanza, we can just pull the data
|
||||
NSArray *streamhosts = [iq.childElement elementsForName:@"streamhost"];
|
||||
|
||||
for (NSXMLElement *streamhost in streamhosts) {
|
||||
NSString *host = [streamhost attributeStringValueForName:@"host"];
|
||||
uint16_t port = (gl_uint16_t) [streamhost attributeUInt32ValueForName:@"port"];
|
||||
|
||||
NSError *err;
|
||||
if (![_asyncSocket connectToHost:host onPort:port error:&err]) {
|
||||
XMPPLogVerbose(@"%@: Unable to host:%@ port:%d error:%@", THIS_FILE, host, port, err);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we make it this far, we've successfully connected to one of the hosts.
|
||||
_streamhostUsed = [streamhost attributeStringValueForName:@"jid"];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If we reach this, we weren't able to connect to any of the streamhosts.
|
||||
// We'll send an error to the sender to let them know, and then we'll alert
|
||||
// the delegate of the failure.
|
||||
//
|
||||
// XEP-0065 Example 13.
|
||||
//
|
||||
// <iq from='mephisto@sanctuary.org/kurast'
|
||||
// id='hu3vax16'
|
||||
// to='baal@sanctuary.org/worldstonechamber'
|
||||
// type='error'>
|
||||
// <error type='modify'>
|
||||
// <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
|
||||
// </error>
|
||||
// </iq>
|
||||
|
||||
XMPPIQ *errorIq = [XMPPIQ iqWithType:@"error" to:iq.from elementID:iq.elementID];
|
||||
|
||||
NSXMLElement *errorElem = [NSXMLElement elementWithName:@"error"];
|
||||
[errorElem addAttributeWithName:@"type" stringValue:@"modify"];
|
||||
|
||||
NSXMLElement *notAcceptable = [NSXMLElement elementWithName:@"not-acceptable"
|
||||
xmlns:@"urn:ietf:params:xml:ns:xmpp-stanzas"];
|
||||
[errorElem addChild:notAcceptable];
|
||||
[errorIq addChild:errorElem];
|
||||
|
||||
[xmppStream sendElement:errorIq];
|
||||
|
||||
NSString *errMsg = @"Unable to connect to any of the provided streamhosts.";
|
||||
[self failWithReason:errMsg error:nil];
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
- (void)socks5WriteMethod
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
// We will attempt anonymous authentication with the proxy. The request is
|
||||
// the same that we would read if this were a direct connection. The only
|
||||
// difference is this time we initiate the request as a client rather than
|
||||
// being a the 'server.'
|
||||
//
|
||||
// +----+----------+----------+
|
||||
// |VER | NMETHODS | METHODS |
|
||||
// +----+----------+----------+
|
||||
// | 1 | 1 | 1 to 255 |
|
||||
// +----+----------+----------+
|
||||
//
|
||||
// We're sending:
|
||||
//
|
||||
// VER = 5 (SOCKS5)
|
||||
// NMETHODS = 1 (number of methods)
|
||||
// METHODS = 0 (no authentication)
|
||||
|
||||
void *byteBuf = malloc(3);
|
||||
|
||||
UInt8 ver = 5;
|
||||
memcpy(byteBuf, &ver, sizeof(ver));
|
||||
|
||||
UInt8 nmethods = 1;
|
||||
memcpy(byteBuf + 1, &nmethods, sizeof(nmethods));
|
||||
|
||||
UInt8 methods = 0;
|
||||
memcpy(byteBuf + 2, &methods, sizeof(methods));
|
||||
|
||||
NSData *data = [NSData dataWithBytesNoCopy:byteBuf length:3 freeWhenDone:YES];
|
||||
[_asyncSocket writeData:data withTimeout:TIMEOUT_WRITE tag:SOCKS_TAG_WRITE_METHOD];
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
- (void)socks5ReadMethod:(NSData *)incomingData
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
// We've sent a request to connect with no authentication. This is the
|
||||
// response:
|
||||
//
|
||||
// +----+--------+
|
||||
// |VER | METHOD |
|
||||
// +----+--------+
|
||||
// | 1 | 1 |
|
||||
// +----+--------+
|
||||
//
|
||||
// We're expecting:
|
||||
//
|
||||
// VER = 5 (SOCKS5)
|
||||
// METHOD = 0 (no authentication)
|
||||
|
||||
UInt8 version = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:0];
|
||||
UInt8 method = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:1];
|
||||
|
||||
if (version != 5 || method) {
|
||||
[self failWithReason:@"Proxy doesn't allow anonymous authentication." error:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
NSData *hash = [self sha1Hash];
|
||||
|
||||
// The SOCKS request is formed as follows:
|
||||
//
|
||||
// +----+-----+-------+------+----------+----------+
|
||||
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|
||||
// +----+-----+-------+------+----------+----------+
|
||||
// | 1 | 1 | X'00' | 1 | Variable | 2 |
|
||||
// +----+-----+-------+------+----------+----------+
|
||||
//
|
||||
// We're sending:
|
||||
//
|
||||
// VER = 5
|
||||
// CMD = 1 (connect)
|
||||
// RSV = 0 (reserved; this will always be 0)
|
||||
// ATYP = 3 (domain name)
|
||||
// DST.ADDR (varies based on ATYP)
|
||||
// DST.PORT = 0 (according to XEP-0065)
|
||||
//
|
||||
// Immediately after ATYP, we need to send the length of our address. Because
|
||||
// SHA1 is always 40 bytes, we simply send this value. After it, we append
|
||||
// the actual hash and then the port.
|
||||
|
||||
void *byteBuf = malloc(5 + 40 + 2);
|
||||
|
||||
UInt8 ver = 5;
|
||||
memcpy(byteBuf, &ver, sizeof(ver));
|
||||
|
||||
UInt8 cmd = 1;
|
||||
memcpy(byteBuf + 1, &cmd, sizeof(cmd));
|
||||
|
||||
UInt8 rsv = 0;
|
||||
memcpy(byteBuf + 2, &rsv, sizeof(rsv));
|
||||
|
||||
UInt8 atyp = 3;
|
||||
memcpy(byteBuf + 3, &atyp, sizeof(atyp));
|
||||
|
||||
UInt8 hashlen = (UInt8) hash.length;
|
||||
memcpy(byteBuf + 4, &hashlen, sizeof(hashlen));
|
||||
|
||||
memcpy(byteBuf + 5, hash.bytes, hashlen);
|
||||
|
||||
UInt8 port = 0;
|
||||
memcpy(byteBuf + 5 + hashlen, &port, sizeof(port));
|
||||
memcpy(byteBuf + 6 + hashlen, &port, sizeof(port));
|
||||
|
||||
NSData *data = [NSData dataWithBytesNoCopy:byteBuf length:47 freeWhenDone:YES];
|
||||
[_asyncSocket writeData:data withTimeout:TIMEOUT_WRITE tag:SOCKS_TAG_WRITE_CONNECT];
|
||||
|
||||
XMPPLogVerbose(@"%@: writing connect request: %@", THIS_FILE, data);
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
- (void)socks5ReadReply:(NSData *)incomingData
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
// The server/sender will reply to our connect command with the following:
|
||||
//
|
||||
// +----+-----+-------+------+----------+----------+
|
||||
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
|
||||
// +----+-----+-------+------+----------+----------+
|
||||
// | 1 | 1 | X'00' | 1 | Variable | 2 |
|
||||
// +----+-----+-------+------+----------+----------+
|
||||
//
|
||||
// VER = 5 (SOCKS5)
|
||||
// REP = 0 (Success)
|
||||
// RSV = 0
|
||||
// ATYP = 3 (Domain) - NOTE: Since we're using ATYP = 3, we must check the
|
||||
// length of the server's host in the next byte.
|
||||
|
||||
UInt8 ver = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:0];
|
||||
UInt8 rep = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:1];
|
||||
UInt8 atyp = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:3];
|
||||
UInt8 hostlen = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:4];
|
||||
|
||||
if (ver != 5 || rep || atyp != 3) {
|
||||
[self failWithReason:@"Invalid VER, REP, or ATYP." error:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
// According to XEP-0065 Example 23, we don't need to validate the
|
||||
// address we were sent (at least that is how I interpret it), so we
|
||||
// just read the next 42 bytes (hostlen + portlen) so there's no
|
||||
// conflict when reading the data and then send <streamhost-used/> to
|
||||
// the file transfer initiator. Note that the sid must be included.
|
||||
//
|
||||
// XEP-0065 Example 17:
|
||||
//
|
||||
// <iq from='mephisto@sanctuary.org/kurast'
|
||||
// id='hu3vax16'
|
||||
// to='baal@sanctuary.org/worldstonechamber'
|
||||
// type='result'>
|
||||
// <query xmlns='http://jabber.org/protocol/bytestreams'
|
||||
// sid='vxf9n471bn46'>
|
||||
// <streamhost-used jid='baal@sanctuary.org/worldstonechamber'/>
|
||||
// </query>
|
||||
// </iq>
|
||||
|
||||
XMPPIQ *iq = [XMPPIQ iqWithType:@"result" to:_senderJID elementID:_streamhostsQueryId];
|
||||
|
||||
NSXMLElement *query = [NSXMLElement elementWithName:@"query"
|
||||
xmlns:XMPPBytestreamsNamespace];
|
||||
[query addAttributeWithName:@"sid" stringValue:self.sid];
|
||||
|
||||
NSXMLElement *streamhostUsed = [NSXMLElement elementWithName:@"streamhost-used"];
|
||||
[streamhostUsed addAttributeWithName:@"jid"
|
||||
stringValue:_streamhostUsed];
|
||||
|
||||
[query addChild:streamhostUsed];
|
||||
[iq addChild:query];
|
||||
|
||||
[xmppStream sendElement:iq];
|
||||
|
||||
// We're basically piping these to dev/null because we don't care.
|
||||
// However, we need to tag this read so we can start to read the actual
|
||||
// data once this read is finished.
|
||||
[_asyncSocket readDataToLength:hostlen + 2
|
||||
withTimeout:TIMEOUT_READ
|
||||
tag:SOCKS_TAG_READ_ADDRESS];
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,140 @@
|
||||
//
|
||||
// Created by Jonathon Staff on 10/21/14.
|
||||
// Copyright (c) 2014 nplexity, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPPFileTransfer.h"
|
||||
|
||||
@interface XMPPOutgoingFileTransfer : XMPPFileTransfer
|
||||
|
||||
/**
|
||||
* (Required)
|
||||
*
|
||||
* The data being sent to the recipient.
|
||||
*
|
||||
* If you're using startFileTransfer:, you *MUST* set this prior to calling
|
||||
* startFileTransfer:.
|
||||
*/
|
||||
@property (nonatomic, strong) NSData *outgoingData;
|
||||
|
||||
/**
|
||||
* (Required)
|
||||
*
|
||||
* The recipient of your file transfer.
|
||||
*
|
||||
* If you're using startFileTransfer:, you *MUST* set this prior to calling
|
||||
* startFileTransfer:.
|
||||
*/
|
||||
@property (nonatomic, strong) XMPPJID *recipientJID;
|
||||
|
||||
/**
|
||||
* (Optional)
|
||||
*
|
||||
* The name of the file you're sending.
|
||||
*
|
||||
* If you don't provide a filename, one will be generated for you.
|
||||
*/
|
||||
@property (nonatomic, copy) NSString *outgoingFileName;
|
||||
|
||||
/**
|
||||
* (Optional)
|
||||
*
|
||||
* The description of the file you're sending.
|
||||
*/
|
||||
@property (nonatomic, copy) NSString *outgoingFileDescription;
|
||||
|
||||
/**
|
||||
* (Optional)
|
||||
*
|
||||
* Specifies whether or not a random name should be generated instead of the
|
||||
* filename provided. The randomly generated name will retain the same file
|
||||
* extension as the original name if one was provided
|
||||
*
|
||||
* The default is NO; set to YES to generate a random name.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL shouldGenerateRandomName;
|
||||
|
||||
/**
|
||||
* (Optional)
|
||||
*
|
||||
* Specifies the default block-size when using IBB file transfers. The default
|
||||
* value is 4096 (Bytes). If the file recipient requests a smaller block-size,
|
||||
* it will be halved.
|
||||
*/
|
||||
@property (nonatomic, assign) int32_t blockSize;
|
||||
|
||||
|
||||
#pragma mark - Public Methods
|
||||
|
||||
/**
|
||||
* Starts the file transfer. This assumes that at a minimum a recipientJID and
|
||||
* outgoingData have already been provided.
|
||||
*
|
||||
* @param errPtr The address of an error which will be contain a description of
|
||||
* the problem if there is one (optional).
|
||||
*
|
||||
* @return Returns NO if there is something blatantly wrong (not authorized, no
|
||||
* recipientJID, no outgoingData); YES otherwise.
|
||||
*/
|
||||
- (BOOL)startFileTransfer:(NSError **)errPtr;
|
||||
|
||||
/**
|
||||
* Sends the provided data to the provided recipient. Use of this method is not
|
||||
* recommended, as there is no error handling, but you're free to make your own
|
||||
* choices.
|
||||
*/
|
||||
- (BOOL)sendData:(NSData *)data toRecipient:(XMPPJID *)recipient;
|
||||
|
||||
/**
|
||||
* Sends the provided data to the provided recipient. Pass nil for params you
|
||||
* don't care about.
|
||||
*
|
||||
* @param data The data you wish to send (required).
|
||||
* @param name The filename of the file you're sending (optional).
|
||||
* @param recipient The recipient of your file transfer (required). Note that a
|
||||
* resource must also be included in the JID.
|
||||
* @param description The description of the file you're sending (optional).
|
||||
* @param errPtr The address of an error which will contain a description of the
|
||||
* problem if there is one (optional).
|
||||
*/
|
||||
- (BOOL)sendData:(NSData *)data
|
||||
named:(NSString *)name
|
||||
toRecipient:(XMPPJID *)recipient
|
||||
description:(NSString *)description
|
||||
error:(NSError **)errPtr;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - XMPPOutgoingFileTransferDelegate
|
||||
|
||||
@protocol XMPPOutgoingFileTransferDelegate
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Implement this method when calling startFileTransfer: or sendData:(variants).
|
||||
* It will be invoked if the file transfer fails to execute properly. More
|
||||
* information will be given in the error.
|
||||
*
|
||||
* @param sender XMPPOutgoingFileTransfer object invoking this delegate method.
|
||||
* @param error NSError containing more details of the failure.
|
||||
*/
|
||||
- (void)xmppOutgoingFileTransfer:(XMPPOutgoingFileTransfer *)sender
|
||||
didFailWithError:(NSError *)error;
|
||||
|
||||
/**
|
||||
* Implement this method when calling startFileTransfer: or sendData:(variants).
|
||||
* It will be invoked if the outgoing file transfer was completed successfully.
|
||||
*
|
||||
* @param sender XMPPOutgoingFileTransfer object invoking this delegate method.
|
||||
*/
|
||||
- (void)xmppOutgoingFileTransferDidSucceed:(XMPPOutgoingFileTransfer *)sender;
|
||||
|
||||
/**
|
||||
* Not really sure why you would want this information, but hey, when I get
|
||||
* information, I'm happy to share.
|
||||
*/
|
||||
- (void)xmppOutgoingFileTransferIBBClosed:(XMPPOutgoingFileTransfer *)sender;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user