From 71d2a952c3faeee61c5899beb7ec457ededa8556 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 16 Mar 2011 16:59:44 +0530 Subject: [PATCH 001/180] Added XMPP transport protocol and delegate. --- Core/XMPPStream.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 9adb297..491c1b1 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -749,3 +749,21 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; - (void)xmppStream:(XMPPStream *)sender willUnregisterModule:(id)module; @end + +@protocol XMPPTransportProtocol + +- (void)setDelegate:(id)delegate; +- (BOOL)open; +- (BOOL)close; +- (BOOL)sendStanza:(NSXMLElement *)stanza; + +@optional +- (BOOL)openSecure; + +@end + +@protocol XMPPTransportDelegate + +- (void)transport:(id )transport didReceiveStanza:(NSXMLElement *)stanza; + +@end From 5b4967ec3fc17667885b87f369f3b9adabb2d748 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 16 Mar 2011 17:10:57 +0530 Subject: [PATCH 002/180] Moved XMPP transport protocol source to separate file. --- Core/XMPPStream.h | 18 ------------------ Core/XMPPTransportProtocol.h | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 18 deletions(-) create mode 100644 Core/XMPPTransportProtocol.h diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 491c1b1..9adb297 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -749,21 +749,3 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; - (void)xmppStream:(XMPPStream *)sender willUnregisterModule:(id)module; @end - -@protocol XMPPTransportProtocol - -- (void)setDelegate:(id)delegate; -- (BOOL)open; -- (BOOL)close; -- (BOOL)sendStanza:(NSXMLElement *)stanza; - -@optional -- (BOOL)openSecure; - -@end - -@protocol XMPPTransportDelegate - -- (void)transport:(id )transport didReceiveStanza:(NSXMLElement *)stanza; - -@end diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h new file mode 100644 index 0000000..a200e8b --- /dev/null +++ b/Core/XMPPTransportProtocol.h @@ -0,0 +1,28 @@ +// +// XMPPTransportProtocol.h +// iPhoneXMPP +// +// Created by Chaitanya Gupta on 16/03/11. +// Copyright 2011 Directi. All rights reserved. +// + +#import + +@protocol XMPPTransportProtocol + +- (void)setDelegate:(id)delegate; +- (BOOL)open; +- (BOOL)close; +- (BOOL)sendStanza:(NSXMLElement *)stanza; + +@optional +- (BOOL)openSecure; + +@end + + +@protocol XMPPTransportDelegate + +- (void)transport:(id )transport didReceiveStanza:(NSXMLElement *)stanza; + +@end From 75a4384dabf1abac344f9fdba4d0e3f67a625ef1 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 16 Mar 2011 17:11:53 +0530 Subject: [PATCH 003/180] Added XMPPTransportProtocol.h to iPhoneXMPP Xcode proj. --- Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj index ddba874..fadbdb5 100755 --- a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj +++ b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj @@ -67,6 +67,7 @@ 28C286E00D94DF7D0034E888 /* RootViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RootViewController.m; sourceTree = ""; }; 28F335F01007B36200424DE2 /* RootViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RootViewController.xib; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 54FDBEC01330D8460099D4DB /* XMPPTransportProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPTransportProtocol.h; path = ../../Core/XMPPTransportProtocol.h; sourceTree = ""; }; 8D1107310486CEB800E47090 /* iPhoneXMPP-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iPhoneXMPP-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; DC1F97E11152CA2D00138A8F /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; }; DC1F97E71152CA4E00138A8F /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; @@ -324,6 +325,7 @@ DC84BBD812440A8E0055A459 /* XMPPReconnect.m */, DC84BBD112440A8E0055A459 /* XMPPModule.h */, DC84BBD212440A8E0055A459 /* XMPPModule.m */, + 54FDBEC01330D8460099D4DB /* XMPPTransportProtocol.h */, ); name = Core; sourceTree = ""; From 633457f89392730c1d4dbf430ee6cc5b134b06e2 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 16 Mar 2011 17:15:52 +0530 Subject: [PATCH 004/180] Added socket transport stub file. --- Core/Transports/XMPPSocketTransport.h | 16 ++++++++++++++++ Core/Transports/XMPPSocketTransport.m | 14 ++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 Core/Transports/XMPPSocketTransport.h create mode 100644 Core/Transports/XMPPSocketTransport.m diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h new file mode 100644 index 0000000..19017f1 --- /dev/null +++ b/Core/Transports/XMPPSocketTransport.h @@ -0,0 +1,16 @@ +// +// XMPPSocketTransport.h +// iPhoneXMPP +// +// Created by Chaitanya Gupta on 16/03/11. +// Copyright 2011 Directi. All rights reserved. +// + +#import + + +@interface XMPPSocketTransport : NSObject { + +} + +@end diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m new file mode 100644 index 0000000..33d841c --- /dev/null +++ b/Core/Transports/XMPPSocketTransport.m @@ -0,0 +1,14 @@ +// +// XMPPSocketTransport.m +// iPhoneXMPP +// +// Created by Chaitanya Gupta on 16/03/11. +// Copyright 2011 Directi. All rights reserved. +// + +#import "XMPPSocketTransport.h" + + +@implementation XMPPSocketTransport + +@end From 61b1b53a2ef9421e4709b1587eaf432d7bbd8b0f Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 16 Mar 2011 17:16:38 +0530 Subject: [PATCH 005/180] Added socket transport files to iPhoneXMPP project. --- .../iPhoneXMPP.xcodeproj/project.pbxproj | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj index fadbdb5..4307e68 100755 --- a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj +++ b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; }; 28C286E10D94DF7D0034E888 /* RootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C286E00D94DF7D0034E888 /* RootViewController.m */; }; 28F335F11007B36200424DE2 /* RootViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28F335F01007B36200424DE2 /* RootViewController.xib */; }; + 54FDBEC41330DA030099D4DB /* XMPPSocketTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FDBEC31330DA030099D4DB /* XMPPSocketTransport.m */; }; DC1F97E21152CA2D00138A8F /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1F97E11152CA2D00138A8F /* libxml2.dylib */; }; DC1F97E81152CA4E00138A8F /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1F97E71152CA4E00138A8F /* CFNetwork.framework */; }; DC1F98371152CBC200138A8F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1F98361152CBC200138A8F /* SystemConfiguration.framework */; }; @@ -68,6 +69,8 @@ 28F335F01007B36200424DE2 /* RootViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RootViewController.xib; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 54FDBEC01330D8460099D4DB /* XMPPTransportProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPTransportProtocol.h; path = ../../Core/XMPPTransportProtocol.h; sourceTree = ""; }; + 54FDBEC21330DA030099D4DB /* XMPPSocketTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPSocketTransport.h; path = ../../Core/Transports/XMPPSocketTransport.h; sourceTree = ""; }; + 54FDBEC31330DA030099D4DB /* XMPPSocketTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPSocketTransport.m; path = ../../Core/Transports/XMPPSocketTransport.m; sourceTree = ""; }; 8D1107310486CEB800E47090 /* iPhoneXMPP-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iPhoneXMPP-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; DC1F97E11152CA2D00138A8F /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; }; DC1F97E71152CA4E00138A8F /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; @@ -222,6 +225,15 @@ name = Frameworks; sourceTree = ""; }; + 54FDBEC51330DA0C0099D4DB /* Transports */ = { + isa = PBXGroup; + children = ( + 54FDBEC21330DA030099D4DB /* XMPPSocketTransport.h */, + 54FDBEC31330DA030099D4DB /* XMPPSocketTransport.m */, + ); + name = Transports; + sourceTree = ""; + }; DC1F97D01152C9DC00138A8F /* KissXML */ = { isa = PBXGroup; children = ( @@ -306,6 +318,7 @@ DC1F98031152CB0C00138A8F /* Core */ = { isa = PBXGroup; children = ( + 54FDBEC51330DA0C0099D4DB /* Transports */, DC84BBC812440A8E0055A459 /* XMPP.h */, DC84BBD312440A8E0055A459 /* XMPPParser.h */, DC84BBD412440A8E0055A459 /* XMPPParser.m */, @@ -480,6 +493,7 @@ DC84BC0412440AF40055A459 /* XMPPUserCoreDataStorage.m in Sources */, 0771B81D1315E9FB0018D5C6 /* XMPPRosterCoreDataStorage+shared.m in Sources */, 0771B81E1315E9FC0018D5C6 /* XMPPStreamCoreDataStorage.m in Sources */, + 54FDBEC41330DA030099D4DB /* XMPPSocketTransport.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From f74a9a1a2f9f005fed5c3c5a038531e518b82477 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Thu, 17 Mar 2011 16:40:33 +0530 Subject: [PATCH 006/180] Updated transport protocol with more methods and better names. --- Core/XMPPTransportProtocol.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index a200e8b..6e3691b 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -11,18 +11,21 @@ @protocol XMPPTransportProtocol - (void)setDelegate:(id)delegate; -- (BOOL)open; -- (BOOL)close; +- (BOOL)connect:(NSError *)errPtr; +- (BOOL)disconnect; - (BOOL)sendStanza:(NSXMLElement *)stanza; @optional -- (BOOL)openSecure; - +- (BOOL)secure; @end @protocol XMPPTransportDelegate - +- (void)transportDidConnect:(id )transport; +- (void)transportDidDisconnect:(id )transport; - (void)transport:(id )transport didReceiveStanza:(NSXMLElement *)stanza; +- (void)transport:(id )transport didReceiveError:(id)error; +@optional +- (void)transportDidSecure:(id )transport; @end From 62e9a8ac6ccf65cc706b9f02f2239ef6df5ffd46 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 18 Mar 2011 15:37:58 +0530 Subject: [PATCH 007/180] Adopting multicast delegates for XMPPTransports. --- Core/XMPPTransportProtocol.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 6e3691b..0b27271 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -10,13 +10,15 @@ @protocol XMPPTransportProtocol -- (void)setDelegate:(id)delegate; +- (void)addDelegate:(id)delegate; +- (void)removeDelegate:(id)delegate; - (BOOL)connect:(NSError *)errPtr; - (BOOL)disconnect; - (BOOL)sendStanza:(NSXMLElement *)stanza; @optional - (BOOL)secure; + @end From 9600240d614a21ee6362c727ab55765680aca008 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 18 Mar 2011 15:38:18 +0530 Subject: [PATCH 008/180] Mark all delegate methods as optional. --- Core/XMPPTransportProtocol.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 0b27271..4c4c366 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -23,11 +23,12 @@ @protocol XMPPTransportDelegate + +@optional - (void)transportDidConnect:(id )transport; - (void)transportDidDisconnect:(id )transport; - (void)transport:(id )transport didReceiveStanza:(NSXMLElement *)stanza; - (void)transport:(id )transport didReceiveError:(id)error; - -@optional - (void)transportDidSecure:(id )transport; + @end From 1c2a3f0840782b18d16a09f249a2c36c19f4ecf5 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 18 Mar 2011 16:30:48 +0530 Subject: [PATCH 009/180] Successfully initializing XMPPSocketTransport. --- Core/Transports/XMPPSocketTransport.h | 20 ++++++++++- Core/Transports/XMPPSocketTransport.m | 50 ++++++++++++++++++++++++++- Core/XMPPStream.h | 4 +++ Core/XMPPStream.m | 11 ++++++ Core/XMPPTransportProtocol.h | 4 ++- 5 files changed, 86 insertions(+), 3 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index 19017f1..d63fc8f 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -7,10 +7,28 @@ // #import +#import "XMPPTransportProtocol.h" +#import "MulticastDelegate.h" +@class AsyncSocket; +@class NSXMLElement; -@interface XMPPSocketTransport : NSObject { +@interface XMPPSocketTransport : NSObject { + MulticastDelegate *multicastDelegate; + AsyncSocket *asyncSocket; + NSString *host; + UInt16 port; } +- (id)initWithHost:(NSString *)host port:(UInt16)port; + +- (void)addDelegate:(id)delegate; +- (void)removeDelegate:(id)delegate; +- (BOOL)connect:(NSError *)errPtr; +- (BOOL)disconnect; +- (BOOL)sendStanza:(NSXMLElement *)stanza; +- (BOOL)sendStanzaWithString:(NSString *)string; +- (BOOL)secure; + @end diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index 33d841c..b073277 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -7,8 +7,56 @@ // #import "XMPPSocketTransport.h" - +#import "MulticastDelegate.h" +#import "AsyncSocket.h" @implementation XMPPSocketTransport +- (id)initWithHost:(NSString *)givenHost port:(UInt16)givenPort +{ + if ((self = [super init])) + { + multicastDelegate = [[MulticastDelegate alloc] init]; + asyncSocket = [[AsyncSocket alloc] init]; + host = givenHost; + port = givenPort; + } + return self; +} + +- (void)addDelegate:(id)delegate +{ + [multicastDelegate addDelegate:delegate]; +} + +- (void)removeDelegate:(id)delegate +{ + [multicastDelegate removeDelegate:delegate]; +} + +- (BOOL)connect:(NSError *)errPtr +{ + return YES; +} + +- (BOOL)disconnect +{ + return YES; +} + +- (BOOL)sendStanza:(NSXMLElement *)stanza +{ + return YES; +} + +- (BOOL)sendStanzaWithString:(NSString *)string +{ + return YES; +} + +- (BOOL)secure +{ + return YES; +} + @end diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 9adb297..b53f0d2 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -1,5 +1,6 @@ #import #import "MulticastDelegate.h" +#import "XMPPTransportProtocol.h" #if TARGET_OS_IPHONE #import "DDXML.h" @@ -80,6 +81,7 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; int state; AsyncSocket *asyncSocket; + id transport; UInt64 numberOfBytesSent; UInt64 numberOfBytesReceived; @@ -113,6 +115,8 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; NSString *synchronousUUID; } +- (id)initWithTransport:(id)transport; + /** * Standard XMPP initialization. * The stream is a standard client to server connection. diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index ba04082..55ee2b9 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -109,6 +109,17 @@ - (void)commonInit autoDelegateDict = [[NSMutableDictionary alloc] init]; } +-(id)initWithTransport:(id)givenTransport +{ + if ((self = [super init])) + { + [self commonInit]; + transport = givenTransport; + [transport addDelegate:self]; + } + return self; +} + /** * Standard XMPP initialization. * The stream is a standard client to server connection. diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 4c4c366..45a473f 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -8,6 +8,8 @@ #import +@class NSXMLElement; + @protocol XMPPTransportProtocol - (void)addDelegate:(id)delegate; @@ -15,7 +17,7 @@ - (BOOL)connect:(NSError *)errPtr; - (BOOL)disconnect; - (BOOL)sendStanza:(NSXMLElement *)stanza; - +- (BOOL)sendStanzaWithString:(NSString *)string; @optional - (BOOL)secure; From d8c3a7a161403f497239b6034fd73a33f7b388f7 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 18 Mar 2011 16:31:44 +0530 Subject: [PATCH 010/180] Using XMPPSocketTransport in iPhoneXMPP, instead of relying on the previous transport which was tied into XMPPStream. --- Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.h | 3 ++- Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m | 14 ++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.h b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.h index 939d747..a51a858 100644 --- a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.h +++ b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.h @@ -1,13 +1,14 @@ #import #import +#import "XMPPSocketTransport.h" @class XMPPStream; @class XMPPRoster; @class XMPPRosterCoreDataStorage; - @interface iPhoneXMPPAppDelegate : NSObject { + XMPPSocketTransport *transport; XMPPStream *xmppStream; XMPPRoster *xmppRoster; XMPPRosterCoreDataStorage *xmppRosterStorage; diff --git a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m index a7d0999..7fef647 100644 --- a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m +++ b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m @@ -3,6 +3,7 @@ #import "XMPP.h" #import "XMPPRosterCoreDataStorage.h" +#import "XMPPSocketTransport.h" #import @@ -17,7 +18,9 @@ @implementation iPhoneXMPPAppDelegate - (void)applicationDidFinishLaunching:(UIApplication *)application { - xmppStream = [[XMPPStream alloc] init]; + // Replace me with the proper host name and port + //transport = [[XMPPSocketTransport alloc] initWithHost:@"talk.google.com" port:5222]; + xmppStream = [[XMPPStream alloc] initWithTransport:transport]; xmppRosterStorage = [[XMPPRosterCoreDataStorage alloc] init]; xmppRoster = [[XMPPRoster alloc] initWithStream:xmppStream rosterStorage:xmppRosterStorage]; @@ -26,14 +29,9 @@ - (void)applicationDidFinishLaunching:(UIApplication *)application [xmppRoster setAutoRoster:YES]; - // Replace me with the proper domain and port. - // The example below is setup for a typical google talk account. -// [xmppStream setHostName:@"talk.google.com"]; -// [xmppStream setHostPort:5222]; - // Replace me with the proper JID and password -// [xmppStream setMyJID:[XMPPJID jidWithString:@"user@gmail.com/iPhoneTest"]]; -// password = @"password"; + //[xmppStream setMyJID:[XMPPJID jidWithString:@"user@gmail.com/iPhoneTest"]]; + //password = @"password"; // You may need to alter these settings depending on the server you're connecting to allowSelfSignedCertificates = NO; From 89db8e34112490dab72ff0bd391703dbaafd28d2 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 18 Mar 2011 19:21:30 +0530 Subject: [PATCH 011/180] Send opening negotiation successfully. --- Core/Transports/XMPPSocketTransport.h | 53 ++++++++++- Core/Transports/XMPPSocketTransport.m | 129 +++++++++++++++++++++++++- Core/XMPPStream.h | 2 +- Core/XMPPStream.m | 86 +++-------------- Core/XMPPTransportProtocol.h | 5 +- 5 files changed, 197 insertions(+), 78 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index d63fc8f..88e1ed6 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -10,23 +10,72 @@ #import "XMPPTransportProtocol.h" #import "MulticastDelegate.h" +#if TARGET_OS_IPHONE +#import "DDXML.h" +#endif + @class AsyncSocket; -@class NSXMLElement; +@class XMPPParser; +@class XMPPJID; + +// Define the debugging state +#define DEBUG_SEND YES +#define DEBUG_RECV_PRE NO // Prints data before going to xmpp parser +#define DEBUG_RECV_POST YES // Prints data as it comes out of xmpp parser + +#define DDLogSend(format, ...) do{ if(DEBUG_SEND) NSLog((format), ##__VA_ARGS__); }while(0) +#define DDLogRecvPre(format, ...) do{ if(DEBUG_RECV_PRE) NSLog((format), ##__VA_ARGS__); }while(0) +#define DDLogRecvPost(format, ...) do{ if(DEBUG_RECV_POST) NSLog((format), ##__VA_ARGS__); }while(0) + +// Define the various timeouts (in seconds) for retreiving various parts of the XML stream +#define TIMEOUT_WRITE 10 +#define TIMEOUT_READ_START 10 +#define TIMEOUT_READ_STREAM -1 + +// Define the various tags we'll use to differentiate what it is we're currently reading or writing +#define TAG_WRITE_START -100 // Must be outside UInt16 range +#define TAG_WRITE_STREAM -101 // Must be outside UInt16 range +#define TAG_WRITE_SYNCHRONOUS -102 // Must be outside UInt16 range + +#define TAG_READ_START 200 +#define TAG_READ_STREAM 201 + + +#if TARGET_OS_IPHONE +#define DEFAULT_KEEPALIVE_INTERVAL 120.0 // 2 Minutes +#else +#define DEFAULT_KEEPALIVE_INTERVAL 300.0 // 5 Minutes +#endif + +enum xmppSocketState { + XMPP_SOCKET_DISCONNECTED, + XMPP_SOCKET_OPENING, + XMPP_SOCKET_NEGOTIATING, + XMPP_SOCKET_NEGOTIATED, + XMPP_SOCKET_CONNECTED +}; @interface XMPPSocketTransport : NSObject { MulticastDelegate *multicastDelegate; AsyncSocket *asyncSocket; + XMPPParser *parser; + enum xmppSocketState state; NSString *host; UInt16 port; + XMPPJID *myJID; + + int numberOfBytesSent; + int numberOfBytesReceived; } - (id)initWithHost:(NSString *)host port:(UInt16)port; - (void)addDelegate:(id)delegate; - (void)removeDelegate:(id)delegate; -- (BOOL)connect:(NSError *)errPtr; +- (BOOL)connect:(NSError **)errPtr; - (BOOL)disconnect; +- (void)restartStream; - (BOOL)sendStanza:(NSXMLElement *)stanza; - (BOOL)sendStanzaWithString:(NSString *)string; - (BOOL)secure; diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index b073277..fc2df9a 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -9,6 +9,16 @@ #import "XMPPSocketTransport.h" #import "MulticastDelegate.h" #import "AsyncSocket.h" +#import "XMPPParser.h" + +@interface XMPPSocketTransport () + +- (void)sendOpeningNegotiation; + +- (BOOL)onSocketWillConnect:(AsyncSocket *)socket; +- (void)onSocket:(AsyncSocket *)socket didConnectToHost:(NSString *)givenHost port:(UInt16)givenPort; + +@end @implementation XMPPSocketTransport @@ -17,9 +27,13 @@ - (id)initWithHost:(NSString *)givenHost port:(UInt16)givenPort if ((self = [super init])) { multicastDelegate = [[MulticastDelegate alloc] init]; - asyncSocket = [[AsyncSocket alloc] init]; + asyncSocket = [[AsyncSocket alloc] initWithDelegate:self]; + parser = [[XMPPParser alloc] initWithDelegate:self]; host = givenHost; port = givenPort; + state = XMPP_SOCKET_DISCONNECTED; + numberOfBytesSent = 0; + numberOfBytesReceived = 0; } return self; } @@ -34,9 +48,10 @@ - (void)removeDelegate:(id)delegate [multicastDelegate removeDelegate:delegate]; } -- (BOOL)connect:(NSError *)errPtr +- (BOOL)connect:(NSError **)errPtr { - return YES; + state = XMPP_SOCKET_OPENING; + return [asyncSocket connectToHost:host onPort:port error:errPtr]; } - (BOOL)disconnect @@ -44,6 +59,11 @@ - (BOOL)disconnect return YES; } +- (void)restartStream +{ + +} + - (BOOL)sendStanza:(NSXMLElement *)stanza { return YES; @@ -59,4 +79,107 @@ - (BOOL)secure return YES; } +///////////////////////// +#pragma mark PrivateAPI +///////////////////////// + +/** + * This method handles sending the opening element which is needed in several situations. + **/ +- (void)sendOpeningNegotiation +{ + BOOL isRenegotiation = NO; + + if (state == XMPP_SOCKET_OPENING) + { + // TCP connection was just opened - We need to include the opening XML stanza + NSString *s1 = @""; + + NSData *outgoingData = [s1 dataUsingEncoding:NSUTF8StringEncoding]; + + DDLogSend(@"SEND: %@", s1); + numberOfBytesSent += [outgoingData length]; + + [asyncSocket writeData:outgoingData + withTimeout:TIMEOUT_WRITE + tag:TAG_WRITE_START]; + } + + if (state != XMPP_SOCKET_OPENING) + { + // We're restarting our negotiation. + // This happens, for example, after securing the connection with SSL/TLS. + isRenegotiation = YES; + + // Since we're restarting the XML stream, we need to reset the parser. + [parser stop]; + [parser release]; + + parser = [(XMPPParser *)[XMPPParser alloc] initWithDelegate:self]; + } + else if (parser == nil) + { + // Need to create parser (it was destroyed when the socket was last disconnected) + parser = [(XMPPParser *)[XMPPParser alloc] initWithDelegate:self]; + } + + NSString *xmlns = @"jabber:client"; + NSString *xmlns_stream = @"http://etherx.jabber.org/streams"; + + NSString *temp, *s2; + + if (myJID) + { + temp = @""; + s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, [myJID domain]]; + } + else if ([host length] > 0) + { + temp = @""; + s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, host]; + } + else + { + temp = @""; + s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream]; + } + + NSData *outgoingData = [s2 dataUsingEncoding:NSUTF8StringEncoding]; + + DDLogSend(@"SEND: %@", s2); + numberOfBytesSent += [outgoingData length]; + + [asyncSocket writeData:outgoingData + withTimeout:TIMEOUT_WRITE + tag:TAG_WRITE_START]; + + // Update status + state = XMPP_SOCKET_NEGOTIATING; + + // For a reneogitation, we need to manually read from the socket. + // This is because we had to reset our parser, which is usually used to continue the reading process. + if (isRenegotiation) + { + [asyncSocket readDataWithTimeout:TIMEOUT_READ_START tag:TAG_READ_START]; + } +} + +////////////////////////////////// +#pragma mark AsyncSocket Delegate +////////////////////////////////// +- (BOOL)onSocketWillConnect:(AsyncSocket *)socket +{ + [multicastDelegate transportWillConnect:self]; + return YES; +} + +- (void)onSocket:(AsyncSocket *)socket didConnectToHost:(NSString *)givenHost port:(UInt16)givenPort +{ + [self sendOpeningNegotiation]; + [multicastDelegate transportDidStartNegotiation:self]; + // And start reading in the server's XML stream + [asyncSocket readDataWithTimeout:TIMEOUT_READ_START tag:TAG_READ_START]; +} + + @end diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index b53f0d2..85cb2e3 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -75,7 +75,7 @@ enum XMPPStreamErrorCode typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; -@interface XMPPStream : NSObject +@interface XMPPStream : NSObject { MulticastDelegate *multicastDelegate; diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 55ee2b9..77ed707 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -299,7 +299,7 @@ - (BOOL)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)err - (BOOL)connect:(NSError **)errPtr { - if (state > STATE_RESOLVING_SRV) + if (state > STATE_DISCONNECTED) { if (errPtr) { @@ -311,18 +311,6 @@ - (BOOL)connect:(NSError **)errPtr return NO; } - if ([self isP2P]) - { - if (errPtr) - { - NSString *errMsg = @"P2P streams must use either connectTo:withAddress: or connectP2PWithSocket:."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidType userInfo:info]; - } - return NO; - } - if (myJID == nil) { // Note: If you wish to use anonymous authentication, you should still set myJID prior to calling connect. @@ -352,23 +340,8 @@ - (BOOL)connect:(NSError **)errPtr // Notify delegates [multicastDelegate xmppStreamWillConnect:self]; - if ([hostName length] == 0) - { - // Resolve the hostName via myJID SRV resolution - - state = STATE_RESOLVING_SRV; - - [srvResolver release]; - srvResolver = [[RFSRVResolver resolveWithStream:self delegate:self] retain]; - - return YES; - } - else - { - // Open TCP connection to the configured hostName. - - return [self connectToHost:hostName onPort:hostPort error:errPtr]; - } + // Open TCP connection to the configured hostName. + return [transport connect:errPtr]; } - (BOOL)oldSchoolSecureConnect:(NSError **)errPtr @@ -1904,54 +1877,25 @@ - (void)srvResolver:(RFSRVResolver *)sender didNotResolveSRVWithError:(NSError * [self tryNextSrvResult]; } -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark AsyncSocket Delegate -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)onSocketWillConnect:(AsyncSocket *)socket { - [multicastDelegate xmppStream:self socketWillConnect:socket]; - - return YES; -} +////////////////////////////////////// +#pragma mark XMPPTransport Delegate +////////////////////////////////////// -/** - * Called when a socket connects and is ready for reading and writing. "host" will be an IP address, not a DNS name. -**/ -- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port +- (void)transportDidConnect:(id)transport { - // The TCP connection is now established - - [srvResolver release]; - srvResolver = nil; - - [srvResults release]; - srvResults = nil; - - // Are we using old-style SSL? (Not the upgrade to TLS technique specified in the XMPP RFC) - if ([self isSecure]) - { - // The connection must be secured immediately (just like with HTTPS) - [self startTLS]; - - // Note: We don't need to wait for asyncSocket to complete TLS negotiation. - // We can just continue reading/writing to the socket, and it will handle queueing everything for us! - } - - // Initialize the XML stream - [self sendOpeningNegotiation]; - - // Inform delegate that the TCP connection is open, and the stream handshake has begun - [multicastDelegate xmppStreamDidStartNegotiation:self]; - - // And start reading in the server's XML stream - [asyncSocket readDataWithTimeout:TIMEOUT_READ_START tag:TAG_READ_START]; + [self sendOpeningNegotiation]; + [multicastDelegate xmppStreamDidStartNegotiation:self]; } -- (void)onSocketDidSecure:(AsyncSocket *)sock +- (void)transportDidSecure:(id)transport { - [multicastDelegate xmppStreamDidSecure:self]; + [multicastDelegate xmppStreamDidSecure:self]; } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark AsyncSocket Delegate +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** * Called when a socket has completed reading the requested data. Not called if there is an error. **/ diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 45a473f..56f252f 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -14,8 +14,9 @@ - (void)addDelegate:(id)delegate; - (void)removeDelegate:(id)delegate; -- (BOOL)connect:(NSError *)errPtr; +- (BOOL)connect:(NSError **)errPtr; - (BOOL)disconnect; +- (void)restartStream; - (BOOL)sendStanza:(NSXMLElement *)stanza; - (BOOL)sendStanzaWithString:(NSString *)string; @optional @@ -27,6 +28,8 @@ @protocol XMPPTransportDelegate @optional +- (void)transportWillConnect:(id )transport; +- (void)transportDidStartNegotiation:(id )transport; - (void)transportDidConnect:(id )transport; - (void)transportDidDisconnect:(id )transport; - (void)transport:(id )transport didReceiveStanza:(NSXMLElement *)stanza; From a775960ba4bdd2547926037683161e0a9a67b4c1 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 18 Mar 2011 19:26:14 +0530 Subject: [PATCH 012/180] Add setMyJID method to transport protocol. Now the 'to' attribute in the opening tag is set to the domain of the jid. --- Core/Transports/XMPPSocketTransport.h | 2 ++ Core/Transports/XMPPSocketTransport.m | 2 ++ Core/XMPPStream.m | 6 +++++- Core/XMPPTransportProtocol.h | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index 88e1ed6..f23999f 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -69,6 +69,8 @@ enum xmppSocketState { int numberOfBytesReceived; } +@property (retain) XMPPJID *myJID; + - (id)initWithHost:(NSString *)host port:(UInt16)port; - (void)addDelegate:(id)delegate; diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index fc2df9a..7b5fc4d 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -22,6 +22,8 @@ - (void)onSocket:(AsyncSocket *)socket didConnectToHost:(NSString *)givenHost po @implementation XMPPSocketTransport +@synthesize myJID; + - (id)initWithHost:(NSString *)givenHost port:(UInt16)givenPort { if ((self = [super init])) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 77ed707..92058c3 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -340,7 +340,11 @@ - (BOOL)connect:(NSError **)errPtr // Notify delegates [multicastDelegate xmppStreamWillConnect:self]; - // Open TCP connection to the configured hostName. + // Instruct transport to open the connection + if (myJID) + { + [transport setMyJID:myJID]; + } return [transport connect:errPtr]; } diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 56f252f..fd00b36 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -9,11 +9,13 @@ #import @class NSXMLElement; +@class XMPPJID; @protocol XMPPTransportProtocol - (void)addDelegate:(id)delegate; - (void)removeDelegate:(id)delegate; +- (void)setMyJID:(XMPPJID *)jid; - (BOOL)connect:(NSError **)errPtr; - (BOOL)disconnect; - (void)restartStream; From ac1b44638304a23b722c67dce549815da1f17656 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 18 Mar 2011 19:46:05 +0530 Subject: [PATCH 013/180] Remove class extension which was used to provide 'private' methods. Private methods are not needed to be declared as long as they are used only after their definition is seen by the compiler. --- Core/Transports/XMPPSocketTransport.m | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index 7b5fc4d..38605d7 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -11,15 +11,6 @@ #import "AsyncSocket.h" #import "XMPPParser.h" -@interface XMPPSocketTransport () - -- (void)sendOpeningNegotiation; - -- (BOOL)onSocketWillConnect:(AsyncSocket *)socket; -- (void)onSocket:(AsyncSocket *)socket didConnectToHost:(NSString *)givenHost port:(UInt16)givenPort; - -@end - @implementation XMPPSocketTransport @synthesize myJID; From 261f09fc208f20b0c224be4f4425e8bd1882e8bc Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 18 Mar 2011 20:41:17 +0530 Subject: [PATCH 014/180] Read opening stream tag from server and call transportDidConnect: --- Core/Transports/XMPPSocketTransport.h | 4 ++- Core/Transports/XMPPSocketTransport.m | 50 +++++++++++++++++++++++++++ Core/XMPPStream.m | 1 - Core/XMPPTransportProtocol.h | 1 + 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index f23999f..ea3fba2 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -51,7 +51,6 @@ enum xmppSocketState { XMPP_SOCKET_DISCONNECTED, XMPP_SOCKET_OPENING, XMPP_SOCKET_NEGOTIATING, - XMPP_SOCKET_NEGOTIATED, XMPP_SOCKET_CONNECTED }; @@ -67,6 +66,8 @@ enum xmppSocketState { int numberOfBytesSent; int numberOfBytesReceived; + + NSXMLElement *rootElement; } @property (retain) XMPPJID *myJID; @@ -78,6 +79,7 @@ enum xmppSocketState { - (BOOL)connect:(NSError **)errPtr; - (BOOL)disconnect; - (void)restartStream; +- (float)serverXmppStreamVersionNumber; - (BOOL)sendStanza:(NSXMLElement *)stanza; - (BOOL)sendStanzaWithString:(NSString *)string; - (BOOL)secure; diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index 38605d7..66925a3 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -10,6 +10,7 @@ #import "MulticastDelegate.h" #import "AsyncSocket.h" #import "XMPPParser.h" +#import "NSXMLElementAdditions.h" @implementation XMPPSocketTransport @@ -57,6 +58,16 @@ - (void)restartStream } +/** + * Returns the version attribute from the servers's element. + * This should be at least 1.0 to be RFC 3920 compliant. + * If no version number was set, the server is not RFC compliant, and 0 is returned. + **/ +- (float)serverXmppStreamVersionNumber +{ + return [rootElement attributeFloatValueForName:@"version" withDefaultValue:0.0F]; +} + - (BOOL)sendStanza:(NSXMLElement *)stanza { return YES; @@ -174,5 +185,44 @@ - (void)onSocket:(AsyncSocket *)socket didConnectToHost:(NSString *)givenHost po [asyncSocket readDataWithTimeout:TIMEOUT_READ_START tag:TAG_READ_START]; } +/** + * Called when a socket has completed reading the requested data. Not called if there is an error. + **/ +- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag +{ + if (DEBUG_RECV_PRE) + { + NSString *dataAsStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + DDLogRecvPre(@"RECV: %@", dataAsStr); + [dataAsStr release]; + } + + numberOfBytesReceived += [data length]; + [parser parseData:data]; +} + +////////////////////////////////////// +#pragma mark XMPPParser Delegate +////////////////////////////////////// + +/** + * Called when the xmpp parser has read in the entire root element. + **/ +- (void)xmppParser:(XMPPParser *)sender didReadRoot:(NSXMLElement *)root +{ + DDLogRecvPost(@"RECV: %@", [root compactXMLString]); + + // At this point we've sent our XML stream header, and we've received the response XML stream header. + // We save the root element of our stream for future reference. + // Digest Access authentication requires us to know the ID attribute from the element. + + [rootElement release]; + rootElement = [root retain]; + state = XMPP_SOCKET_CONNECTED; + [multicastDelegate transportDidConnect:self]; +} + + + @end diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 92058c3..df3d69a 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1887,7 +1887,6 @@ - (void)srvResolver:(RFSRVResolver *)sender didNotResolveSRVWithError:(NSError * - (void)transportDidConnect:(id)transport { - [self sendOpeningNegotiation]; [multicastDelegate xmppStreamDidStartNegotiation:self]; } diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index fd00b36..4f34c65 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -19,6 +19,7 @@ - (BOOL)connect:(NSError **)errPtr; - (BOOL)disconnect; - (void)restartStream; +- (float)serverXmppStreamVersionNumber; - (BOOL)sendStanza:(NSXMLElement *)stanza; - (BOOL)sendStanzaWithString:(NSString *)string; @optional From 9f509954578394f096658e702c152a81cae9bb2a Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 18 Mar 2011 22:08:43 +0530 Subject: [PATCH 015/180] Read uptil the tag. --- Core/Transports/XMPPSocketTransport.h | 2 +- Core/Transports/XMPPSocketTransport.m | 34 +++++- Core/XMPPStream.m | 148 ++++++++------------------ Core/XMPPTransportProtocol.h | 4 +- 4 files changed, 80 insertions(+), 108 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index ea3fba2..646cb90 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -11,7 +11,7 @@ #import "MulticastDelegate.h" #if TARGET_OS_IPHONE -#import "DDXML.h" + #import "DDXML.h" #endif @class AsyncSocket; diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index 66925a3..b5b8b71 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -70,7 +70,16 @@ - (float)serverXmppStreamVersionNumber - (BOOL)sendStanza:(NSXMLElement *)stanza { - return YES; + NSString *outgoingStr = [stanza compactXMLString]; + NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; + + DDLogSend(@"SEND: %@", outgoingStr); + numberOfBytesSent += [outgoingData length]; + + [asyncSocket writeData:outgoingData + withTimeout:TIMEOUT_WRITE + tag:TAG_WRITE_STREAM]; + return YES; // FIXME: does this need to be a BOOL? } - (BOOL)sendStanzaWithString:(NSString *)string @@ -193,7 +202,7 @@ - (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)ta if (DEBUG_RECV_PRE) { NSString *dataAsStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - DDLogRecvPre(@"RECV: %@", dataAsStr); + DDLogRecvPre(@"RECV RAW: %@", dataAsStr); [dataAsStr release]; } @@ -205,6 +214,20 @@ - (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)ta #pragma mark XMPPParser Delegate ////////////////////////////////////// +- (void)xmppParser:(XMPPParser *)sender didParseDataOfLength:(NSUInteger)length +{ + // The chunk we read has now been fully parsed. + // Continue reading for XML elements. + if(state == XMPP_SOCKET_OPENING) + { + [asyncSocket readDataWithTimeout:TIMEOUT_READ_START tag:TAG_READ_START]; + } + else + { + [asyncSocket readDataWithTimeout:TIMEOUT_READ_STREAM tag:TAG_READ_STREAM]; + } +} + /** * Called when the xmpp parser has read in the entire root element. **/ @@ -222,7 +245,10 @@ - (void)xmppParser:(XMPPParser *)sender didReadRoot:(NSXMLElement *)root [multicastDelegate transportDidConnect:self]; } - - +- (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element +{ + DDLogRecvPost(@"RECV: %@", [element compactXMLString]); + [multicastDelegate transport:self didReceiveStanza:element]; +} @end diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index df3d69a..967c3ca 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -68,7 +68,6 @@ - (void)setIsSecure:(BOOL)flag; - (void)sendOpeningNegotiation; - (void)setupKeepAliveTimer; - @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -140,6 +139,15 @@ - (id)init return self; } +- (NSXMLElement *)newRootElement +{ + NSString *streamNamespaceURI = @"http://etherx.jabber.org/streams"; + NSXMLElement *element = [[[NSXMLElement alloc] initWithName:@"stream" URI:streamNamespaceURI] retain]; + [element addNamespaceWithPrefix:@"stream" stringValue:streamNamespaceURI]; + [element addNamespaceWithPrefix:@"" stringValue:@"jabber:client"]; + return element; +} + /** * Peer to Peer XMPP initialization. * The stream is a direct client to client connection as outlined in XEP-0174. @@ -1885,9 +1893,44 @@ - (void)srvResolver:(RFSRVResolver *)sender didNotResolveSRVWithError:(NSError * #pragma mark XMPPTransport Delegate ////////////////////////////////////// -- (void)transportDidConnect:(id)transport +- (void)transportDidConnect:(id)sender { - [multicastDelegate xmppStreamDidStartNegotiation:self]; + + // At this point we've sent our XML stream header, and we've received the response XML stream header. + // We save the root element of our stream for future reference. + // Digest Access authentication requires us to know the ID attribute from the element. + + [rootElement release]; + rootElement = [self newRootElement]; + + // Check for RFC compliance + if([transport serverXmppStreamVersionNumber] >= 1.0) + { + // Update state - we're now onto stream negotiations + state = STATE_NEGOTIATING; + + // Note: We're waiting for the now + } + else + { + // The server isn't RFC comliant, and won't be sending any stream features. + + // We would still like to know what authentication features it supports though, + // so we'll use the jabber:iq:auth namespace, which was used prior to the RFC spec. + + // Update state - we're onto psuedo negotiation + state = STATE_NEGOTIATING; + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:auth"]; + + NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"]; + [iq addAttributeWithName:@"type" stringValue:@"get"]; + [iq addChild:query]; + + [transport sendStanza:iq]; + + // Now wait for the response IQ + } } - (void)transportDidSecure:(id)transport @@ -1993,105 +2036,6 @@ - (void)onSocketDidDisconnect:(AsyncSocket *)sock #pragma mark XMPPParser Delegate //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/** - * Called when the xmpp parser has read in the entire root element. -**/ -- (void)xmppParser:(XMPPParser *)sender didReadRoot:(NSXMLElement *)root -{ - DDLogRecvPost(@"RECV: %@", [root compactXMLString]); - - // At this point we've sent our XML stream header, and we've received the response XML stream header. - // We save the root element of our stream for future reference. - // Digest Access authentication requires us to know the ID attribute from the element. - - [rootElement release]; - rootElement = [root retain]; - - if([self isP2P]) - { - // XEP-0174 specifies that SHOULD be sent by the receiver. - // In other words, if we're the recipient we will now send our features. - // But if we're the initiator, we can't depend on receiving their features. - - // Either way, we're connected at this point. - state = STATE_CONNECTED; - - if([self isP2PRecipient]) - { - // Extract the remoteJID: - // - // - - NSString *from = [[rootElement attributeForName:@"from"] stringValue]; - remoteJID = [[XMPPJID jidWithString:from] retain]; - - // Send our stream features. - // To do so we need to ask the delegate to fill it out for us. - - NSXMLElement *streamFeatures = [NSXMLElement elementWithName:@"stream:features"]; - - [multicastDelegate xmppStream:self willSendP2PFeatures:streamFeatures]; - - NSString *outgoingStr = [streamFeatures compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; - - } - - // Make sure the delegate didn't disconnect us in the xmppStream:willSendP2PFeatures: method. - - if([self isConnected]) - { - [multicastDelegate xmppStreamDidConnect:self]; - } - } - else - { - // Check for RFC compliance - if([self serverXmppStreamVersionNumber] >= 1.0) - { - // Update state - we're now onto stream negotiations - state = STATE_NEGOTIATING; - - // Note: We're waiting for the now - } - else - { - // The server isn't RFC comliant, and won't be sending any stream features. - - // We would still like to know what authentication features it supports though, - // so we'll use the jabber:iq:auth namespace, which was used prior to the RFC spec. - - // Update state - we're onto psuedo negotiation - state = STATE_NEGOTIATING; - - NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:auth"]; - - NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"]; - [iq addAttributeWithName:@"type" stringValue:@"get"]; - [iq addChild:query]; - - NSString *outgoingStr = [iq compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; - - // Now wait for the response IQ - } - } -} - - (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element { DDLogRecvPost(@"RECV: %@", [element compactXMLString]); diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 4f34c65..f5460f4 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -7,8 +7,10 @@ // #import +#if TARGET_OS_IPHONE + #import "DDXML.h" +#endif -@class NSXMLElement; @class XMPPJID; @protocol XMPPTransportProtocol From 433f20b4eb78e30d8333939ac99f695c26f2d014 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sat, 19 Mar 2011 02:40:08 +0530 Subject: [PATCH 016/180] Add TLS support. --- Core/Transports/XMPPSocketTransport.h | 8 +- Core/Transports/XMPPSocketTransport.m | 73 +++++--- Core/XMPPStream.m | 259 +++++++++++--------------- Core/XMPPTransportProtocol.h | 5 +- 4 files changed, 167 insertions(+), 178 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index 646cb90..875e9e4 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -64,12 +64,14 @@ enum xmppSocketState { UInt16 port; XMPPJID *myJID; + BOOL isSecure; + int numberOfBytesSent; int numberOfBytesReceived; NSXMLElement *rootElement; } - +@property (readonly) NSString *host; @property (retain) XMPPJID *myJID; - (id)initWithHost:(NSString *)host port:(UInt16)port; @@ -82,6 +84,8 @@ enum xmppSocketState { - (float)serverXmppStreamVersionNumber; - (BOOL)sendStanza:(NSXMLElement *)stanza; - (BOOL)sendStanzaWithString:(NSString *)string; -- (BOOL)secure; + +- (void)secure; +- (BOOL)isSecure; @end diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index b5b8b71..fb60c2a 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -14,6 +14,7 @@ @implementation XMPPSocketTransport +@synthesize host; @synthesize myJID; - (id)initWithHost:(NSString *)givenHost port:(UInt16)givenPort @@ -25,6 +26,7 @@ - (id)initWithHost:(NSString *)givenHost port:(UInt16)givenPort parser = [[XMPPParser alloc] initWithDelegate:self]; host = givenHost; port = givenPort; + isSecure = NO; state = XMPP_SOCKET_DISCONNECTED; numberOfBytesSent = 0; numberOfBytesReceived = 0; @@ -70,26 +72,46 @@ - (float)serverXmppStreamVersionNumber - (BOOL)sendStanza:(NSXMLElement *)stanza { - NSString *outgoingStr = [stanza compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; + return [self sendStanzaWithString:[stanza compactXMLString]]; +} - [asyncSocket writeData:outgoingData +- (BOOL)sendStanzaWithString:(NSString *)string +{ + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; + DDLogSend(@"SEND: %@", string); + numberOfBytesSent += [data length]; + [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE tag:TAG_WRITE_STREAM]; return YES; // FIXME: does this need to be a BOOL? } -- (BOOL)sendStanzaWithString:(NSString *)string +/** + * This method handles starting TLS negotiation on the socket, using the proper settings. + **/ +- (void)secure { - return YES; + // Create a mutable dictionary for security settings + NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:5]; + + // Prompt the delegate(s) to populate the security settings + [multicastDelegate transport:self willSecureWithSettings:settings]; + + // If the delegates didn't respond + if ([settings count] == 0) + { + // Use the default settings, and set the peer name + if (host) + { + [settings setObject:host forKey:(NSString *)kCFStreamSSLPeerName]; + } + } + [asyncSocket startTLS:settings]; } -- (BOOL)secure +- (BOOL)isSecure { - return YES; + return isSecure; } ///////////////////////// @@ -108,14 +130,7 @@ - (void)sendOpeningNegotiation // TCP connection was just opened - We need to include the opening XML stanza NSString *s1 = @""; - NSData *outgoingData = [s1 dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", s1); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_START]; + [self sendStanzaWithString:s1]; } if (state != XMPP_SOCKET_OPENING) @@ -157,15 +172,8 @@ - (void)sendOpeningNegotiation s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream]; } - NSData *outgoingData = [s2 dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", s2); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_START]; - + [self sendStanzaWithString:s2]; + // Update status state = XMPP_SOCKET_NEGOTIATING; @@ -210,6 +218,17 @@ - (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)ta [parser parseData:data]; } +- (void)onSocketDidSecure:(AsyncSocket *)sock +{ + isSecure = YES; + [multicastDelegate transportDidSecure:self]; +} + +- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err +{ + NSLog(@"%@", [err description]); +} + ////////////////////////////////////// #pragma mark XMPPParser Delegate ////////////////////////////////////// diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 967c3ca..d0baca1 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -353,7 +353,9 @@ - (BOOL)connect:(NSError **)errPtr { [transport setMyJID:myJID]; } - return [transport connect:errPtr]; + BOOL result = [transport connect:errPtr]; + state = STATE_OPENING; + return result; } - (BOOL)oldSchoolSecureConnect:(NSError **)errPtr @@ -577,15 +579,7 @@ - (BOOL)supportsStartTLS - (void)sendStartTLSRequest { NSString *starttls = @""; - - NSData *outgoingData = [starttls dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", starttls); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; + [transport sendStanzaWithString:starttls]; } - (BOOL)secureConnection:(NSError **)errPtr @@ -1351,33 +1345,6 @@ - (void)sendOpeningNegotiation } } -/** - * This method handles starting TLS negotiation on the socket, using the proper settings. -**/ -- (void)startTLS -{ - // Create a mutable dictionary for security settings - NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:5]; - - // Prompt the delegate(s) to populate the security settings - [multicastDelegate xmppStream:self willSecureWithSettings:settings]; - - // If the delegates didn't respond - if ([settings count] == 0) - { - // Use the default settings, and set the peer name - if (hostName) - { - [settings setObject:hostName forKey:(NSString *)kCFStreamSSLPeerName]; - } - } - - [asyncSocket startTLS:settings]; - - // Note: We don't need to wait for asyncSocket to complete TLS negotiation. - // We can just continue reading/writing to the socket, and it will handle queueing everything for us! -} - /** * This method is called anytime we receive the server's stream features. * This method looks at the stream features, and handles any requirements so communication can continue. @@ -1498,13 +1465,10 @@ - (void)handleStartTLSResponse:(NSXMLElement *)response } // Start TLS negotiation - [self startTLS]; + [transport secure]; // Make a note of the switch to TLS [self setIsSecure:YES]; - - // Now we start our negotiation over again... - [self sendOpeningNegotiation]; } /** @@ -1933,113 +1897,9 @@ - (void)transportDidConnect:(id)sender } } -- (void)transportDidSecure:(id)transport -{ - [multicastDelegate xmppStreamDidSecure:self]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark AsyncSocket Delegate -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Called when a socket has completed reading the requested data. Not called if there is an error. -**/ -- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag -{ - if (DEBUG_RECV_PRE) - { - NSString *dataAsStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - - DDLogRecvPre(@"RECV: %@", dataAsStr); - - [dataAsStr release]; - } - - numberOfBytesReceived += [data length]; - - [parser parseData:data]; -} - -/** - * Called after data with the given tag has been successfully sent. -**/ -- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag -{ - if ((tag >= 0) && (tag <= UINT16_MAX)) - { - [multicastDelegate xmppStream:self didSendElementWithTag:tag]; - } - else if (tag == TAG_WRITE_SYNCHRONOUS) - { - flags &= ~kSynchronousSendPending; - } -} - -/** - * In the event of an error, the socket is closed. - * When connecting, this delegate method may be called before onSocket:didConnectToHost: -**/ -- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err +- (void)transport:(id)transport didReceiveStanza:(NSXMLElement *)element { - [multicastDelegate xmppStream:self didReceiveError:err]; -} - -/** - * Called when a socket disconnects with or without error. If you want to release a socket after it disconnects, - * do so here. It is not safe to do that during "onSocket:willDisconnectWithError:". -**/ -- (void)onSocketDidDisconnect:(AsyncSocket *)sock -{ - if (srvResults && (++srvResultsIndex < [srvResults count])) - { - [self tryNextSrvResult]; - } - else - { - // Update state - state = STATE_DISCONNECTED; - - // Update configuration - [self setIsSecure:NO]; - [self setIsAuthenticated:NO]; - - // Release the parser (to free underlying resources) - [parser stop]; - [parser release]; - parser = nil; - - // Clear the root element - [rootElement release]; rootElement = nil; - - // Clear any saved authentication information - [tempPassword release]; tempPassword = nil; - - // Clear srv results - [srvResolver release]; srvResolver = nil; - [srvResults release]; srvResults = nil; - - // Clear any synchronous send attempts - [synchronousUUID release]; synchronousUUID = nil; - - // Stop the keep alive timer - [keepAliveTimer invalidate]; - [keepAliveTimer release]; - keepAliveTimer = nil; - - // Notify delegate - [multicastDelegate xmppStreamDidDisconnect:self]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark XMPPParser Delegate -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element -{ - DDLogRecvPost(@"RECV: %@", [element compactXMLString]); - + [rootElement addChild:element]; NSString *elementName = [element name]; if([elementName isEqualToString:@"stream:error"] || [elementName isEqualToString:@"error"]) @@ -2157,7 +2017,7 @@ - (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element [multicastDelegate xmppStream:self didReceivePresence:[XMPPPresence presenceFromElement:element]]; } else if([self isP2P] && - ([elementName isEqualToString:@"stream:features"] || [elementName isEqualToString:@"features"])) + ([elementName isEqualToString:@"stream:features"] || [elementName isEqualToString:@"features"])) { [multicastDelegate xmppStream:self didReceiveP2PFeatures:element]; } @@ -2168,6 +2028,109 @@ - (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element } } +- (void)transportDidSecure:(id)transport +{ + [multicastDelegate xmppStreamDidSecure:self]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark AsyncSocket Delegate +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Called when a socket has completed reading the requested data. Not called if there is an error. +**/ +- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag +{ + if (DEBUG_RECV_PRE) + { + NSString *dataAsStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + DDLogRecvPre(@"RECV: %@", dataAsStr); + + [dataAsStr release]; + } + + numberOfBytesReceived += [data length]; + + [parser parseData:data]; +} + +/** + * Called after data with the given tag has been successfully sent. +**/ +- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag +{ + if ((tag >= 0) && (tag <= UINT16_MAX)) + { + [multicastDelegate xmppStream:self didSendElementWithTag:tag]; + } + else if (tag == TAG_WRITE_SYNCHRONOUS) + { + flags &= ~kSynchronousSendPending; + } +} + +/** + * In the event of an error, the socket is closed. + * When connecting, this delegate method may be called before onSocket:didConnectToHost: +**/ +- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err +{ + [multicastDelegate xmppStream:self didReceiveError:err]; +} + +/** + * Called when a socket disconnects with or without error. If you want to release a socket after it disconnects, + * do so here. It is not safe to do that during "onSocket:willDisconnectWithError:". +**/ +- (void)onSocketDidDisconnect:(AsyncSocket *)sock +{ + if (srvResults && (++srvResultsIndex < [srvResults count])) + { + [self tryNextSrvResult]; + } + else + { + // Update state + state = STATE_DISCONNECTED; + + // Update configuration + [self setIsSecure:NO]; + [self setIsAuthenticated:NO]; + + // Release the parser (to free underlying resources) + [parser stop]; + [parser release]; + parser = nil; + + // Clear the root element + [rootElement release]; rootElement = nil; + + // Clear any saved authentication information + [tempPassword release]; tempPassword = nil; + + // Clear srv results + [srvResolver release]; srvResolver = nil; + [srvResults release]; srvResults = nil; + + // Clear any synchronous send attempts + [synchronousUUID release]; synchronousUUID = nil; + + // Stop the keep alive timer + [keepAliveTimer invalidate]; + [keepAliveTimer release]; + keepAliveTimer = nil; + + // Notify delegate + [multicastDelegate xmppStreamDidDisconnect:self]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark XMPPParser Delegate +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + - (void)xmppParserDidEnd:(XMPPParser *)sender { [asyncSocket disconnect]; diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index f5460f4..904a5e4 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -24,8 +24,10 @@ - (float)serverXmppStreamVersionNumber; - (BOOL)sendStanza:(NSXMLElement *)stanza; - (BOOL)sendStanzaWithString:(NSString *)string; + @optional -- (BOOL)secure; +- (void)secure; +- (BOOL)isSecure; @end @@ -37,6 +39,7 @@ - (void)transportDidStartNegotiation:(id )transport; - (void)transportDidConnect:(id )transport; - (void)transportDidDisconnect:(id )transport; +- (void)transport:(id )transport willSecureWithSettings:(NSDictionary *)settings; - (void)transport:(id )transport didReceiveStanza:(NSXMLElement *)stanza; - (void)transport:(id )transport didReceiveError:(id)error; - (void)transportDidSecure:(id )transport; From 2fe8ac6af04e78192f73b1afce0e3375d78970f2 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sat, 19 Mar 2011 02:59:08 +0530 Subject: [PATCH 017/180] Make stream restarts work. --- Core/Transports/XMPPSocketTransport.h | 3 ++- Core/Transports/XMPPSocketTransport.m | 17 +++++++---------- Core/XMPPStream.m | 5 +++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index 875e9e4..6f0d726 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -51,7 +51,8 @@ enum xmppSocketState { XMPP_SOCKET_DISCONNECTED, XMPP_SOCKET_OPENING, XMPP_SOCKET_NEGOTIATING, - XMPP_SOCKET_CONNECTED + XMPP_SOCKET_CONNECTED, + XMPP_SOCKET_RESTARTING }; @interface XMPPSocketTransport : NSObject { diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index fb60c2a..1de48f2 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -55,11 +55,6 @@ - (BOOL)disconnect return YES; } -- (void)restartStream -{ - -} - /** * Returns the version attribute from the servers's element. * This should be at least 1.0 to be RFC 3920 compliant. @@ -114,10 +109,6 @@ - (BOOL)isSecure return isSecure; } -///////////////////////// -#pragma mark PrivateAPI -///////////////////////// - /** * This method handles sending the opening element which is needed in several situations. **/ @@ -125,7 +116,7 @@ - (void)sendOpeningNegotiation { BOOL isRenegotiation = NO; - if (state == XMPP_SOCKET_OPENING) + if ((state == XMPP_SOCKET_OPENING) || (state == XMPP_SOCKET_RESTARTING)) { // TCP connection was just opened - We need to include the opening XML stanza NSString *s1 = @""; @@ -185,6 +176,12 @@ - (void)sendOpeningNegotiation } } +- (void)restartStream +{ + state = XMPP_SOCKET_RESTARTING; + [self sendOpeningNegotiation]; +} + ////////////////////////////////// #pragma mark AsyncSocket Delegate ////////////////////////////////// diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index d0baca1..f610c58 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1897,7 +1897,7 @@ - (void)transportDidConnect:(id)sender } } -- (void)transport:(id)transport didReceiveStanza:(NSXMLElement *)element +- (void)transport:(id)sender didReceiveStanza:(NSXMLElement *)element { [rootElement addChild:element]; NSString *elementName = [element name]; @@ -2028,9 +2028,10 @@ - (void)transport:(id)transport didReceiveStanza:(NSXMLEl } } -- (void)transportDidSecure:(id)transport +- (void)transportDidSecure:(id)sender { [multicastDelegate xmppStreamDidSecure:self]; + [transport restartStream]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From b5c2916b091bec3654b318d339bcdb00580a2df3 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sat, 19 Mar 2011 03:15:38 +0530 Subject: [PATCH 018/180] Send successful PLAIN auth (tested with gtalk) --- Core/XMPPStream.m | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index f610c58..f79c779 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -906,15 +906,7 @@ - (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr if ([self supportsDigestMD5Authentication]) { NSString *auth = @""; - - NSData *outgoingData = [auth dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", auth); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; + [transport sendStanzaWithString:auth]; // Save authentication information [tempPassword release]; @@ -941,15 +933,7 @@ - (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr [auth addAttributeWithName:@"mechanism" stringValue:@"PLAIN"]; [auth setStringValue:base64]; - NSString *outgoingStr = [auth compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; + [transport sendStanza:auth]; // Update state state = STATE_AUTH_1; @@ -992,16 +976,8 @@ - (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr [iqElement addAttributeWithName:@"type" stringValue:@"set"]; [iqElement addChild:queryElement]; - NSString *outgoingStr = [iqElement compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; - + [transport sendStanza:iqElement]; + // Update state state = STATE_AUTH_1; } @@ -2031,6 +2007,8 @@ - (void)transport:(id)sender didReceiveStanza:(NSXMLEleme - (void)transportDidSecure:(id)sender { [multicastDelegate xmppStreamDidSecure:self]; + [rootElement release]; + rootElement = [self newRootElement]; [transport restartStream]; } From 7e07898115ac724520857287c505e0779a321473 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sat, 19 Mar 2011 03:22:31 +0530 Subject: [PATCH 019/180] Stream restart after plain auth. --- Core/XMPPStream.m | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index f79c779..5233392 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1215,6 +1215,13 @@ - (BOOL)synchronouslySendElement:(NSXMLElement *)element #pragma mark Stream Negotiation //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +- (void)restartStream +{ + [rootElement release]; + rootElement = [self newRootElement]; + [transport restartStream]; +} + /** * This method handles sending the opening element which is needed in several situations. **/ @@ -1557,7 +1564,7 @@ - (void)handleAuth1:(NSXMLElement *)response [self setIsAuthenticated:YES]; // Now we start our negotiation over again... - [self sendOpeningNegotiation]; + [self restartStream]; } } else @@ -1632,7 +1639,7 @@ - (void)handleAuth2:(NSXMLElement *)response [self setIsAuthenticated:YES]; // Now we start our negotiation over again... - [self sendOpeningNegotiation]; + [self restartStream]; } else { @@ -1665,7 +1672,7 @@ - (void)handleAuth3:(NSXMLElement *)response [self setIsAuthenticated:YES]; // Now we start our negotiation over again... - [self sendOpeningNegotiation]; + [self restartStream]; } } @@ -2007,9 +2014,7 @@ - (void)transport:(id)sender didReceiveStanza:(NSXMLEleme - (void)transportDidSecure:(id)sender { [multicastDelegate xmppStreamDidSecure:self]; - [rootElement release]; - rootElement = [self newRootElement]; - [transport restartStream]; + [self restartStream]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From d11d9578b6380cb3fa1f798c53b45863cac8e9e9 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sat, 19 Mar 2011 03:53:54 +0530 Subject: [PATCH 020/180] bind, session and XML stanza exchange works now! --- Core/XMPPStream.m | 52 +++++------------------------------------------ 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 5233392..73511de 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1119,15 +1119,7 @@ - (void)sendElement:(NSXMLElement *)element withTag:(long)tag } } - NSString *outgoingStr = [element compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:tag]; + [transport sendStanza:element]; if ([element isKindOfClass:[XMPPIQ class]]) { @@ -1387,15 +1379,7 @@ - (void)handleStreamFeatures [iq addAttributeWithName:@"type" stringValue:@"set"]; [iq addChild:bind]; - NSString *outgoingStr = [iq compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; + [transport sendStanza:iq]; } else { @@ -1407,15 +1391,7 @@ - (void)handleStreamFeatures [iq addAttributeWithName:@"type" stringValue:@"set"]; [iq addChild:bind]; - NSString *outgoingStr = [iq compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; + [transport sendStanza:iq]; } // We're already listening for the response... @@ -1706,16 +1682,7 @@ - (void)handleBinding:(NSXMLElement *)response [iq addAttributeWithName:@"type" stringValue:@"set"]; [iq addChild:session]; - NSString *outgoingStr = [iq compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; - + [transport sendStanza:iq]; // Update state state = STATE_START_SESSION; } @@ -1738,16 +1705,7 @@ - (void)handleBinding:(NSXMLElement *)response [iq addAttributeWithName:@"type" stringValue:@"set"]; [iq addChild:bind]; - NSString *outgoingStr = [iq compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; - + [transport sendStanza:iq]; // The state remains in STATE_BINDING } } From 3b03dc31bc28c4ea41771aa5a90ed740f33d458b Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sat, 19 Mar 2011 13:55:29 +0530 Subject: [PATCH 021/180] No need to add received stanzas to the rootElement. --- Core/XMPPStream.m | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 73511de..219e671 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1840,7 +1840,6 @@ - (void)transportDidConnect:(id)sender - (void)transport:(id)sender didReceiveStanza:(NSXMLElement *)element { - [rootElement addChild:element]; NSString *elementName = [element name]; if([elementName isEqualToString:@"stream:error"] || [elementName isEqualToString:@"error"]) From 8c8f57be8318d9f89167bbbff84d8f94a591c5e3 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sat, 19 Mar 2011 15:09:21 +0530 Subject: [PATCH 022/180] Handle disconnects. --- Core/Transports/XMPPSocketTransport.h | 2 +- Core/Transports/XMPPSocketTransport.m | 13 ++++++++++--- Core/XMPPStream.m | 19 +++++++++++++++++-- Core/XMPPTransportProtocol.h | 4 +++- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index 6f0d726..9e60d38 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -80,7 +80,7 @@ enum xmppSocketState { - (void)addDelegate:(id)delegate; - (void)removeDelegate:(id)delegate; - (BOOL)connect:(NSError **)errPtr; -- (BOOL)disconnect; +- (void)disconnect; - (void)restartStream; - (float)serverXmppStreamVersionNumber; - (BOOL)sendStanza:(NSXMLElement *)stanza; diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index 1de48f2..18a0a7e 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -50,9 +50,11 @@ - (BOOL)connect:(NSError **)errPtr return [asyncSocket connectToHost:host onPort:port error:errPtr]; } -- (BOOL)disconnect +- (void)disconnect { - return YES; + [self sendStanzaWithString:@""]; + [multicastDelegate transportWillDisconnect:self]; + [asyncSocket disconnect]; } /** @@ -223,7 +225,12 @@ - (void)onSocketDidSecure:(AsyncSocket *)sock - (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err { - NSLog(@"%@", [err description]); + [multicastDelegate transportWillDisconnect:self withError:err]; +} + +- (void)onSocketDidDisconnect:(AsyncSocket *)sock +{ + [multicastDelegate transportDidDisconnect:self]; } ////////////////////////////////////// diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 219e671..97f2ad6 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -530,7 +530,7 @@ - (BOOL)connectP2PWithSocket:(AsyncSocket *)acceptedSocket error:(NSError **)err - (void)disconnect { [multicastDelegate xmppStreamWasToldToDisconnect:self]; - [asyncSocket disconnect]; + [transport disconnect]; // FIXME: this method should be synchronous // Note: The state is updated automatically in the onSocketDidDisconnect: method. } @@ -538,7 +538,7 @@ - (void)disconnect - (void)disconnectAfterSending { [multicastDelegate xmppStreamWasToldToDisconnect:self]; - [asyncSocket disconnectAfterWriting]; + [transport disconnect]; // Note: The state is updated automatically in the onSocketDidDisconnect: method. } @@ -1974,6 +1974,21 @@ - (void)transportDidSecure:(id)sender [self restartStream]; } +- (void)transportWillDisconnect:(id)sender withError:(NSError *)err +{ + [multicastDelegate xmppStream:self didReceiveError:err]; +} + +- (void)transportDidDisconnect:(id)sender +{ + state = STATE_DISCONNECTED; + + [rootElement release]; + rootElement = nil; + + [multicastDelegate xmppStreamDidDisconnect:self]; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark AsyncSocket Delegate //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 904a5e4..9c6f096 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -19,7 +19,7 @@ - (void)removeDelegate:(id)delegate; - (void)setMyJID:(XMPPJID *)jid; - (BOOL)connect:(NSError **)errPtr; -- (BOOL)disconnect; +- (void)disconnect; - (void)restartStream; - (float)serverXmppStreamVersionNumber; - (BOOL)sendStanza:(NSXMLElement *)stanza; @@ -38,6 +38,8 @@ - (void)transportWillConnect:(id )transport; - (void)transportDidStartNegotiation:(id )transport; - (void)transportDidConnect:(id )transport; +- (void)transportWillDisconnect:(id )transport; +- (void)transportWillDisconnect:(id)transport withError:(NSError *)err; - (void)transportDidDisconnect:(id )transport; - (void)transport:(id )transport willSecureWithSettings:(NSDictionary *)settings; - (void)transport:(id )transport didReceiveStanza:(NSXMLElement *)stanza; From 247bf499ba0aeed664d0f4ee0a9b2bb108ea40f0 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sat, 19 Mar 2011 15:16:24 +0530 Subject: [PATCH 023/180] Send stanzas using the transport for registration and remaining auth methods. --- Core/XMPPStream.m | 42 +++++------------------------------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 97f2ad6..074e07f 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -697,15 +697,7 @@ - (BOOL)registerWithPassword:(NSString *)password error:(NSError **)errPtr [iqElement addAttributeWithName:@"type" stringValue:@"set"]; [iqElement addChild:queryElement]; - NSString *outgoingStr = [iqElement compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; + [transport sendStanza:iqElement]; // Update state state = STATE_REGISTERING; @@ -1018,15 +1010,7 @@ - (BOOL)authenticateAnonymously:(NSError **)errPtr NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"]; [auth addAttributeWithName:@"mechanism" stringValue:@"ANONYMOUS"]; - NSString *outgoingStr = [auth compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; + [transport sendStanza:auth]; // Update state state = STATE_AUTH_3; @@ -1505,16 +1489,8 @@ - (void)handleAuth1:(NSXMLElement *)response NSXMLElement *cr = [NSXMLElement elementWithName:@"response" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"]; [cr setStringValue:[auth base64EncodedFullResponse]]; - NSString *outgoingStr = [cr compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; - + [transport sendStanza:cr]; + // Release unneeded resources [auth release]; [tempPassword release]; tempPassword = nil; @@ -1596,15 +1572,7 @@ - (void)handleAuth2:(NSXMLElement *)response // Create and send empty challenge response element NSXMLElement *cr = [NSXMLElement elementWithName:@"response" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"]; - NSString *outgoingStr = [cr compactXMLString]; - NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; + [transport sendStanza:cr]; // The state remains in STATE_AUTH_2 } From 1a3be78f43f1fb6070f73e1d9cb0b0dbe289e9d3 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sun, 20 Mar 2011 02:27:28 +0530 Subject: [PATCH 024/180] Integrate P2P transport with XMPPSocketTransport. This version seems to work for the P2P recipient example from ServerlessDemo. --- Core/Transports/XMPPSocketTransport.h | 9 + Core/Transports/XMPPSocketTransport.m | 120 ++++++++++-- Core/XMPPStream.h | 36 +--- Core/XMPPStream.m | 260 +++----------------------- Core/XMPPTransportProtocol.h | 5 + 5 files changed, 140 insertions(+), 290 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index 9e60d38..31660ab 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -71,11 +71,20 @@ enum xmppSocketState { int numberOfBytesReceived; NSXMLElement *rootElement; + + // P2P stuff + BOOL isP2P; + BOOL isP2PRecipient; + XMPPJID *remoteJID; } @property (readonly) NSString *host; @property (retain) XMPPJID *myJID; +@property (retain) XMPPJID *remoteJID; +@property (readonly) BOOL isP2PRecipient; - (id)initWithHost:(NSString *)host port:(UInt16)port; +- (id)initP2PWithHost:(NSString *)host port:(UInt16)port; +- (id)initP2PWithSocket:(AsyncSocket *)socket; - (void)addDelegate:(id)delegate; - (void)removeDelegate:(id)delegate; diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index 18a0a7e..94931bd 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -11,29 +11,70 @@ #import "AsyncSocket.h" #import "XMPPParser.h" #import "NSXMLElementAdditions.h" +#import "XMPPJID.h" + +@interface XMPPSocketTransport () +- (void)sendOpeningNegotiation; +@end @implementation XMPPSocketTransport @synthesize host; @synthesize myJID; +@synthesize remoteJID; +@synthesize isP2PRecipient; -- (id)initWithHost:(NSString *)givenHost port:(UInt16)givenPort +- (id)init { - if ((self = [super init])) + self = [super init]; + if (self) { multicastDelegate = [[MulticastDelegate alloc] init]; - asyncSocket = [[AsyncSocket alloc] initWithDelegate:self]; parser = [[XMPPParser alloc] initWithDelegate:self]; - host = givenHost; - port = givenPort; isSecure = NO; - state = XMPP_SOCKET_DISCONNECTED; numberOfBytesSent = 0; numberOfBytesReceived = 0; } return self; } +- (id)initWithHost:(NSString *)givenHost port:(UInt16)givenPort +{ + self = [self init]; + if (self) + { + asyncSocket = [[AsyncSocket alloc] initWithDelegate:self]; + host = givenHost; + port = givenPort; + state = XMPP_SOCKET_DISCONNECTED; + } + return self; +} + +- (id)initP2PWithHost:(NSString *)givenHost port:(UInt16)givenPort +{ + self = [self initWithHost:givenHost port:givenPort]; + if (self) + { + isP2P = YES; + } + return self; +} + +- (id)initP2PWithSocket:(AsyncSocket *)socket +{ + self = [self init]; + if (self) + { + asyncSocket = [socket retain]; + [asyncSocket setDelegate:self]; + state = XMPP_SOCKET_OPENING; + isP2P = YES; + isP2PRecipient = YES; + } + return self; +} + - (void)addDelegate:(id)delegate { [multicastDelegate addDelegate:delegate]; @@ -46,8 +87,17 @@ - (void)removeDelegate:(id)delegate - (BOOL)connect:(NSError **)errPtr { - state = XMPP_SOCKET_OPENING; - return [asyncSocket connectToHost:host onPort:port error:errPtr]; + if (isP2P && isP2PRecipient) + { + // Start reading in the peer's XML stream + [asyncSocket readDataWithTimeout:TIMEOUT_READ_START tag:TAG_READ_START]; + return YES; + } + else + { + state = XMPP_SOCKET_OPENING; + return [asyncSocket connectToHost:host onPort:port error:errPtr]; + } } - (void)disconnect @@ -149,20 +199,46 @@ - (void)sendOpeningNegotiation NSString *temp, *s2; - if (myJID) - { - temp = @""; - s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, [myJID domain]]; - } - else if ([host length] > 0) + if (isP2P) { - temp = @""; - s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, host]; + if (myJID && remoteJID) + { + temp = @""; + s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, [myJID bare], [remoteJID bare]]; + } + else if (myJID) + { + temp = @""; + s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, [myJID bare]]; + } + else if (remoteJID) + { + temp = @""; + s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, [remoteJID bare]]; + } + else + { + temp = @""; + s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream]; + } } else { - temp = @""; - s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream]; + if (myJID) + { + temp = @""; + s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, [myJID domain]]; + } + else if ([host length] > 0) + { + temp = @""; + s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, host]; + } + else + { + temp = @""; + s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream]; + } } [self sendStanzaWithString:s2]; @@ -257,6 +333,14 @@ - (void)xmppParser:(XMPPParser *)sender didParseDataOfLength:(NSUInteger)length - (void)xmppParser:(XMPPParser *)sender didReadRoot:(NSXMLElement *)root { DDLogRecvPost(@"RECV: %@", [root compactXMLString]); + + if (isP2PRecipient) + { + NSLog(@"didReadRoot"); + self.remoteJID = [XMPPJID jidWithString:[root attributeStringValueForName:@"from"]]; + [self sendOpeningNegotiation]; + [multicastDelegate transportDidStartNegotiation:self]; + } // At this point we've sent our XML stream header, and we've received the response XML stream header. // We save the root element of our stream for future reference. diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 85cb2e3..717a396 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -116,21 +116,7 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; } - (id)initWithTransport:(id)transport; - -/** - * Standard XMPP initialization. - * The stream is a standard client to server connection. - * - * P2P streams using XEP-0174 are also supported. - * See the P2P section below. -**/ -- (id)init; - -/** - * Peer to Peer XMPP initialization. - * The stream is a direct client to client connection as outlined in XEP-0174. -**/ -- (id)initP2PFrom:(XMPPJID *)myJID; +- (id)initWithP2PTransport:(id)transport; /** * XMPPStream uses a multicast delegate. @@ -215,7 +201,7 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; /** * Only used in P2P streams. **/ -@property (nonatomic, readonly) XMPPJID *remoteJID; +@property (nonatomic, readwrite, copy) XMPPJID *remoteJID; /** * Many routers will teardown a socket mapping if there is no activity on the socket. @@ -303,24 +289,6 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; **/ - (BOOL)oldSchoolSecureConnect:(NSError **)errPtr; -/** - * Starts a P2P connection to the given user and given address. - * This method only works with XMPPStream objects created using the initP2P method. - * - * The given address is specified as a sockaddr structure wrapped in a NSData object. - * For example, a NSData object returned from NSNetservice's addresses method. -**/ -- (BOOL)connectTo:(XMPPJID *)remoteJID withAddress:(NSData *)remoteAddr error:(NSError **)errPtr; - -/** - * Starts a P2P connection with the given accepted socket. - * This method only works with XMPPStream objects created using the initP2P method. - * - * The given socket should be a socket that has already been accepted. - * The remoteJID will be extracted from the opening stream negotiation. -**/ -- (BOOL)connectP2PWithSocket:(AsyncSocket *)acceptedSocket error:(NSError **)errPtr; - /** * Disconnects from the remote host by closing the underlying TCP socket connection. * diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 074e07f..3e8d3a6 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -65,8 +65,6 @@ - (NSString *)base64EncodedFullResponse; @interface XMPPStream (PrivateAPI) - (void)setIsSecure:(BOOL)flag; - -- (void)sendOpeningNegotiation; - (void)setupKeepAliveTimer; @end @@ -79,6 +77,7 @@ @implementation XMPPStream @synthesize hostName; @synthesize hostPort; @synthesize myJID; +@synthesize remoteJID; @synthesize keepAliveInterval; @synthesize numberOfBytesSent; @synthesize numberOfBytesReceived; @@ -108,7 +107,7 @@ - (void)commonInit autoDelegateDict = [[NSMutableDictionary alloc] init]; } --(id)initWithTransport:(id)givenTransport +- (id)initWithTransport:(id)givenTransport { if ((self = [super init])) { @@ -119,24 +118,13 @@ -(id)initWithTransport:(id)givenTransport return self; } -/** - * Standard XMPP initialization. - * The stream is a standard client to server connection. -**/ -- (id)init +- (id)initWithP2PTransport:(id)givenTransport { - if ((self = [super init])) - { - // Common initialization - [self commonInit]; - - // Initialize socket - asyncSocket = [(AsyncSocket *)[AsyncSocket alloc] initWithDelegate:self]; - - // Initialize configuration - flags = 0; - } - return self; + if ((self = [self initWithTransport:givenTransport])) + { + flags = kP2PMode; + } + return self; } - (NSXMLElement *)newRootElement @@ -148,28 +136,6 @@ - (NSXMLElement *)newRootElement return element; } -/** - * Peer to Peer XMPP initialization. - * The stream is a direct client to client connection as outlined in XEP-0174. -**/ -- (id)initP2PFrom:(XMPPJID *)jid -{ - if ((self = [super init])) - { - // Common initialization - [self commonInit]; - - // Store JID - myJID = [jid retain]; - - // We do not initialize the socket, since the connectP2PWithSocket: method might be used. - - // Initialize configuration - flags = kP2PMode; - } - return self; -} - /** * Standard deallocation method. * Every object variable declared in the header file should be released here. @@ -233,16 +199,11 @@ - (BOOL)isP2P return (flags & kP2PMode) ? YES : NO; } -- (BOOL)isP2PInitiator -{ - return (flags & (kP2PMode | kP2PInitiator)) ? YES : NO; -} - - (BOOL)isP2PRecipient { if (flags & kP2PMode) { - return (flags & kP2PInitiator) ? NO : YES; + return [transport isP2PRecipient]; } return NO; } @@ -353,6 +314,10 @@ - (BOOL)connect:(NSError **)errPtr { [transport setMyJID:myJID]; } + if ([self isP2P] && remoteJID) + { + [transport setRemoteJID:remoteJID]; + } BOOL result = [transport connect:errPtr]; state = STATE_OPENING; return result; @@ -438,88 +403,6 @@ - (BOOL)connectTo:(XMPPJID *)jid withAddress:(NSData *)remoteAddr error:(NSError return result; } -/** - * Starts a P2P connection with the given accepted socket. - * This method only works with XMPPStream objects created using the initP2P method. - * - * The given socket should be a socket that has already been accepted. - * The remoteJID will be extracted from the opening stream negotiation. -**/ -- (BOOL)connectP2PWithSocket:(AsyncSocket *)acceptedSocket error:(NSError **)errPtr -{ - if (state != STATE_DISCONNECTED) - { - if (errPtr) - { - NSString *errMsg = @"Attempting to connect while already connected or connecting."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; - } - return NO; - } - - if (![self isP2P]) - { - if (errPtr) - { - NSString *errMsg = @"Non P2P streams must use the connect: method"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidType userInfo:info]; - } - return NO; - } - - if (acceptedSocket == nil) - { - if (errPtr) - { - NSString *errMsg = @"Parameter acceptedSocket is nil."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidParameter userInfo:info]; - } - return NO; - } - - // Turn off P2P initiator flag - flags &= ~kP2PInitiator; - - NSAssert((asyncSocket == nil), @"Forgot to release the previous asyncSocket instance."); - - // Store and configure socket - asyncSocket = [acceptedSocket retain]; - [asyncSocket setDelegate:self]; - - // Notify delegates - [multicastDelegate xmppStreamWillConnect:self]; - - // Update state - state = STATE_CONNECTING; - - if ([self resetByteCountPerConnection]) - { - numberOfBytesSent = 0; - numberOfBytesReceived = 0; - } - - if ([acceptedSocket isConnected]) - { - // Initialize the XML stream - [self sendOpeningNegotiation]; - - // And start reading in the server's XML stream - [asyncSocket readDataWithTimeout:TIMEOUT_READ_START tag:TAG_READ_START]; - } - else - { - // We'll wait for the onSocket:didConnectToHost:onPort: method which will handle everything for us. - } - - return YES; -} - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Disconnect //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1198,112 +1081,6 @@ - (void)restartStream [transport restartStream]; } -/** - * This method handles sending the opening element which is needed in several situations. -**/ -- (void)sendOpeningNegotiation -{ - BOOL isRenegotiation = NO; - - if (state == STATE_CONNECTING) - { - // TCP connection was just opened - We need to include the opening XML stanza - NSString *s1 = @""; - - NSData *outgoingData = [s1 dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", s1); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_START]; - } - - if (state != STATE_CONNECTING) - { - // We're restarting our negotiation. - // This happens, for example, after securing the connection with SSL/TLS. - isRenegotiation = YES; - - // Since we're restarting the XML stream, we need to reset the parser. - [parser stop]; - [parser release]; - - parser = [(XMPPParser *)[XMPPParser alloc] initWithDelegate:self]; - } - else if (parser == nil) - { - // Need to create parser (it was destroyed when the socket was last disconnected) - parser = [(XMPPParser *)[XMPPParser alloc] initWithDelegate:self]; - } - - NSString *xmlns = @"jabber:client"; - NSString *xmlns_stream = @"http://etherx.jabber.org/streams"; - - NSString *temp, *s2; - if ([self isP2P]) - { - if (myJID && remoteJID) - { - temp = @""; - s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, [myJID bare], [remoteJID bare]]; - } - else if (myJID) - { - temp = @""; - s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, [myJID bare]]; - } - else if (remoteJID) - { - temp = @""; - s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, [remoteJID bare]]; - } - else - { - temp = @""; - s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream]; - } - } - else - { - if (myJID) - { - temp = @""; - s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, [myJID domain]]; - } - else if ([hostName length] > 0) - { - temp = @""; - s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, hostName]; - } - else - { - temp = @""; - s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream]; - } - } - - NSData *outgoingData = [s2 dataUsingEncoding:NSUTF8StringEncoding]; - - DDLogSend(@"SEND: %@", s2); - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_START]; - - // Update status - state = STATE_OPENING; - - // For a reneogitation, we need to manually read from the socket. - // This is because we had to reset our parser, which is usually used to continue the reading process. - if (isRenegotiation) - { - [asyncSocket readDataWithTimeout:TIMEOUT_READ_START tag:TAG_READ_START]; - } -} - /** * This method is called anytime we receive the server's stream features. * This method looks at the stream features, and handles any requirements so communication can continue. @@ -1767,14 +1544,21 @@ - (void)srvResolver:(RFSRVResolver *)sender didNotResolveSRVWithError:(NSError * ////////////////////////////////////// - (void)transportDidConnect:(id)sender -{ - +{ // At this point we've sent our XML stream header, and we've received the response XML stream header. // We save the root element of our stream for future reference. // Digest Access authentication requires us to know the ID attribute from the element. [rootElement release]; rootElement = [self newRootElement]; + if ([self isP2P] && [self isP2PRecipient]) + { + self.remoteJID = [transport remoteJID]; + NSXMLElement *streamFeatures = [NSXMLElement elementWithName:@"stream:features"]; + [transport sendStanza:streamFeatures]; + state = STATE_CONNECTED; + return; + } // Check for RFC compliance if([transport serverXmppStreamVersionNumber] >= 1.0) diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 9c6f096..d4c2d0e 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -29,6 +29,11 @@ - (void)secure; - (BOOL)isSecure; +// P2P +- (XMPPJID *)remoteJID; +- (void)setRemoteJID:(XMPPJID *)jid; +- (BOOL)isP2PRecipient; + @end From e08edf75fccc83f73aff85dc8e4040268334719b Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sun, 20 Mar 2011 02:29:07 +0530 Subject: [PATCH 025/180] Update ServerlessDemo to work with P2P transport. --- .../ServerlessDemo/Classes/ChatViewController.m | 10 ++++++++-- .../ServerlessDemo.xcodeproj/project.pbxproj | 17 +++++++++++++++++ Xcode/ServerlessDemo/StreamController.m | 9 ++++++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Xcode/ServerlessDemo/Classes/ChatViewController.m b/Xcode/ServerlessDemo/Classes/ChatViewController.m index b8c826c..d8fa8af 100755 --- a/Xcode/ServerlessDemo/Classes/ChatViewController.m +++ b/Xcode/ServerlessDemo/Classes/ChatViewController.m @@ -10,6 +10,7 @@ #import "XMPPPresence.h" #import "NSXMLElementAdditions.h" #import "NSStringAdditions.h" +#import "XMPPSocketTransport.h" #import @@ -649,10 +650,15 @@ - (void)netServiceDidResolveAddress:(NSNetService *)ns NSLog(@"myJID: %@", myJID); NSLog(@"serviceJID: %@", serviceJID); - xmppStream = [[XMPPStream alloc] initP2PFrom:myJID]; + struct sockaddr_in *addr = (struct sockaddr_in *)[address bytes]; + XMPPSocketTransport *transport = [[XMPPSocketTransport alloc] initP2PWithHost:addrStr port:ntohs(addr->sin_port)]; + xmppStream = [[XMPPStream alloc] initWithP2PTransport:transport]; + + [xmppStream setMyJID:myJID]; + [xmppStream setRemoteJID:serviceJID]; [xmppStream addDelegate:self]; - [xmppStream connectTo:serviceJID withAddress:address error:nil]; + [xmppStream connect:nil]; } [ns stop]; diff --git a/Xcode/ServerlessDemo/ServerlessDemo.xcodeproj/project.pbxproj b/Xcode/ServerlessDemo/ServerlessDemo.xcodeproj/project.pbxproj index ab4d376..d49622c 100755 --- a/Xcode/ServerlessDemo/ServerlessDemo.xcodeproj/project.pbxproj +++ b/Xcode/ServerlessDemo/ServerlessDemo.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 2899E5600DE3E45000AC0155 /* RootViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2899E55F0DE3E45000AC0155 /* RootViewController.xib */; }; 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; }; 28C286E10D94DF7D0034E888 /* RootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C286E00D94DF7D0034E888 /* RootViewController.m */; }; + 54F60D621335246500014278 /* XMPPSocketTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 54F60D611335246500014278 /* XMPPSocketTransport.m */; }; DC84BC7C12440D6B0055A459 /* DDXMLDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = DC84BC7412440D6B0055A459 /* DDXMLDocument.m */; }; DC84BC7D12440D6B0055A459 /* DDXMLElement.m in Sources */ = {isa = PBXBuildFile; fileRef = DC84BC7612440D6B0055A459 /* DDXMLElement.m */; }; DC84BC7E12440D6B0055A459 /* DDXMLNode.m in Sources */ = {isa = PBXBuildFile; fileRef = DC84BC7812440D6B0055A459 /* DDXMLNode.m */; }; @@ -68,6 +69,9 @@ 28C286DF0D94DF7D0034E888 /* RootViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RootViewController.h; sourceTree = ""; }; 28C286E00D94DF7D0034E888 /* RootViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RootViewController.m; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 54F60D5D1335244A00014278 /* XMPPTransportProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPTransportProtocol.h; path = ../../Core/XMPPTransportProtocol.h; sourceTree = ""; }; + 54F60D601335246500014278 /* XMPPSocketTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMPPSocketTransport.h; sourceTree = ""; }; + 54F60D611335246500014278 /* XMPPSocketTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMPPSocketTransport.m; sourceTree = ""; }; 8D1107310486CEB800E47090 /* ServerlessDemo-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "ServerlessDemo-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; DC84BC7012440D5B0055A459 /* idn-int.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "idn-int.h"; path = "../../Vendor/libidn/idn-int.h"; sourceTree = SOURCE_ROOT; }; DC84BC7112440D5B0055A459 /* stringprep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stringprep.h; path = ../../Vendor/libidn/stringprep.h; sourceTree = SOURCE_ROOT; }; @@ -229,9 +233,21 @@ name = Frameworks; sourceTree = ""; }; + 54F60D5F1335246500014278 /* Transports */ = { + isa = PBXGroup; + children = ( + 54F60D601335246500014278 /* XMPPSocketTransport.h */, + 54F60D611335246500014278 /* XMPPSocketTransport.m */, + ); + name = Transports; + path = ../../Core/Transports; + sourceTree = ""; + }; DC84BC6312440D1C0055A459 /* Core */ = { isa = PBXGroup; children = ( + 54F60D5F1335246500014278 /* Transports */, + 54F60D5D1335244A00014278 /* XMPPTransportProtocol.h */, DC84BC9612440DD80055A459 /* XMPP.h */, DC84BCA112440DD80055A459 /* XMPPParser.h */, DC84BCA212440DD80055A459 /* XMPPParser.m */, @@ -492,6 +508,7 @@ DC84BCAF12440DD80055A459 /* XMPPPresence.m in Sources */, DC84BCB012440DD80055A459 /* XMPPReconnect.m in Sources */, DC84BCB112440DD80055A459 /* XMPPStream.m in Sources */, + 54F60D621335246500014278 /* XMPPSocketTransport.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Xcode/ServerlessDemo/StreamController.m b/Xcode/ServerlessDemo/StreamController.m index f84bcde..aa4618c 100755 --- a/Xcode/ServerlessDemo/StreamController.m +++ b/Xcode/ServerlessDemo/StreamController.m @@ -6,6 +6,7 @@ #import "Message.h" #import "NSXMLElementAdditions.h" #import "NSStringAdditions.h" +#import "XMPPSocketTransport.h" #define THIS_FILE @"StreamController" #define THIS_METHOD NSStringFromSelector(_cmd) @@ -168,13 +169,15 @@ - (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UIn NSLog(@"Accepting connection from service: %@", service.serviceDescription); id tag = [self nextXMPPStreamTag]; - - XMPPStream *xmppStream = [[XMPPStream alloc] initP2PFrom:[self myJID]]; + XMPPSocketTransport *transport = [[XMPPSocketTransport alloc] initP2PWithSocket:sock]; + XMPPStream *xmppStream = [[XMPPStream alloc] initWithP2PTransport:transport]; + + [xmppStream setMyJID:[self myJID]]; [xmppStream addDelegate:self]; xmppStream.tag = tag; - [xmppStream connectP2PWithSocket:sock error:nil]; + [xmppStream connect:nil]; [xmppStreams addObject:xmppStream]; [serviceDict setObject:[service objectID] forKey:tag]; From cba4718dd789f8f9b498885b121bec4bdfb77ac2 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sun, 20 Mar 2011 16:17:15 +0530 Subject: [PATCH 026/180] Add SRV resolution to XMPPSocketTransport. --- Core/Transports/XMPPSocketTransport.h | 10 +++- Core/Transports/XMPPSocketTransport.m | 84 ++++++++++++++++++++++++++- Core/XMPPStream.m | 30 ---------- Core/XMPPTransportProtocol.h | 1 + Utilities/RFSRVResolver.h | 24 ++++---- Utilities/RFSRVResolver.m | 21 +++---- 6 files changed, 112 insertions(+), 58 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index 31660ab..067f029 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -17,6 +17,7 @@ @class AsyncSocket; @class XMPPParser; @class XMPPJID; +@class RFSRVResolver; // Define the debugging state #define DEBUG_SEND YES @@ -49,6 +50,7 @@ enum xmppSocketState { XMPP_SOCKET_DISCONNECTED, + XMPP_SOCKET_RESOLVING_SRV, XMPP_SOCKET_OPENING, XMPP_SOCKET_NEGOTIATING, XMPP_SOCKET_CONNECTED, @@ -76,12 +78,18 @@ enum xmppSocketState { BOOL isP2P; BOOL isP2PRecipient; XMPPJID *remoteJID; + + // SRV resolver + RFSRVResolver *srvResolver; + NSArray *srvResults; + NSUInteger srvResultsIndex; } -@property (readonly) NSString *host; +@property (readonly, copy) NSString *host; @property (retain) XMPPJID *myJID; @property (retain) XMPPJID *remoteJID; @property (readonly) BOOL isP2PRecipient; +- (id)init; - (id)initWithHost:(NSString *)host port:(UInt16)port; - (id)initP2PWithHost:(NSString *)host port:(UInt16)port; - (id)initP2PWithSocket:(AsyncSocket *)socket; diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index 94931bd..318c3fe 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -12,14 +12,18 @@ #import "XMPPParser.h" #import "NSXMLElementAdditions.h" #import "XMPPJID.h" +#import "RFSRVResolver.h" @interface XMPPSocketTransport () +@property (readwrite, copy) NSString *host; +@property (readwrite, assign) UInt16 port; - (void)sendOpeningNegotiation; @end @implementation XMPPSocketTransport @synthesize host; +@synthesize port; @synthesize myJID; @synthesize remoteJID; @synthesize isP2PRecipient; @@ -43,7 +47,6 @@ - (id)initWithHost:(NSString *)givenHost port:(UInt16)givenPort self = [self init]; if (self) { - asyncSocket = [[AsyncSocket alloc] initWithDelegate:self]; host = givenHost; port = givenPort; state = XMPP_SOCKET_DISCONNECTED; @@ -93,9 +96,17 @@ - (BOOL)connect:(NSError **)errPtr [asyncSocket readDataWithTimeout:TIMEOUT_READ_START tag:TAG_READ_START]; return YES; } - else + else if ([host length] == 0) + { + state = XMPP_SOCKET_RESOLVING_SRV; + [srvResolver release]; + srvResolver = [[RFSRVResolver resolveWithTransport:self delegate:self] retain]; + return YES; + } + else { state = XMPP_SOCKET_OPENING; + asyncSocket = [[AsyncSocket alloc] initWithDelegate:self]; return [asyncSocket connectToHost:host onPort:port error:errPtr]; } } @@ -336,7 +347,6 @@ - (void)xmppParser:(XMPPParser *)sender didReadRoot:(NSXMLElement *)root if (isP2PRecipient) { - NSLog(@"didReadRoot"); self.remoteJID = [XMPPJID jidWithString:[root attributeStringValueForName:@"from"]]; [self sendOpeningNegotiation]; [multicastDelegate transportDidStartNegotiation:self]; @@ -358,4 +368,72 @@ - (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element [multicastDelegate transport:self didReceiveStanza:element]; } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark RFSRVResolver Delegate +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)tryNextSrvResult +{ + NSError *connectError = nil; + BOOL success = NO; + + while (srvResultsIndex < [srvResults count]) + { + RFSRVRecord *srvRecord = [srvResults objectAtIndex:srvResultsIndex]; + self.host = srvRecord.target; + self.port = srvRecord.port; + + DDLogInfo(@"Got SRV results. Trying %@:%d", self.host, self.port); + success = [self connect:&connectError]; + + if (success) + { + break; + } + else + { + srvResultsIndex++; + } + } + + if (!success) + { + // SRV resolution of the JID domain failed. + // As per the RFC: + // + // "If the SRV lookup fails, the fallback is a normal IPv4/IPv6 address record resolution + // to determine the IP address, using the "xmpp-client" port 5222, registered with the IANA." + // + // In other words, just try connecting to the domain specified in the JID. + + self.host = [myJID domain]; + self.port = 5222; + + success = [self connect:&connectError]; + } + + if (!success) + { + state = XMPP_SOCKET_DISCONNECTED; + + [multicastDelegate transport:self didReceiveError:connectError]; + [multicastDelegate transportDidDisconnect:self]; + } +} + +- (void)srvResolverDidResoveSRV:(RFSRVResolver *)sender +{ + srvResults = [[sender results] copy]; + srvResultsIndex = 0; + + [self tryNextSrvResult]; +} + +- (void)srvResolver:(RFSRVResolver *)sender didNotResolveSRVWithError:(NSError *)srvError +{ + DDLogError(@"%s %@",__PRETTY_FUNCTION__,srvError); + + [self tryNextSrvResult]; +} + @end diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 3e8d3a6..d6bd5e0 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1835,36 +1835,6 @@ - (void)onSocketDidDisconnect:(AsyncSocket *)sock } } -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark XMPPParser Delegate -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)xmppParserDidEnd:(XMPPParser *)sender -{ - [asyncSocket disconnect]; -} - -- (void)xmppParser:(XMPPParser *)sender didFail:(NSError *)error -{ - [multicastDelegate xmppStream:self didReceiveError:error]; - - [asyncSocket disconnect]; -} - -- (void)xmppParser:(XMPPParser *)sender didParseDataOfLength:(NSUInteger)length -{ - // The chunk we read has now been fully parsed. - // Continue reading for XML elements. - if(state == STATE_OPENING) - { - [asyncSocket readDataWithTimeout:TIMEOUT_READ_START tag:TAG_READ_START]; - } - else - { - [asyncSocket readDataWithTimeout:TIMEOUT_READ_STREAM tag:TAG_READ_STREAM]; - } -} - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Keep Alive //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index d4c2d0e..7dd9ceb 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -17,6 +17,7 @@ - (void)addDelegate:(id)delegate; - (void)removeDelegate:(id)delegate; +- (XMPPJID *)myJID; - (void)setMyJID:(XMPPJID *)jid; - (BOOL)connect:(NSError **)errPtr; - (void)disconnect; diff --git a/Utilities/RFSRVResolver.h b/Utilities/RFSRVResolver.h index cb51e97..f3d4463 100644 --- a/Utilities/RFSRVResolver.h +++ b/Utilities/RFSRVResolver.h @@ -9,7 +9,7 @@ #import #import -#import "XMPPStream.h" +#import "XMPPTransportProtocol.h" #import "DDLog.h" @@ -40,19 +40,19 @@ extern NSString * kRFSRVResolverErrorDomain; @interface RFSRVResolver : NSObject { - XMPPStream * _xmppStream; + id _transport; - id _delegate; + id _delegate; - BOOL _finished; - NSError * _error; - NSMutableArray * _results; - DNSServiceRef _sdRef; - CFSocketRef _sdRefSocket; - NSTimer * _timeoutTimer; + BOOL _finished; + NSError * _error; + NSMutableArray * _results; + DNSServiceRef _sdRef; + CFSocketRef _sdRefSocket; + NSTimer * _timeoutTimer; } -@property (nonatomic, retain, readonly) XMPPStream * xmppStream; +@property (nonatomic, retain, readonly) id transport; @property (nonatomic, assign, readwrite) id delegate; @property (nonatomic, assign, readonly, getter=isFinished) BOOL finished; // observable @@ -60,9 +60,9 @@ extern NSString * kRFSRVResolverErrorDomain; @property (nonatomic, retain, readonly) NSArray * results; // of RFSRVRecord, observable -+ (RFSRVResolver *)resolveWithStream:(XMPPStream *)xmppStream delegate:(id)delegate; ++ (RFSRVResolver *)resolveWithTransport:(id)transport delegate:(id)delegate; -- (id)initWithStream:(XMPPStream *)xmppStream; +- (id)initWithTransport:(id)transport; - (void)start; - (void)stop; diff --git a/Utilities/RFSRVResolver.m b/Utilities/RFSRVResolver.m index 94f0ac2..3bb7053 100644 --- a/Utilities/RFSRVResolver.m +++ b/Utilities/RFSRVResolver.m @@ -11,8 +11,6 @@ #include #include -#import "XMPPStream.h" - NSString * kRFSRVResolverErrorDomain = @"kRFSRVResolverErrorDomain"; @@ -96,7 +94,7 @@ - (NSComparisonResult)compareByPriority:(RFSRVRecord *)aRecord @interface RFSRVResolver() // Redeclare some external properties as read/write -@property (nonatomic, retain, readwrite) XMPPStream * xmppStream; +@property (nonatomic, retain, readwrite) id transport; @property (nonatomic, assign, readwrite, getter=isFinished) BOOL finished; @property (nonatomic, retain, readwrite) NSError * error; @@ -119,7 +117,7 @@ - (void)sortResults; @implementation RFSRVResolver -@synthesize xmppStream = _xmppStream; +@synthesize transport = _transport; @synthesize delegate = _delegate; @synthesize finished = _finished; @@ -129,19 +127,19 @@ @implementation RFSRVResolver #pragma mark Init methods -+ (RFSRVResolver *)resolveWithStream:(XMPPStream *)aXmppStream - delegate:(id)delegate ++ (RFSRVResolver *)resolveWithTransport:(id)transport + delegate:(id)delegate { - RFSRVResolver *srvResolver = [[[RFSRVResolver alloc] initWithStream:aXmppStream] autorelease]; + RFSRVResolver *srvResolver = [[[RFSRVResolver alloc] initWithTransport:transport] autorelease]; srvResolver.delegate = delegate; [srvResolver start]; return srvResolver; } -- (id)initWithStream:(XMPPStream *)xmppStream +- (id)initWithTransport:(id)transport { - if (self = [super init]) { - self.xmppStream = xmppStream; + if ((self = [super init])) { + self.transport = transport; self.results = [NSMutableArray arrayWithCapacity:1]; } return self; @@ -152,7 +150,6 @@ - (void)dealloc [self _closeSockets]; [_error release]; [_results release]; - [_xmppStream release]; [_timeoutTimer invalidate]; [_timeoutTimer release]; @@ -414,7 +411,7 @@ - (void)_start err = kDNSServiceErr_NoError; - NSString *srvName = [NSString stringWithFormat:@"_xmpp-client._tcp.%@", [[self.xmppStream myJID] domain]]; + NSString *srvName = [NSString stringWithFormat:@"_xmpp-client._tcp.%@", [[self.transport myJID] domain]]; DDLogVerbose(@"%s Looking up %@...",__PRETTY_FUNCTION__,srvName); From 7fd7a21bc2bcb458c4c67606c3320ce853c60f49 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sun, 20 Mar 2011 16:58:36 +0530 Subject: [PATCH 027/180] Add keep alive to XMPPSocketTransport. --- Core/Transports/XMPPSocketTransport.h | 4 ++ Core/Transports/XMPPSocketTransport.m | 74 +++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index 067f029..bd785f6 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -74,6 +74,10 @@ enum xmppSocketState { NSXMLElement *rootElement; + // keep alive + NSTimeInterval keepAliveInterval; + NSTimer *keepAliveTimer; + // P2P stuff BOOL isP2P; BOOL isP2PRecipient; diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index 318c3fe..02417a9 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -17,6 +17,7 @@ @interface XMPPSocketTransport () @property (readwrite, copy) NSString *host; @property (readwrite, assign) UInt16 port; +- (BOOL)sendString:(NSString *)string; - (void)sendOpeningNegotiation; @end @@ -38,6 +39,7 @@ - (id)init isSecure = NO; numberOfBytesSent = 0; numberOfBytesReceived = 0; + keepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; } return self; } @@ -78,6 +80,13 @@ - (id)initP2PWithSocket:(AsyncSocket *)socket return self; } +- (void)dealloc +{ + [keepAliveTimer invalidate]; + [keepAliveTimer release]; + [super dealloc]; +} + - (void)addDelegate:(id)delegate { [multicastDelegate addDelegate:delegate]; @@ -113,7 +122,7 @@ - (BOOL)connect:(NSError **)errPtr - (void)disconnect { - [self sendStanzaWithString:@""]; + [self sendString:@""]; [multicastDelegate transportWillDisconnect:self]; [asyncSocket disconnect]; } @@ -128,12 +137,7 @@ - (float)serverXmppStreamVersionNumber return [rootElement attributeFloatValueForName:@"version" withDefaultValue:0.0F]; } -- (BOOL)sendStanza:(NSXMLElement *)stanza -{ - return [self sendStanzaWithString:[stanza compactXMLString]]; -} - -- (BOOL)sendStanzaWithString:(NSString *)string +- (BOOL)sendString:(NSString *)string { NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; DDLogSend(@"SEND: %@", string); @@ -144,6 +148,16 @@ - (BOOL)sendStanzaWithString:(NSString *)string return YES; // FIXME: does this need to be a BOOL? } +- (BOOL)sendStanzaWithString:(NSString *)string +{ + return [self sendString:string]; +} + +- (BOOL)sendStanza:(NSXMLElement *)stanza +{ + return [self sendStanzaWithString:[stanza compactXMLString]]; +} + /** * This method handles starting TLS negotiation on the socket, using the proper settings. **/ @@ -184,7 +198,7 @@ - (void)sendOpeningNegotiation // TCP connection was just opened - We need to include the opening XML stanza NSString *s1 = @""; - [self sendStanzaWithString:s1]; + [self sendString:s1]; } if (state != XMPP_SOCKET_OPENING) @@ -252,7 +266,7 @@ - (void)sendOpeningNegotiation } } - [self sendStanzaWithString:s2]; + [self sendString:s2]; // Update status state = XMPP_SOCKET_NEGOTIATING; @@ -271,6 +285,47 @@ - (void)restartStream [self sendOpeningNegotiation]; } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Keep Alive +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)setupKeepAliveTimer +{ + [keepAliveTimer invalidate]; + [keepAliveTimer release]; + keepAliveTimer = nil; + + if (state == XMPP_SOCKET_CONNECTED) + { + if (keepAliveInterval > 0) + { + keepAliveTimer = [[NSTimer scheduledTimerWithTimeInterval:keepAliveInterval + target:self + selector:@selector(keepAlive:) + userInfo:nil + repeats:YES] retain]; + } + } +} + +- (void)setKeepAliveInterval:(NSTimeInterval)interval +{ + if (keepAliveInterval != interval) + { + keepAliveInterval = interval; + + [self setupKeepAliveTimer]; + } +} + +- (void)keepAlive:(NSTimer *)aTimer +{ + if (state == XMPP_SOCKET_CONNECTED) + { + [self sendString:@" "]; + } +} + ////////////////////////////////// #pragma mark AsyncSocket Delegate ////////////////////////////////// @@ -359,6 +414,7 @@ - (void)xmppParser:(XMPPParser *)sender didReadRoot:(NSXMLElement *)root [rootElement release]; rootElement = [root retain]; state = XMPP_SOCKET_CONNECTED; + [self setupKeepAliveTimer]; [multicastDelegate transportDidConnect:self]; } From 4af550d931390ca8e8744eb15fe58af42d64284e Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Mon, 21 Mar 2011 13:59:16 +0530 Subject: [PATCH 028/180] BoshTransport Initial Commit --- Xcode/iPhoneXMPP/BoshTransport.h | 51 ++++++++ Xcode/iPhoneXMPP/BoshTransport.m | 116 ++++++++++++++++++ .../iPhoneXMPP.xcodeproj/project.pbxproj | 84 +++++++++++++ 3 files changed, 251 insertions(+) create mode 100644 Xcode/iPhoneXMPP/BoshTransport.h create mode 100644 Xcode/iPhoneXMPP/BoshTransport.m diff --git a/Xcode/iPhoneXMPP/BoshTransport.h b/Xcode/iPhoneXMPP/BoshTransport.h new file mode 100644 index 0000000..b070410 --- /dev/null +++ b/Xcode/iPhoneXMPP/BoshTransport.h @@ -0,0 +1,51 @@ +// +// BoshTransport.h +// iPhoneXMPP +// +// Created by Satyam Shekhar on 3/17/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import "XMPPTransportProtocol.h" + + +typedef enum { + DISCONNECTED = 0, + CONNECTING = 1, + CONNECTED = 2 +} BoshConnectionState; + +@interface BoshTransport : NSObject { + NSString *version; + NSInteger wait; + NSInteger hold; + NSString *from; + NSInteger ack; + + u_int32_t rid; + NSInteger sid; + NSString *lang; + NSString *contentType; + NSString *jid; + NSInteger polling; + NSInteger inactivity; + NSInteger requests; + NSString *to; + id delegate; +} + +@property(copy) NSString *jid; +@property(retain) id delegate; +@property(assign) NSInteger wait; +@property(assign) NSInteger hold; +@property(copy) NSString *lang; +@property(copy) NSString *contentType; + +- (id)init; +- (id)initWithDelegate:(id)delegate; +- (BOOL)connect:(NSError **)error; +- (BOOL)disconnect; +- (BOOL)sendStanza:(NSXMLElement *)stanza; + +@end \ No newline at end of file diff --git a/Xcode/iPhoneXMPP/BoshTransport.m b/Xcode/iPhoneXMPP/BoshTransport.m new file mode 100644 index 0000000..9c529c3 --- /dev/null +++ b/Xcode/iPhoneXMPP/BoshTransport.m @@ -0,0 +1,116 @@ +// +// BoshTransport.m +// iPhoneXMPP +// +// Created by Satyam Shekhar on 3/17/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "BoshTransport.h" +#import "DDXML.h" +#import "NSXMLElementAdditions.h" + +@interface BoshTransport() +- (void) sendData; +- (u_int32_t) generateRid; +- (NSString *) newRequestWithPayload:(NSXMLElement *)payload attributes:(NSArray *)attributes namespaces:(NSArray *)namespaces; +- (NSArray *) createAttribureArrayFromDictionary:(NSDictionary *)attributes; +- (NSArray *) createNamespaceArrayFromDictionary:(NSDictionary *)namespacesDictionary; +@end + +@implementation BoshTransport + +@synthesize jid; +@synthesize delegate; +@synthesize wait; +@synthesize hold; +@synthesize lang; +@synthesize contentType; + +- (u_int32_t) generateRid { + return (arc4random() % 1000000000LL + 1000000001LL); +} + +- (NSArray *) createArrayFromDictionary:(NSDictionary *)dictionary of:(NSString *)type { + NSMutableArray *array = [[NSMutableArray alloc] init]; + NSString *key; + for (key in dictionary) { + NSString *value = [dictionary objectForKey:key]; + NSXMLNode *node; + if([type isEqualToString:@"attributes"]) { + node = [NSXMLNode attributeWithName:key stringValue:value]; + } + else { + node = [NSXMLNode namespaceWithName:key stringValue:value]; + } + [array addObject:node]; + } + return array; +} + +- (NSArray *) createArributeArrayFromDictionary:(NSDictionary *)attributesDictionary { + return [self createArrayFromDictionary:attributesDictionary of:@"attributes"]; +} + +- (NSArray *) createNamespaceArrayFromDictionary:(NSDictionary *)namespacesDictionary { + return [self createArrayFromDictionary:namespacesDictionary of:@"namespaces"]; +} + +-(NSString *) newRequestWithPayload:(NSXMLElement *)payload attributes:(NSArray *)attributes namespaces:(NSArray *)namespaces { + NSXMLElement *boshRequest = [[NSXMLElement alloc] initWithName:@"body"]; + + [boshRequest setNamespaces:namespaces]; + [boshRequest setAttributes:attributes]; + [boshRequest addChild:payload]; + + NSString *serializedRequest = [boshRequest compactXMLString]; + [boshRequest release]; + return serializedRequest; +} + +- (void) sendData { + return; +} + +- (id) init { + return [self initWithDelegate:nil]; +} + +- (id)initWithDelegate:(id)delegateToSet { + self = [super init]; + if(self) { + version = @"1.6"; + lang = @"en"; + contentType = @"text/xml; charset=utf-8"; + wait = 60; + hold = 1; + ack = 1; + + rid = [self generateRid]; + delegate = delegateToSet; + + sid = 0; + polling = 0; + inactivity = 0; + requests = 0; + } + return self; +} + +- (BOOL)connect:(NSError **)error { + NSArray *objects = [[NSArray alloc] initWithObjects:@"content", @"hold", @"rid", @"to", @"ver", @"wait", @"ack", @"xml:lang", nil]; + NSArray *keys = [[NSArray alloc] initWithObjects:contentType, [NSString stringWithFormat:@"%d", hold], [NSString stringWithFormat:@"%d", rid], ; + NSDictionary *headers = [[NSDictionary alloc] initWithObjects:objects forKeys:keys]; + [self newRequestWithPayload:nil attributes:headers]; + return TRUE; +} + +- (BOOL)disconnect { + return TRUE; +}; + +- (BOOL) sendStanza:(NSXMLElement *)stanza { + return TRUE; +} + +@end diff --git a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj index 4307e68..4d98722 100755 --- a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj +++ b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj @@ -17,6 +17,18 @@ 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; }; 28C286E10D94DF7D0034E888 /* RootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C286E00D94DF7D0034E888 /* RootViewController.m */; }; 28F335F11007B36200424DE2 /* RootViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28F335F01007B36200424DE2 /* RootViewController.xib */; }; + 4A97CB60133201C6009028BE /* BoshTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB5F133201C6009028BE /* BoshTransport.m */; }; + 4A97CB751332094B009028BE /* ASIAuthenticationDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB621332094B009028BE /* ASIAuthenticationDialog.m */; }; + 4A97CB761332094B009028BE /* ASIDataCompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB651332094B009028BE /* ASIDataCompressor.m */; }; + 4A97CB771332094B009028BE /* ASIDataDecompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB671332094B009028BE /* ASIDataDecompressor.m */; }; + 4A97CB781332094B009028BE /* ASIDownloadCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB691332094B009028BE /* ASIDownloadCache.m */; }; + 4A97CB791332094B009028BE /* ASIFormDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB6B1332094B009028BE /* ASIFormDataRequest.m */; }; + 4A97CB7A1332094B009028BE /* ASIHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB6D1332094B009028BE /* ASIHTTPRequest.m */; }; + 4A97CB7B1332094B009028BE /* ASIInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB711332094B009028BE /* ASIInputStream.m */; }; + 4A97CB7C1332094B009028BE /* ASINetworkQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB731332094B009028BE /* ASINetworkQueue.m */; }; + 4A97CB7F1332096A009028BE /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB7E1332096A009028BE /* Reachability.m */; }; + 4A97CBDB1332314C009028BE /* libz.1.2.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A97CBDA1332314C009028BE /* libz.1.2.3.dylib */; }; + 4A97CBDF13323191009028BE /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A97CBDE13323191009028BE /* MobileCoreServices.framework */; }; 54FDBEC41330DA030099D4DB /* XMPPSocketTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FDBEC31330DA030099D4DB /* XMPPSocketTransport.m */; }; DC1F97E21152CA2D00138A8F /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1F97E11152CA2D00138A8F /* libxml2.dylib */; }; DC1F97E81152CA4E00138A8F /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1F97E71152CA4E00138A8F /* CFNetwork.framework */; }; @@ -68,6 +80,32 @@ 28C286E00D94DF7D0034E888 /* RootViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RootViewController.m; sourceTree = ""; }; 28F335F01007B36200424DE2 /* RootViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RootViewController.xib; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 4A97CB5E133201C6009028BE /* BoshTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BoshTransport.h; sourceTree = ""; }; + 4A97CB5F133201C6009028BE /* BoshTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BoshTransport.m; sourceTree = ""; }; + 4A97CB611332094B009028BE /* ASIAuthenticationDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIAuthenticationDialog.h; path = "../../../asi-http-request/Classes/ASIAuthenticationDialog.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB621332094B009028BE /* ASIAuthenticationDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIAuthenticationDialog.m; path = "../../../asi-http-request/Classes/ASIAuthenticationDialog.m"; sourceTree = SOURCE_ROOT; }; + 4A97CB631332094B009028BE /* ASICacheDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASICacheDelegate.h; path = "../../../asi-http-request/Classes/ASICacheDelegate.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB641332094B009028BE /* ASIDataCompressor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIDataCompressor.h; path = "../../../asi-http-request/Classes/ASIDataCompressor.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB651332094B009028BE /* ASIDataCompressor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIDataCompressor.m; path = "../../../asi-http-request/Classes/ASIDataCompressor.m"; sourceTree = SOURCE_ROOT; }; + 4A97CB661332094B009028BE /* ASIDataDecompressor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIDataDecompressor.h; path = "../../../asi-http-request/Classes/ASIDataDecompressor.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB671332094B009028BE /* ASIDataDecompressor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIDataDecompressor.m; path = "../../../asi-http-request/Classes/ASIDataDecompressor.m"; sourceTree = SOURCE_ROOT; }; + 4A97CB681332094B009028BE /* ASIDownloadCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIDownloadCache.h; path = "../../../asi-http-request/Classes/ASIDownloadCache.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB691332094B009028BE /* ASIDownloadCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIDownloadCache.m; path = "../../../asi-http-request/Classes/ASIDownloadCache.m"; sourceTree = SOURCE_ROOT; }; + 4A97CB6A1332094B009028BE /* ASIFormDataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIFormDataRequest.h; path = "../../../asi-http-request/Classes/ASIFormDataRequest.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB6B1332094B009028BE /* ASIFormDataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIFormDataRequest.m; path = "../../../asi-http-request/Classes/ASIFormDataRequest.m"; sourceTree = SOURCE_ROOT; }; + 4A97CB6C1332094B009028BE /* ASIHTTPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIHTTPRequest.h; path = "../../../asi-http-request/Classes/ASIHTTPRequest.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB6D1332094B009028BE /* ASIHTTPRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIHTTPRequest.m; path = "../../../asi-http-request/Classes/ASIHTTPRequest.m"; sourceTree = SOURCE_ROOT; }; + 4A97CB6E1332094B009028BE /* ASIHTTPRequestConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIHTTPRequestConfig.h; path = "../../../asi-http-request/Classes/ASIHTTPRequestConfig.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB6F1332094B009028BE /* ASIHTTPRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIHTTPRequestDelegate.h; path = "../../../asi-http-request/Classes/ASIHTTPRequestDelegate.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB701332094B009028BE /* ASIInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIInputStream.h; path = "../../../asi-http-request/Classes/ASIInputStream.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB711332094B009028BE /* ASIInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIInputStream.m; path = "../../../asi-http-request/Classes/ASIInputStream.m"; sourceTree = SOURCE_ROOT; }; + 4A97CB721332094B009028BE /* ASINetworkQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASINetworkQueue.h; path = "../../../asi-http-request/Classes/ASINetworkQueue.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB731332094B009028BE /* ASINetworkQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASINetworkQueue.m; path = "../../../asi-http-request/Classes/ASINetworkQueue.m"; sourceTree = SOURCE_ROOT; }; + 4A97CB741332094B009028BE /* ASIProgressDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIProgressDelegate.h; path = "../../../asi-http-request/Classes/ASIProgressDelegate.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB7D1332096A009028BE /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Reachability.h; path = "../../../asi-http-request/External/Reachability/Reachability.h"; sourceTree = SOURCE_ROOT; }; + 4A97CB7E1332096A009028BE /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Reachability.m; path = "../../../asi-http-request/External/Reachability/Reachability.m"; sourceTree = SOURCE_ROOT; }; + 4A97CBDA1332314C009028BE /* libz.1.2.3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.1.2.3.dylib; path = usr/lib/libz.1.2.3.dylib; sourceTree = SDKROOT; }; + 4A97CBDE13323191009028BE /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 54FDBEC01330D8460099D4DB /* XMPPTransportProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPTransportProtocol.h; path = ../../Core/XMPPTransportProtocol.h; sourceTree = ""; }; 54FDBEC21330DA030099D4DB /* XMPPSocketTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPSocketTransport.h; path = ../../Core/Transports/XMPPSocketTransport.h; sourceTree = ""; }; 54FDBEC31330DA030099D4DB /* XMPPSocketTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPSocketTransport.m; path = ../../Core/Transports/XMPPSocketTransport.m; sourceTree = ""; }; @@ -152,6 +190,8 @@ DC1F985D1152CC2B00138A8F /* CoreData.framework in Frameworks */, DCD4944312234A49004BEE1A /* libresolv.dylib in Frameworks */, DC84BC1812440C500055A459 /* libidn.a in Frameworks */, + 4A97CBDB1332314C009028BE /* libz.1.2.3.dylib in Frameworks */, + 4A97CBDF13323191009028BE /* MobileCoreServices.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -186,6 +226,8 @@ 29B97317FDCFA39411CA2CEA /* Resources */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, + 4A97CBDA1332314C009028BE /* libz.1.2.3.dylib */, + 4A97CBDE13323191009028BE /* MobileCoreServices.framework */, ); name = CustomTemplate; sourceTree = ""; @@ -225,11 +267,42 @@ name = Frameworks; sourceTree = ""; }; + 4A97CB431331FC49009028BE /* HTTP */ = { + isa = PBXGroup; + children = ( + 4A97CB7D1332096A009028BE /* Reachability.h */, + 4A97CB7E1332096A009028BE /* Reachability.m */, + 4A97CB611332094B009028BE /* ASIAuthenticationDialog.h */, + 4A97CB621332094B009028BE /* ASIAuthenticationDialog.m */, + 4A97CB631332094B009028BE /* ASICacheDelegate.h */, + 4A97CB641332094B009028BE /* ASIDataCompressor.h */, + 4A97CB651332094B009028BE /* ASIDataCompressor.m */, + 4A97CB661332094B009028BE /* ASIDataDecompressor.h */, + 4A97CB671332094B009028BE /* ASIDataDecompressor.m */, + 4A97CB681332094B009028BE /* ASIDownloadCache.h */, + 4A97CB691332094B009028BE /* ASIDownloadCache.m */, + 4A97CB6A1332094B009028BE /* ASIFormDataRequest.h */, + 4A97CB6B1332094B009028BE /* ASIFormDataRequest.m */, + 4A97CB6C1332094B009028BE /* ASIHTTPRequest.h */, + 4A97CB6D1332094B009028BE /* ASIHTTPRequest.m */, + 4A97CB6E1332094B009028BE /* ASIHTTPRequestConfig.h */, + 4A97CB6F1332094B009028BE /* ASIHTTPRequestDelegate.h */, + 4A97CB701332094B009028BE /* ASIInputStream.h */, + 4A97CB711332094B009028BE /* ASIInputStream.m */, + 4A97CB721332094B009028BE /* ASINetworkQueue.h */, + 4A97CB731332094B009028BE /* ASINetworkQueue.m */, + 4A97CB741332094B009028BE /* ASIProgressDelegate.h */, + ); + name = HTTP; + sourceTree = ""; + }; 54FDBEC51330DA0C0099D4DB /* Transports */ = { isa = PBXGroup; children = ( 54FDBEC21330DA030099D4DB /* XMPPSocketTransport.h */, 54FDBEC31330DA030099D4DB /* XMPPSocketTransport.m */, + 4A97CB5E133201C6009028BE /* BoshTransport.h */, + 4A97CB5F133201C6009028BE /* BoshTransport.m */, ); name = Transports; sourceTree = ""; @@ -385,6 +458,7 @@ DC84BBA0124409CE0055A459 /* Vendor */ = { isa = PBXGroup; children = ( + 4A97CB431331FC49009028BE /* HTTP */, DC84BBA1124409D60055A459 /* IDN */, DC1F97E31152CA3500138A8F /* TCP */, DC1F97D01152C9DC00138A8F /* KissXML */, @@ -494,6 +568,16 @@ 0771B81D1315E9FB0018D5C6 /* XMPPRosterCoreDataStorage+shared.m in Sources */, 0771B81E1315E9FC0018D5C6 /* XMPPStreamCoreDataStorage.m in Sources */, 54FDBEC41330DA030099D4DB /* XMPPSocketTransport.m in Sources */, + 4A97CB60133201C6009028BE /* BoshTransport.m in Sources */, + 4A97CB751332094B009028BE /* ASIAuthenticationDialog.m in Sources */, + 4A97CB761332094B009028BE /* ASIDataCompressor.m in Sources */, + 4A97CB771332094B009028BE /* ASIDataDecompressor.m in Sources */, + 4A97CB781332094B009028BE /* ASIDownloadCache.m in Sources */, + 4A97CB791332094B009028BE /* ASIFormDataRequest.m in Sources */, + 4A97CB7A1332094B009028BE /* ASIHTTPRequest.m in Sources */, + 4A97CB7B1332094B009028BE /* ASIInputStream.m in Sources */, + 4A97CB7C1332094B009028BE /* ASINetworkQueue.m in Sources */, + 4A97CB7F1332096A009028BE /* Reachability.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From f7b6feab08333ae28a6bf097f714202f6e7c35e9 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 21 Mar 2011 14:00:04 +0530 Subject: [PATCH 029/180] Remove unnecessary code from XMPPStream. Also remove XMPPReconnect for now. At a later point, we will integrate this with the socket transport. --- Core/XMPP.h | 1 - Core/XMPPStream.h | 148 ---------------- Core/XMPPStream.m | 423 ++-------------------------------------------- 3 files changed, 10 insertions(+), 562 deletions(-) diff --git a/Core/XMPP.h b/Core/XMPP.h index a2329a5..8c8622d 100644 --- a/Core/XMPP.h +++ b/Core/XMPP.h @@ -4,6 +4,5 @@ #import "XMPPIQ.h" #import "XMPPMessage.h" #import "XMPPPresence.h" -#import "XMPPReconnect.h" #import "NSXMLElementAdditions.h" diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 717a396..cfe4ac2 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -19,8 +19,6 @@ // Define the various states we'll use to track our progress enum { STATE_DISCONNECTED, - STATE_RESOLVING_SRV, - STATE_CONNECTING, STATE_OPENING, STATE_NEGOTIATING, STATE_STARTTLS, @@ -33,35 +31,6 @@ enum { STATE_CONNECTED, }; -// Define the debugging state -#define DEBUG_SEND YES -#define DEBUG_RECV_PRE NO // Prints data before going to xmpp parser -#define DEBUG_RECV_POST YES // Prints data as it comes out of xmpp parser - -#define DDLogSend(format, ...) do{ if(DEBUG_SEND) NSLog((format), ##__VA_ARGS__); }while(0) -#define DDLogRecvPre(format, ...) do{ if(DEBUG_RECV_PRE) NSLog((format), ##__VA_ARGS__); }while(0) -#define DDLogRecvPost(format, ...) do{ if(DEBUG_RECV_POST) NSLog((format), ##__VA_ARGS__); }while(0) - -// Define the various timeouts (in seconds) for retreiving various parts of the XML stream -#define TIMEOUT_WRITE 10 -#define TIMEOUT_READ_START 10 -#define TIMEOUT_READ_STREAM -1 - -// Define the various tags we'll use to differentiate what it is we're currently reading or writing -#define TAG_WRITE_START -100 // Must be outside UInt16 range -#define TAG_WRITE_STREAM -101 // Must be outside UInt16 range -#define TAG_WRITE_SYNCHRONOUS -102 // Must be outside UInt16 range - -#define TAG_READ_START 200 -#define TAG_READ_STREAM 201 - - -#if TARGET_OS_IPHONE - #define DEFAULT_KEEPALIVE_INTERVAL 120.0 // 2 Minutes -#else - #define DEFAULT_KEEPALIVE_INTERVAL 300.0 // 5 Minutes -#endif - extern NSString *const XMPPStreamErrorDomain; enum XMPPStreamErrorCode @@ -80,19 +49,10 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; MulticastDelegate *multicastDelegate; int state; - AsyncSocket *asyncSocket; id transport; - UInt64 numberOfBytesSent; - UInt64 numberOfBytesReceived; - - XMPPParser *parser; - Byte flags; - NSString *hostName; - UInt16 hostPort; - NSString *tempPassword; XMPPJID *myJID; @@ -100,19 +60,10 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; NSXMLElement *rootElement; - NSTimeInterval keepAliveInterval; - NSTimer *keepAliveTimer; - id registeredModules; NSMutableDictionary *autoDelegateDict; id userTag; - - RFSRVResolver *srvResolver; - NSArray *srvResults; - NSUInteger srvResultsIndex; - - NSString *synchronousUUID; } - (id)initWithTransport:(id)transport; @@ -133,39 +84,6 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; #pragma mark Properties //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/** - * The server's hostname that should be used to make the TCP connection. - * This may be a domain name (e.g. "deusty.com") or an IP address (e.g. "70.85.193.226"). - * - * Note that this may be different from the virtual xmpp hostname. - * Just as HTTP servers can support mulitple virtual hosts from a single server, so too can xmpp servers. - * A prime example is google via google apps. - * - * For example, say you own the domain "mydomain.com". - * If you go to mydomain.com in a web browser, - * you are directed to your apache server running on your webserver somewhere in the cloud. - * But you use google apps for your email and xmpp needs. - * So if somebody sends you an email, it actually goes to google's servers, where you later access it from. - * Similarly, you connect to google's servers to sign into xmpp. - * - * In the example above, your hostname is "talk.google.com" and your JID is "me@mydomain.com". - * - * This hostName property is optional. - * If you do not set the hostName, then the framework will follow the xmpp specification using jid's domain. - * That is, it first do an SRV lookup (as specified in the xmpp RFC). - * If that fails, it will fall back to simply attempting to connect to the jid's domain. -**/ -@property (nonatomic, readwrite, copy) NSString *hostName; - -/** - * The port the xmpp server is running on. - * If you do not explicitly set the port, the default port will be used. - * If you set the port to zero, the default port will be used. - * - * The default port is 5222. -**/ -@property (nonatomic, readwrite, assign) UInt16 hostPort; - /** * The JID of the user. * @@ -203,39 +121,6 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; **/ @property (nonatomic, readwrite, copy) XMPPJID *remoteJID; -/** - * Many routers will teardown a socket mapping if there is no activity on the socket. - * For this reason, the xmpp stream supports sending keep alive data. - * This is simply whitespace, which is ignored by the xmpp protocol. - * - * The default value is defined in DEFAULT_KEEPALIVE_INTERVAL. - * - * To disable keepalive, set the interval to zero. -**/ -@property (nonatomic, readwrite, assign) NSTimeInterval keepAliveInterval; - -/** - * Returns the total number of bytes bytes sent/received by the xmpp stream. - * - * By default this is the byte count since the xmpp stream object has been created. - * If the stream has connected/disconnected/reconnected multiple times, - * the count will be the summation of all connections. - * - * The functionality may optionaly be changed to count only the current socket connection. - * See the resetByteCountPerConnection property. -**/ -@property (nonatomic, readonly) UInt64 numberOfBytesSent; -@property (nonatomic, readonly) UInt64 numberOfBytesReceived; - -/** - * Affects the funtionality of the byte counter. - * - * The default value is NO. - * - * If set to YES, the byte count will be reset just prior to a new connection (in the connect methods). -**/ -@property (nonatomic, readwrite, assign) BOOL resetByteCountPerConnection; - /** * Returns a list of all currently registered modules. * That is, instances of xmpp extension classes that subclass XMPPModule. @@ -278,17 +163,6 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; **/ - (BOOL)connect:(NSError **)errPtr; -/** - * THIS IS DEPRECATED BY THE XMPP SPECIFICATION. - * - * The xmpp specification outlines the proper use of SSL/TLS by negotiating - * the startTLS upgrade within the stream negotiation. - * This method exists for those ancient servers that still require the connection to be secured prematurely. - * - * Note: Such servers generally use port 5223 for this, which you will need to set. -**/ -- (BOOL)oldSchoolSecureConnect:(NSError **)errPtr; - /** * Disconnects from the remote host by closing the underlying TCP socket connection. * @@ -462,28 +336,6 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; **/ - (void)sendElement:(NSXMLElement *)element andNotifyMe:(UInt16)tag; -/** - * Just like the sendElement: method above, - * but this method does not return until after the element has been sent. - * - * It is important to understand what this means. - * It does NOT mean the server has received the element. - * It only means the data has been queued for sending in the underlying OS socket buffer. - * - * So at this point the OS will do everything in its capacity to send the data to the server, - * which generally means the server will eventually receive the data. - * Unless, of course, something horrible happens such as a network failure, - * or a system crash, or the server crashes, etc. - * - * Even if you close the xmpp stream after this point, the OS will still do everything it can to send the data. - * - * This method should be used sparingly. - * In other words, it should be used only when absolutely necessary. - * For example, when the system is about to go to sleep, - * or when your iOS app is about to be backgrounded, and you need to synchronously send an unavailable presence. -**/ -- (BOOL)synchronouslySendElement:(NSXMLElement *)element; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Utilities //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index d6bd5e0..d12603c 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -25,8 +25,6 @@ kP2PInitiator = 1 << 1, // If set, we are the P2P initializer kIsSecure = 1 << 2, // If set, connection has been secured via SSL/TLS kIsAuthenticated = 1 << 3, // If set, authentication has succeeded - kResetByteCountPerConnection = 1 << 4, // If set, byte count should be reset per connection - kSynchronousSendPending = 1 << 5, // If set, a synchronous send is in progress }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -74,18 +72,11 @@ - (void)setupKeepAliveTimer; @implementation XMPPStream -@synthesize hostName; -@synthesize hostPort; @synthesize myJID; @synthesize remoteJID; -@synthesize keepAliveInterval; -@synthesize numberOfBytesSent; -@synthesize numberOfBytesReceived; @synthesize registeredModules; @synthesize tag = userTag; -@dynamic resetByteCountPerConnection; - /** * Shared initialization between the various init methods. **/ @@ -95,14 +86,6 @@ - (void)commonInit state = STATE_DISCONNECTED; - numberOfBytesSent = 0; - numberOfBytesReceived = 0; - - parser = [(XMPPParser *)[XMPPParser alloc] initWithDelegate:self]; - - hostPort = 5222; - keepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; - registeredModules = [[MulticastDelegate alloc] init]; autoDelegateDict = [[NSMutableDictionary alloc] init]; } @@ -144,15 +127,6 @@ - (void)dealloc { [multicastDelegate release]; - [asyncSocket setDelegate:nil]; - [asyncSocket disconnect]; - [asyncSocket release]; - - [parser stop]; - [parser release]; - - [hostName release]; - [tempPassword release]; [myJID release]; @@ -160,17 +134,9 @@ - (void)dealloc [rootElement release]; - [keepAliveTimer invalidate]; - [keepAliveTimer release]; - [registeredModules release]; [autoDelegateDict release]; - [srvResolver release]; - [srvResults release]; - - [synchronousUUID release]; - [userTag release]; [super dealloc]; @@ -208,19 +174,6 @@ - (BOOL)isP2PRecipient return NO; } -- (BOOL)resetByteCountPerConnection -{ - return (flags & kResetByteCountPerConnection) ? YES : NO; -} - -- (void)setResetByteCountPerConnection:(BOOL)flag -{ - if (flag) - flags |= kResetByteCountPerConnection; - else - flags &= ~kResetByteCountPerConnection; -} - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Connection State //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -247,25 +200,6 @@ - (BOOL)isConnected #pragma mark C2S Connection //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (BOOL)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)errPtr -{ - state = STATE_CONNECTING; - - BOOL result = [asyncSocket connectToHost:host onPort:port error:errPtr]; - - if (result == NO) - { - state = STATE_DISCONNECTED; - } - else if ([self resetByteCountPerConnection]) - { - numberOfBytesSent = 0; - numberOfBytesReceived = 0; - } - - return result; -} - - (BOOL)connect:(NSError **)errPtr { if (state > STATE_DISCONNECTED) @@ -323,86 +257,6 @@ - (BOOL)connect:(NSError **)errPtr return result; } -- (BOOL)oldSchoolSecureConnect:(NSError **)errPtr -{ - // Mark the secure flag. - // We will check the flag in onSocket:didConnectToHost:port: - [self setIsSecure:YES]; - - // Then go through the regular connect routine - return [self connect:errPtr]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark P2P Connection -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Starts a P2P connection to the given user and given address. - * This method only works with XMPPStream objects created using the initP2P method. - * - * The given address is specified as a sockaddr structure wrapped in a NSData object. - * For example, a NSData object returned from NSNetservice's addresses method. -**/ -- (BOOL)connectTo:(XMPPJID *)jid withAddress:(NSData *)remoteAddr error:(NSError **)errPtr -{ - if (state != STATE_DISCONNECTED) - { - if (errPtr) - { - NSString *errMsg = @"Attempting to connect while already connected or connecting."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; - } - return NO; - } - - if (![self isP2P]) - { - if (errPtr) - { - NSString *errMsg = @"Non P2P streams must use the connect: method"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidType userInfo:info]; - } - return NO; - } - - // Turn on P2P initiator flag - flags |= kP2PInitiator; - - // Store remoteJID - [remoteJID release]; - remoteJID = [jid copy]; - - NSAssert((asyncSocket == nil), @"Forgot to release the previous asyncSocket instance."); - - // Notify delegates - [multicastDelegate xmppStreamWillConnect:self]; - - // Update state - state = STATE_CONNECTING; - - // Initailize socket - asyncSocket = [(AsyncSocket *)[AsyncSocket alloc] initWithDelegate:self]; - - BOOL result = [asyncSocket connectToAddress:remoteAddr error:errPtr]; - - if (result == NO) - { - state = STATE_DISCONNECTED; - } - else if ([self resetByteCountPerConnection]) - { - numberOfBytesSent = 0; - numberOfBytesReceived = 0; - } - - return result; -} - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Disconnect //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -412,8 +266,9 @@ - (BOOL)connectTo:(XMPPJID *)jid withAddress:(NSData *)remoteAddr error:(NSError **/ - (void)disconnect { + // FIXME: this method should be synchronous [multicastDelegate xmppStreamWasToldToDisconnect:self]; - [transport disconnect]; // FIXME: this method should be synchronous + [transport disconnect]; // Note: The state is updated automatically in the onSocketDidDisconnect: method. } @@ -1010,7 +865,7 @@ - (void)sendElement:(NSXMLElement *)element { if (state == STATE_CONNECTED) { - [self sendElement:element withTag:TAG_WRITE_STREAM]; + [self sendElement:element withTag:0]; } } @@ -1028,48 +883,6 @@ - (void)sendElement:(NSXMLElement *)element andNotifyMe:(UInt16)tag [self sendElement:element withTag:tag]; } } - -- (BOOL)synchronouslySendElement:(NSXMLElement *)element -{ - if (state == STATE_CONNECTED) - { - NSString *innerRunLoopMode = @"XMPPStreamSynchronousMode"; - - [self retain]; - [asyncSocket addRunLoopMode:innerRunLoopMode]; - - flags |= kSynchronousSendPending; - [self sendElement:element withTag:TAG_WRITE_SYNCHRONOUS]; - - NSString *uuid = [self generateUUID]; - - [synchronousUUID release]; - synchronousUUID = [uuid retain]; - - BOOL noError = YES; - while (noError && (uuid == synchronousUUID) && (flags & kSynchronousSendPending)) - { - NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init]; - - noError = [[NSRunLoop currentRunLoop] runMode:innerRunLoopMode - beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; - [innerPool release]; - } - - [asyncSocket removeRunLoopMode:innerRunLoopMode]; - [self autorelease]; - - BOOL result = noError && (uuid == synchronousUUID); - - [synchronousUUID release]; - synchronousUUID = nil; - - return result; - } - - return NO; -} - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Stream Negotiation //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1164,8 +977,6 @@ - (void)handleStreamFeatures if (![self isAuthenticated]) { - [self setupKeepAliveTimer]; - // Notify delegates [multicastDelegate xmppStreamDidConnect:self]; } @@ -1241,23 +1052,19 @@ - (void)handleAuth1:(NSXMLElement *)response XMPPDigestAuthentication *auth = [[XMPPDigestAuthentication alloc] initWithChallenge:response]; NSString *virtualHostName = [myJID domain]; - NSString *serverHostName = hostName; // Sometimes the realm isn't specified // In this case I believe the realm is implied as the virtual host name + // Note: earlier, in case the virtual host name was not set, the server host name was used. + // However with the introduction of BOSH, we can't always know the server host name, + // so we rely only on virtual hostnames for auth realm and digest URI. if (![auth realm]) { - if([virtualHostName length] > 0) - [auth setRealm:virtualHostName]; - else - [auth setRealm:serverHostName]; + [auth setRealm:virtualHostName]; } // Set digest-uri - if([virtualHostName length] > 0) - [auth setDigestURI:[NSString stringWithFormat:@"xmpp/%@", virtualHostName]]; - else - [auth setDigestURI:[NSString stringWithFormat:@"xmpp/%@", serverHostName]]; + [auth setDigestURI:[NSString stringWithFormat:@"xmpp/%@", virtualHostName]]; // Set username and password [auth setUsername:[myJID user] password:tempPassword]; @@ -1473,72 +1280,6 @@ - (void)handleStartSessionResponse:(NSXMLElement *)response } } -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark RFSRVResolver Delegate -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)tryNextSrvResult -{ - DDLogTrace(); - NSError *connectError = nil; - BOOL success = NO; - - while (srvResultsIndex < [srvResults count]) - { - RFSRVRecord *srvRecord = [srvResults objectAtIndex:srvResultsIndex]; - NSString *srvHost = srvRecord.target; - UInt16 srvPort = srvRecord.port; - - success = [self connectToHost:srvHost onPort:srvPort error:&connectError]; - - if (success) - { - break; - } - else - { - srvResultsIndex++; - } - } - - if (!success) - { - // SRV resolution of the JID domain failed. - // As per the RFC: - // - // "If the SRV lookup fails, the fallback is a normal IPv4/IPv6 address record resolution - // to determine the IP address, using the "xmpp-client" port 5222, registered with the IANA." - // - // In other words, just try connecting to the domain specified in the JID. - - success = [self connectToHost:[myJID domain] onPort:5222 error:&connectError]; - } - - if (!success) - { - state = STATE_DISCONNECTED; - - [multicastDelegate xmppStream:self didReceiveError:connectError]; - [multicastDelegate xmppStreamDidDisconnect:self]; - } -} - -- (void)srvResolverDidResoveSRV:(RFSRVResolver *)sender -{ - DDLogTrace(); - srvResults = [[sender results] copy]; - srvResultsIndex = 0; - - [self tryNextSrvResult]; -} - -- (void)srvResolver:(RFSRVResolver *)sender didNotResolveSRVWithError:(NSError *)srvError -{ - DDLogError(@"%s %@",__PRETTY_FUNCTION__,srvError); - - [self tryNextSrvResult]; -} - ////////////////////////////////////// #pragma mark XMPPTransport Delegate ////////////////////////////////////// @@ -1741,147 +1482,6 @@ - (void)transportDidDisconnect:(id)sender [multicastDelegate xmppStreamDidDisconnect:self]; } -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark AsyncSocket Delegate -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Called when a socket has completed reading the requested data. Not called if there is an error. -**/ -- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag -{ - if (DEBUG_RECV_PRE) - { - NSString *dataAsStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - - DDLogRecvPre(@"RECV: %@", dataAsStr); - - [dataAsStr release]; - } - - numberOfBytesReceived += [data length]; - - [parser parseData:data]; -} - -/** - * Called after data with the given tag has been successfully sent. -**/ -- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag -{ - if ((tag >= 0) && (tag <= UINT16_MAX)) - { - [multicastDelegate xmppStream:self didSendElementWithTag:tag]; - } - else if (tag == TAG_WRITE_SYNCHRONOUS) - { - flags &= ~kSynchronousSendPending; - } -} - -/** - * In the event of an error, the socket is closed. - * When connecting, this delegate method may be called before onSocket:didConnectToHost: -**/ -- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err -{ - [multicastDelegate xmppStream:self didReceiveError:err]; -} - -/** - * Called when a socket disconnects with or without error. If you want to release a socket after it disconnects, - * do so here. It is not safe to do that during "onSocket:willDisconnectWithError:". -**/ -- (void)onSocketDidDisconnect:(AsyncSocket *)sock -{ - if (srvResults && (++srvResultsIndex < [srvResults count])) - { - [self tryNextSrvResult]; - } - else - { - // Update state - state = STATE_DISCONNECTED; - - // Update configuration - [self setIsSecure:NO]; - [self setIsAuthenticated:NO]; - - // Release the parser (to free underlying resources) - [parser stop]; - [parser release]; - parser = nil; - - // Clear the root element - [rootElement release]; rootElement = nil; - - // Clear any saved authentication information - [tempPassword release]; tempPassword = nil; - - // Clear srv results - [srvResolver release]; srvResolver = nil; - [srvResults release]; srvResults = nil; - - // Clear any synchronous send attempts - [synchronousUUID release]; synchronousUUID = nil; - - // Stop the keep alive timer - [keepAliveTimer invalidate]; - [keepAliveTimer release]; - keepAliveTimer = nil; - - // Notify delegate - [multicastDelegate xmppStreamDidDisconnect:self]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Keep Alive -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)setKeepAliveInterval:(NSTimeInterval)interval -{ - if (keepAliveInterval != interval) - { - keepAliveInterval = interval; - - [self setupKeepAliveTimer]; - } -} - -- (void)setupKeepAliveTimer -{ - [keepAliveTimer invalidate]; - [keepAliveTimer release]; - keepAliveTimer = nil; - - if (state == STATE_CONNECTED) - { - if (keepAliveInterval > 0) - { - keepAliveTimer = [[NSTimer scheduledTimerWithTimeInterval:keepAliveInterval - target:self - selector:@selector(keepAlive:) - userInfo:nil - repeats:YES] retain]; - } - } -} - -- (void)keepAlive:(NSTimer *)aTimer -{ - if (state == STATE_CONNECTED) - { - NSData *outgoingData = [@" " dataUsingEncoding:NSUTF8StringEncoding]; - - numberOfBytesSent += [outgoingData length]; - - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_WRITE - tag:TAG_WRITE_STREAM]; - } -} - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Utilities //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2055,10 +1655,7 @@ - (id)initWithChallenge:(NSXMLElement *)challenge NSString *authStr = [[[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding] autorelease]; - if (DEBUG_RECV_PRE || DEBUG_RECV_POST) - { - NSLog(@"decoded challenge: %@", authStr); - } + DDLogInfo(@"decoded challenge: %@", authStr); // Extract all the key=value pairs, and put them in a dictionary for easy lookup NSMutableDictionary *auth = [NSMutableDictionary dictionaryWithCapacity:5]; @@ -2199,7 +1796,7 @@ - (NSString *)base64EncodedFullResponse [buffer appendFormat:@"response=%@,", [self response]]; [buffer appendFormat:@"charset=utf-8"]; - DDLogSend(@"decoded response: %@", buffer); + DDLogInfo(@"decoded response: %@", buffer); NSData *utf8data = [buffer dataUsingEncoding:NSUTF8StringEncoding]; From 2f4aa3d616cf4e3c5ebc82c7f50ad84e7e3ef00d Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 21 Mar 2011 14:01:07 +0530 Subject: [PATCH 030/180] Remove XMPPReconnect from iPhoneXMPP. --- Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj index 4307e68..8da9de5 100755 --- a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj +++ b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj @@ -40,7 +40,6 @@ DC84BBDF12440A8E0055A459 /* XMPPModule.m in Sources */ = {isa = PBXBuildFile; fileRef = DC84BBD212440A8E0055A459 /* XMPPModule.m */; }; DC84BBE012440A8E0055A459 /* XMPPParser.m in Sources */ = {isa = PBXBuildFile; fileRef = DC84BBD412440A8E0055A459 /* XMPPParser.m */; }; DC84BBE112440A8E0055A459 /* XMPPPresence.m in Sources */ = {isa = PBXBuildFile; fileRef = DC84BBD612440A8E0055A459 /* XMPPPresence.m */; }; - DC84BBE212440A8E0055A459 /* XMPPReconnect.m in Sources */ = {isa = PBXBuildFile; fileRef = DC84BBD812440A8E0055A459 /* XMPPReconnect.m */; }; DC84BBE312440A8E0055A459 /* XMPPStream.m in Sources */ = {isa = PBXBuildFile; fileRef = DC84BBDA12440A8E0055A459 /* XMPPStream.m */; }; DC84BC0012440AF40055A459 /* XMPPResourceCoreDataStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = DC84BBF612440AF40055A459 /* XMPPResourceCoreDataStorage.m */; }; DC84BC0112440AF40055A459 /* XMPPRoster.m in Sources */ = {isa = PBXBuildFile; fileRef = DC84BBF812440AF40055A459 /* XMPPRoster.m */; }; @@ -118,8 +117,6 @@ DC84BBD412440A8E0055A459 /* XMPPParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPParser.m; path = ../../Core/XMPPParser.m; sourceTree = SOURCE_ROOT; }; DC84BBD512440A8E0055A459 /* XMPPPresence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPPresence.h; path = ../../Core/XMPPPresence.h; sourceTree = SOURCE_ROOT; }; DC84BBD612440A8E0055A459 /* XMPPPresence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPPresence.m; path = ../../Core/XMPPPresence.m; sourceTree = SOURCE_ROOT; }; - DC84BBD712440A8E0055A459 /* XMPPReconnect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPReconnect.h; path = ../../Core/XMPPReconnect.h; sourceTree = SOURCE_ROOT; }; - DC84BBD812440A8E0055A459 /* XMPPReconnect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPReconnect.m; path = ../../Core/XMPPReconnect.m; sourceTree = SOURCE_ROOT; }; DC84BBD912440A8E0055A459 /* XMPPStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPStream.h; path = ../../Core/XMPPStream.h; sourceTree = SOURCE_ROOT; }; DC84BBDA12440A8E0055A459 /* XMPPStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPStream.m; path = ../../Core/XMPPStream.m; sourceTree = SOURCE_ROOT; }; DC84BBF412440AF40055A459 /* XMPPResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPResource.h; path = ../../Roster/XMPPResource.h; sourceTree = SOURCE_ROOT; }; @@ -334,8 +331,6 @@ DC84BBD012440A8E0055A459 /* XMPPMessage.m */, DC84BBD512440A8E0055A459 /* XMPPPresence.h */, DC84BBD612440A8E0055A459 /* XMPPPresence.m */, - DC84BBD712440A8E0055A459 /* XMPPReconnect.h */, - DC84BBD812440A8E0055A459 /* XMPPReconnect.m */, DC84BBD112440A8E0055A459 /* XMPPModule.h */, DC84BBD212440A8E0055A459 /* XMPPModule.m */, 54FDBEC01330D8460099D4DB /* XMPPTransportProtocol.h */, @@ -484,7 +479,6 @@ DC84BBDF12440A8E0055A459 /* XMPPModule.m in Sources */, DC84BBE012440A8E0055A459 /* XMPPParser.m in Sources */, DC84BBE112440A8E0055A459 /* XMPPPresence.m in Sources */, - DC84BBE212440A8E0055A459 /* XMPPReconnect.m in Sources */, DC84BBE312440A8E0055A459 /* XMPPStream.m in Sources */, DC84BC0012440AF40055A459 /* XMPPResourceCoreDataStorage.m in Sources */, DC84BC0112440AF40055A459 /* XMPPRoster.m in Sources */, From 7be7ee084784524b9203dbfffa8d91f20219efc4 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 21 Mar 2011 14:06:30 +0530 Subject: [PATCH 031/180] Update iPhoneXMPP to work with socket transport and SRV. --- Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m index 7fef647..d6ad35b 100644 --- a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m +++ b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m @@ -20,10 +20,12 @@ - (void)applicationDidFinishLaunching:(UIApplication *)application { // Replace me with the proper host name and port //transport = [[XMPPSocketTransport alloc] initWithHost:@"talk.google.com" port:5222]; + transport = [[XMPPSocketTransport alloc] init]; xmppStream = [[XMPPStream alloc] initWithTransport:transport]; xmppRosterStorage = [[XMPPRosterCoreDataStorage alloc] init]; xmppRoster = [[XMPPRoster alloc] initWithStream:xmppStream rosterStorage:xmppRosterStorage]; + [transport addDelegate:self]; [xmppStream addDelegate:self]; [xmppRoster addDelegate:self]; @@ -99,7 +101,7 @@ - (void)goOffline #pragma mark XMPPStream Delegate //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings +- (void)transport:(XMPPSocketTransport *)sender willSecureWithSettings:(NSMutableDictionary *)settings { NSLog(@"---------- xmppStream:willSecureWithSettings: ----------"); @@ -121,10 +123,10 @@ - (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDiction NSString *expectedCertName = nil; - NSString *serverDomain = xmppStream.hostName; - NSString *virtualDomain = [xmppStream.myJID domain]; + NSString *serverDomain = transport.host; + NSString *virtualDomain = [transport.myJID domain]; - if ([serverDomain isEqualToString:@"talk.google.com"]) + if ([serverDomain hasPrefix:@"talk"] && [serverDomain hasSuffix:@"google.com"]) { if ([virtualDomain isEqualToString:@"gmail.com"]) { From 637aaf042e641b082876c97837f2c55a8c3538ff Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 21 Mar 2011 15:25:14 +0530 Subject: [PATCH 032/180] Send a transportDidStartNegotiation: message after stream restart. --- Core/Transports/XMPPSocketTransport.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index 02417a9..aa778de 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -283,6 +283,7 @@ - (void)restartStream { state = XMPP_SOCKET_RESTARTING; [self sendOpeningNegotiation]; + [multicastDelegate transportDidStartNegotiation:self]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 161f8fd84976581bfbfdce764bdd568503480f2e Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Tue, 22 Mar 2011 14:18:53 +0530 Subject: [PATCH 033/180] Fix memory leaks. --- Core/Transports/XMPPSocketTransport.m | 8 ++++++++ Core/XMPPStream.m | 6 ++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index aa778de..edb936c 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -82,6 +82,14 @@ - (id)initP2PWithSocket:(AsyncSocket *)socket - (void)dealloc { + [multicastDelegate release]; + [asyncSocket release]; + [parser release]; + [host release]; + [rootElement release]; + [remoteJID release]; + [srvResolver release]; + [srvResults release]; [keepAliveTimer invalidate]; [keepAliveTimer release]; [super dealloc]; diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index d12603c..89b35d1 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -113,7 +113,7 @@ - (id)initWithP2PTransport:(id)givenTransport - (NSXMLElement *)newRootElement { NSString *streamNamespaceURI = @"http://etherx.jabber.org/streams"; - NSXMLElement *element = [[[NSXMLElement alloc] initWithName:@"stream" URI:streamNamespaceURI] retain]; + NSXMLElement *element = [[NSXMLElement alloc] initWithName:@"stream" URI:streamNamespaceURI]; [element addNamespaceWithPrefix:@"stream" stringValue:streamNamespaceURI]; [element addNamespaceWithPrefix:@"" stringValue:@"jabber:client"]; return element; @@ -889,8 +889,6 @@ - (void)sendElement:(NSXMLElement *)element andNotifyMe:(UInt16)tag - (void)restartStream { - [rootElement release]; - rootElement = [self newRootElement]; [transport restartStream]; } @@ -1291,7 +1289,7 @@ - (void)transportDidConnect:(id)sender // Digest Access authentication requires us to know the ID attribute from the element. [rootElement release]; - rootElement = [self newRootElement]; + rootElement = [[self newRootElement] retain]; if ([self isP2P] && [self isP2PRecipient]) { self.remoteJID = [transport remoteJID]; From a036732ca2beb8b5963fcb4d70e645f5d1232565 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Tue, 22 Mar 2011 14:45:07 +0530 Subject: [PATCH 034/180] Added transport protocol header to XMPP.h --- Core/XMPP.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/XMPP.h b/Core/XMPP.h index 8c8622d..59a09a1 100644 --- a/Core/XMPP.h +++ b/Core/XMPP.h @@ -4,5 +4,6 @@ #import "XMPPIQ.h" #import "XMPPMessage.h" #import "XMPPPresence.h" +#import "XMPPTransportProtocol.h" #import "NSXMLElementAdditions.h" From 95d8e7bd0c898ef65d631d2a362dfcff5ef9b670 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Fri, 25 Mar 2011 10:47:37 +0530 Subject: [PATCH 035/180] Setting the state = negotiating for stream_features that come after authentication --- Core/XMPPStream.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index d12603c..336131d 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1165,7 +1165,7 @@ - (void)handleAuth2:(NSXMLElement *)response { // We are successfully authenticated (via sasl:digest-md5) [self setIsAuthenticated:YES]; - + state = STATE_NEGOTIATING; // Now we start our negotiation over again... [self restartStream]; } @@ -1175,7 +1175,6 @@ - (void)handleAuth2:(NSXMLElement *)response // Revert back to connected state (from authenticating state) state = STATE_CONNECTED; - [multicastDelegate xmppStream:self didNotAuthenticate:response]; } } @@ -1334,13 +1333,13 @@ - (void)transportDidConnect:(id)sender - (void)transport:(id)sender didReceiveStanza:(NSXMLElement *)element { NSString *elementName = [element name]; - + if([elementName isEqualToString:@"stream:error"] || [elementName isEqualToString:@"error"]) { [multicastDelegate xmppStream:self didReceiveError:element]; return; } - + if(state == STATE_NEGOTIATING) { // We've just read in the stream features From 53918e256e7492b88570e3354a9aa35a6bdb8999 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Mon, 28 Mar 2011 22:41:59 +0530 Subject: [PATCH 036/180] ping-pong and inorder processing working --- Core/Transports/BoshTransport.h | 95 ++++ Core/Transports/BoshTransport.m | 483 ++++++++++++++++++ Core/Transports/BoshWindowManager.h | 35 ++ Core/Transports/BoshWindowManager.m | 121 +++++ Xcode/iPhoneXMPP/BoshTransport.h | 51 -- Xcode/iPhoneXMPP/BoshTransport.m | 116 ----- .../Classes/iPhoneXMPPAppDelegate.h | 2 +- .../Classes/iPhoneXMPPAppDelegate.m | 49 +- .../iPhoneXMPP.xcodeproj/project.pbxproj | 18 +- 9 files changed, 774 insertions(+), 196 deletions(-) create mode 100644 Core/Transports/BoshTransport.h create mode 100644 Core/Transports/BoshTransport.m create mode 100644 Core/Transports/BoshWindowManager.h create mode 100644 Core/Transports/BoshWindowManager.m delete mode 100644 Xcode/iPhoneXMPP/BoshTransport.h delete mode 100644 Xcode/iPhoneXMPP/BoshTransport.m diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h new file mode 100644 index 0000000..0630b1e --- /dev/null +++ b/Core/Transports/BoshTransport.h @@ -0,0 +1,95 @@ +// +// BoshTransport.h +// iPhoneXMPP +// +// Created by Satyam Shekhar on 3/17/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import "XMPPTransportProtocol.h" +#import "XMPPJID.h" +#import "MulticastDelegate.h" +#import "ASIHTTPRequest.h" +#import "BoshWindowManager.h" + +typedef enum { + DISCONNECTED = 0, + CONNECTING = 1, + CONNECTED = 2 +} BoshConnectionState; + +@interface BoshTransport : NSObject { + NSString *boshVersion; + NSNumber *wait; + NSNumber *hold; + NSString *from; + NSNumber *ack; + + NSString *sid; + NSString *lang; + NSString *content; + XMPPJID *myJID; + NSNumber *polling; + NSNumber *inactivity; + NSNumber *requests; + NSString *to; + + NSString *STREAM_NS; + NSString *CLIENT_NS; + NSString *STANZA_NS; + NSString *SASL_NS; + NSString *BIND_NS; + NSString *SESSION_NS; + NSString *BODY_NS; + NSString *XMPP_NS; + + u_int32_t nextRidToSend; + + NSMutableArray *pendingXMPPStanzas; + BoshWindowManager *boshWindowManager; + + BOOL secure; + NSString *boshUrl; + MulticastDelegate *multicastDelegate; +} + +@property(retain) XMPPJID *myJID; +@property(retain) NSNumber *wait; +@property(retain) NSNumber *hold; +@property(copy) NSString *lang; +@property(copy) NSString *content; +@property(copy) NSString *host; +@property(readonly) NSNumber *inactivity; +@property(readonly) BOOL secure; +@property(readonly) NSString *authid; +@property(readonly) NSString *sid; +@property(readonly) NSNumber *requests; + +/* init Methods */ +#pragma mark - +#pragma mark init Methods + +- (id)initWithUrl:(NSString *)url forHost:(NSString *)host; +- (id)initWithUrl:(NSString *)url forHost:(NSString *)host withDelegate:(id)delegate; + +/* ASI http methods */ +- (void)requestFinished:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; + +/* Protocol Methods */ +#pragma mark - +#pragma mark Protocol Methods + +- (void)addDelegate:(id)delegate; +- (void)removeDelegate:(id)delegate; +- (XMPPJID *)myJID; +- (void)setMyJID:(XMPPJID *)jid; +- (BOOL)connect:(NSError **)errPtr; +- (void)disconnect; +- (void)restartStream; +- (float)serverXmppStreamVersionNumber; +- (BOOL)sendStanza:(NSXMLElement *)stanza; +- (BOOL)sendStanzaWithString:(NSString *)string; +- (void)sessionResponseHandler:(ASIHTTPRequest *)request; +@end diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m new file mode 100644 index 0000000..74a8faa --- /dev/null +++ b/Core/Transports/BoshTransport.m @@ -0,0 +1,483 @@ +// +// BoshTransport.m +// iPhoneXMPP +// +// Created by Satyam Shekhar on 3/17/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "BoshTransport.h" +#import "DDXML.h" +#import "NSXMLElementAdditions.h" +#import "MulticastDelegate.h" +#import "ASIHTTPRequest.h" +#import "BoshWindowManager.h" + +typedef enum { + ATTRIBUTE_TYPE, + NAMESPACE_TYPE +} NODE_TYPE; + +#pragma mark - +#pragma mark Private Accessor Methods +@interface BoshTransport() +- (void)setInactivity:(NSString *)givenInactivity; +- (void)setSecure:(NSString *)isSecure; +- (void)setAuthid:(NSString *)givenAuthid; +- (void)setSid:(NSString *)newSID; +- (void)setRequests:(NSString *)maxRequests; +- (NSNumber *)numberFromString:(NSString *)stringNumber; +@end + +#pragma mark Private utilities +@interface BoshTransport() +- (u_int32_t) generateRid; +- (NSArray *) convertToStrings:(NSArray *)array; +- (SEL)setterForProperty:(NSString *)property; +@end + +#pragma mark http +@interface BoshTransport() +- (void) sendRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler; +- (void)broadcastStanzas:(NSXMLNode *)node; +- (void)startSessionWithRequest:(NSXMLElement *)body; +- (void)trySendingStanzas; +- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; +@end + +#pragma mark xml +@interface BoshTransport() +- (u_int32_t)getRidInRequest:(NSXMLElement *)body; +- (NSXMLElement *) newRequestWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; +- (NSArray *) createAttributeArrayFromDictionary:(NSDictionary *)attributes; +- (NSArray *) createNamespaceArrayFromDictionary:(NSDictionary *)namespacesDictionary; +- (NSXMLElement *) parseXMLData:(NSData *)xml; +- (NSXMLElement *) parseXMLString:(NSString *)xml; +@end + + +@implementation BoshTransport + +@synthesize wait; +@synthesize hold; +@synthesize lang; +//@synthesize content; +@synthesize host=to; +@synthesize myJID; + +@synthesize inactivity; +@synthesize secure; +@synthesize authid; +@synthesize sid; +@synthesize requests; + +#pragma mark - +#pragma mark Private Accessor Method Implementation + +- (void)setInactivity:(NSString *)inactivityString +{ + NSNumber *givenInactivity = [self numberFromString:inactivityString]; + [inactivity autorelease]; + inactivity = [givenInactivity retain]; +} + +- (void)setRequests:(NSString *)requestsString +{ + NSNumber *maxRequests = [self numberFromString:requestsString]; + [requests autorelease]; + requests = [maxRequests retain]; +} + +- (void)setSecure:(NSString *)isSecure +{ + if ([isSecure isEqualToString:@"true"]) secure=YES; + else secure = NO; +} + +- (void)setAuthid:(NSString *)givenAuthid +{ + [authid autorelease]; + authid = [givenAuthid copy]; +} + +- (void)setSid:(NSString *)newSID +{ + [sid autorelease]; + sid = [newSID copy]; +} + + +#pragma mark - +#pragma mark init + +- (id)initWithUrl:(NSString *)url forHost:(NSString *)host +{ + return [self initWithUrl:url forHost:host withDelegate:nil]; +} + +- (id)initWithUrl:(NSString *)url forHost:(NSString *)host withDelegate:(id)delegate +{ + self = [super init]; + if(self) + { + boshVersion = @"1.6"; + lang = @"en"; + content = [NSString stringWithFormat:@"text/xml; charset=utf-8"]; + wait = [[NSNumber alloc] initWithDouble:60.0]; + hold = [[NSNumber alloc] initWithInt:1]; + ack = [[NSNumber alloc] initWithInt:1]; + + nextRidToSend = [self generateRid]; + + multicastDelegate = [[MulticastDelegate alloc] init]; + if( delegate != nil ) [multicastDelegate addDelegate:delegate]; + + STREAM_NS = @"http://etherx.jabber.org/streams"; + CLIENT_NS = @"jabber:client"; + STANZA_NS = @"urn:ietf:params:xml:ns:xmpp-stanzas"; + SASL_NS = @"urn:ietf:params:xml:ns:xmpp-sasl"; + BIND_NS = @"urn:ietf:params:xml:ns:xmpp-bind"; + SESSION_NS = @"urn:ietf:params:xml:ns:xmpp-session"; + BODY_NS = @"http://jabber.org/protocol/httpbind"; + XMPP_NS = @"urn:xmpp:xbosh"; + + sid = nil; + polling = [[NSNumber alloc] initWithInt:0]; + inactivity = [[NSNumber alloc] initWithInt:0]; + requests = [[NSNumber alloc] initWithInt:0]; + boshUrl = url; + to = host; + myJID = nil; + + /* Keeping a random capacity right now */ + pendingXMPPStanzas = [[NSMutableArray alloc] initWithCapacity:25]; + } + return self; +} + +#pragma mark - +#pragma mark Transport Protocols +/* Implemet this as well */ +- (float)serverXmppStreamVersionNumber +{ + return 1.0; +} + +- (BOOL)connect:(NSError **)error +{ + NSLog(@"BOSH: Connecting to %@ with jid = %@", to, [myJID bare]); + + if(!to) + { + NSLog(@"BOSH: Called Connect with specifying the host"); + return NO; + } + + if(!myJID) + { + NSLog(@"BOSH: Called connect without setting the jid"); + return NO; + } + + [multicastDelegate transportWillConnect:self]; + + NSArray *keys = [NSArray arrayWithObjects:@"content", @"hold", @"to", @"ver", @"wait", @"ack", @"xml:lang", @"from", @"secure", nil]; + NSArray *objects = [NSArray arrayWithObjects:content, hold, to, boshVersion, wait, ack, lang, [myJID bare], @"false", nil]; + NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjects:[self convertToStrings:objects] forKeys:keys]; + NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; + + NSXMLElement *httpRequest = [self newRequestWithPayload:nil attributes:attr namespaces:ns]; + + [self startSessionWithRequest:httpRequest]; + + return YES; +} + +- (void)restartStream +{ + NSLog(@"Bosh: Will Restart Stream"); + NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: lang, @"xml:lang", @"true", @"xmpp:restart", nil]; + NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", XMPP_NS, @"xmpp", nil]; + [self sendRequest:nil attributes:attr namespaces:ns]; +} + +- (void)disconnect +{ + return; +} + +- (BOOL)sendStanza:(NSXMLElement *)stanza +{ + //NSLog(@"BOSH: Enqueuing payload = %@", stanza); + [pendingXMPPStanzas addObject:stanza]; + [self trySendingStanzas]; + return TRUE; +} + +- (BOOL)sendStanzaWithString:(NSString *)string +{ + //NSLog(@"BOSH: will send stanza as string = %@", string); + NSXMLElement *payload = [self parseXMLString:string]; + return [self sendStanza:payload]; +} + +- (void)addDelegate:(id)delegate +{ + [multicastDelegate addDelegate:delegate]; +} + +- (void) removeDelegate:(id)delegate +{ + [multicastDelegate removeDelegate:delegate]; +} + +#pragma mark - +#pragma mark BOSH + +/* + Will be called either when the response arrives or when + the client queues his request in the request queue. + + When the response arrives we call the following - + i) broadcastStanzas. (might internally call processRequestQueue) + ii) processRequestQueue (will do nothing if called internally by previous case) + iii) call sendRequestToHold. +*/ + +- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces +{ + NSXMLElement *requestPayload = [self newRequestWithPayload:bodyPayload attributes:attributes namespaces:namespaces]; + [self sendRequestWithBody:requestPayload responseHandler:nil errorHandler:nil]; + [boshWindowManager sentRequest:requestPayload]; + [requestPayload release]; +} + +- (void)trySendingStanzas +{ + if( [boshWindowManager canSendMoreRequests] && [pendingXMPPStanzas count] > 0) + { + [self sendRequest:pendingXMPPStanzas attributes:nil namespaces:nil]; + [pendingXMPPStanzas removeAllObjects]; + } +} + +- (void)sendRequestsToHold +{ + while( [boshWindowManager canLetServerHoldRequests:[hold unsignedIntValue]] ) + [self sendRequest:nil attributes:nil namespaces:nil]; +} + +/* + For each received stanza the client might send out packets. + We should ideally put all the request in the queue and call + processRequestQueue with a timeOut. +*/ +- (void)broadcastStanzas:(NSXMLNode *)node +{ + while( node = [node nextNode] ) + { + if([node level] == 1) + { + NSLog(@"BOSH: Passing to delegates the stanza = %@", node); + [multicastDelegate transport:self didReceiveStanza:[(NSXMLElement *)node copy]]; + } + } +} + +- (void)sessionResponseHandler:(ASIHTTPRequest *)request +{ + [multicastDelegate transportDidConnect:self]; + NSLog(@"BOSH: Response = %@", [request responseString]); + NSXMLElement *rootElement = [self parseXMLData:[request responseData]]; + + NSArray *responseAttributes = [rootElement attributes]; + for(NSXMLNode *attr in responseAttributes) + { + NSString *attrName = [attr name]; + NSString *attrValue = [attr stringValue]; + SEL setter = [self setterForProperty:attrName]; + + if([self respondsToSelector:setter]) + [self performSelector:setter withObject:attrValue]; + //else + // NSLog(@"BOSH: NOTE: Selector For attribute= %@ Not Found", attrName); + } + + /* Not doing anything with namespaces right now - because chirkut doesn't send it */ + //NSArray *responseNamespaces = [rootElement namespaces]; + + [multicastDelegate transportDidStartNegotiation:self]; + boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:[self getRidInRequest:rootElement]]; + [boshWindowManager setWindowSize:[requests unsignedIntValue]]; + + if( [(NSXMLNode *)rootElement childCount] > 0 ) + [self broadcastStanzas:rootElement]; + + /* should we send these requests after a delay?? */ + [self sendRequestsToHold]; +} + +- (void)startSessionWithRequest:(NSXMLElement *)body +{ + [self sendRequestWithBody:body responseHandler:@selector(sessionResponseHandler:) errorHandler:nil]; +} + +#pragma mark - +#pragma mark HTTP + +/* Should call processRequestQueue after some timeOut */ +- (void)requestFinished:(ASIHTTPRequest *)request +{ + NSString *responseString = [request responseString]; + NSLog(@"BOSH: response string = %@", responseString); + [boshWindowManager recievedResponse:[self parseXMLData:[request responseData]]]; + [self sendRequestsToHold]; +} + +- (void)requestFailed:(ASIHTTPRequest *)request +{ + NSLog(@"BOSH: request Failed = %@", [request error]); +} + +/* + Need to set timeouts on requests + and handle the requests that time out. + and resend the timed out requests. + We should also implement a recovery strategy. +*/ +- (void)sendRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler +{ + NSURL *url = [NSURL URLWithString:boshUrl]; + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + [request setRequestMethod:@"POST"]; + [request setDelegate:self]; + [request setTimeOutSeconds:[wait doubleValue]+4]; + + if(body) [request appendPostData:[[body compactXMLString] dataUsingEncoding:NSUTF8StringEncoding]]; + if(responseHandler) [request setDidFinishSelector:responseHandler]; + if(errorHandler) [request setDidFailSelector:errorHandler]; + + //NSAssert(outstandingRequests < [requests unsignedIntValue] + 1, @"BOSH: Number of outstanding requests exceeded \"requests\""); + [request startAsynchronous]; + + NSLog(@"BOSH: Async Request Sent with data = %@", body); + return; +} + +#pragma mark - +#pragma mark XML + +- (u_int32_t)getRidInRequest:(NSXMLElement *)body +{ + return [[[body attributeForName:@"rid"] stringValue] intValue]; +} + +- (NSXMLElement *)newRequestWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces +{ + attributes = attributes?attributes:[NSMutableDictionary dictionaryWithCapacity:3]; + namespaces = namespaces?namespaces:[NSMutableDictionary dictionaryWithCapacity:1]; + + /* Adding sid attribute on every outgoing request */ + if( sid ) [attributes setValue:sid forKey:@"sid"]; + + [attributes setValue:[NSString stringWithFormat:@"%d", nextRidToSend] forKey:@"rid"]; + + /* Adding the BODY_NS namespace on every outgoing request */ + [namespaces setValue:BODY_NS forKey:@""]; + + NSXMLElement *boshRequest = [[NSXMLElement alloc] initWithName:@"body"]; + + [boshRequest setNamespaces:[self createNamespaceArrayFromDictionary:namespaces]]; + [boshRequest setAttributes:[self createAttributeArrayFromDictionary:attributes]]; + + if(payload != nil) + for(NSXMLElement *child in payload) + [boshRequest addChild:child]; + + //NSLog(@"BOSH: Rid = %d attached to request", nextRidToSend); + ++nextRidToSend; + + return boshRequest; +} + +- (NSXMLElement *)returnRootElement:(NSXMLDocument *)doc +{ + NSXMLElement *rootElement = [doc rootElement]; + [doc release]; + return rootElement; +} + +- (NSXMLElement *)parseXMLString:(NSString *)xml +{ + NSXMLDocument *doc = [[NSXMLDocument alloc] initWithXMLString:xml options:0 error:nil]; + return [self returnRootElement:doc]; +} + +- (NSXMLElement *)parseXMLData:(NSData *)xml +{ + NSXMLDocument *doc = [[NSXMLDocument alloc] initWithData:xml options:0 error:nil]; + return [self returnRootElement:doc]; +} + +- (NSArray *)createArrayFromDictionary:(NSDictionary *)dictionary ofType:(NSString *)type +{ + NSMutableArray *array = [[NSMutableArray alloc] init]; + for (NSString *key in dictionary) { + NSString *value = [dictionary objectForKey:key]; + NSXMLNode *node; + + if([type isEqualToString:@"attributes"]) + node = [NSXMLNode attributeWithName:key stringValue:value]; + else if([type isEqualToString:@"namespaces"]) + node = [NSXMLNode namespaceWithName:key stringValue:value]; + else + NSLog(@"BOSH: Wrong Type Passed to createArrayFrom Dictionary"); + + [array addObject:node]; + } + return [array autorelease]; +} + +- (NSArray *)createAttributeArrayFromDictionary:(NSDictionary *)attributesDictionary +{ + return [self createArrayFromDictionary:attributesDictionary ofType:@"attributes"]; +} + +- (NSArray *)createNamespaceArrayFromDictionary:(NSDictionary *)namespacesDictionary +{ + return [self createArrayFromDictionary:namespacesDictionary ofType:@"namespaces"]; +} + +#pragma mark - +#pragma mark utilities + +- (u_int32_t)generateRid +{ + return (arc4random() % 1000000000LL + 1000000001LL); +} + +- (NSArray *)convertToStrings:(NSArray *)array +{ + NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:0]; + for(id element in array) + [mutableArray addObject:[NSString stringWithFormat:@"%@", element]]; + return [mutableArray autorelease]; +} + +- (SEL)setterForProperty:(NSString *)property +{ + NSString *setter = @"set"; + setter = [setter stringByAppendingString:[property capitalizedString]]; + setter = [setter stringByAppendingString:@":"]; + return NSSelectorFromString(setter); +} + +- (NSNumber *)numberFromString:(NSString *)stringNumber +{ + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; + NSNumber *number = [formatter numberFromString:stringNumber]; + [formatter release]; + return number; +} +@end diff --git a/Core/Transports/BoshWindowManager.h b/Core/Transports/BoshWindowManager.h new file mode 100644 index 0000000..e12509c --- /dev/null +++ b/Core/Transports/BoshWindowManager.h @@ -0,0 +1,35 @@ +// +// BoshWindowManager.h +// iPhoneXMPP +// +// Created by Satyam Shekhar on 3/28/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import "DDXML.h" + +@protocol BoshWindowProtocol +- (void)broadcastStanzas:(NSXMLNode *)node; +@end + +@interface BoshWindowManager : NSObject { + NSMutableDictionary *window; + + u_int32_t maxRidReceived; + u_int32_t maxRidSent; + + id delegate; +} + +@property u_int32_t windowSize; +@property(readonly) u_int32_t outstandingRequests; + +- (void)sentRequest:(NSXMLElement *)request; +- (void)recievedResponse:(NSXMLElement *)response; +- (BOOL)canSendMoreRequests; +- (BOOL)canLetServerHoldRequests:(u_int32_t)hold; +- (NSXMLElement *)getRequestForRid:(u_int32_t)rid; +- (id)initWithDelegate:(id)del rid:(u_int32_t)rid; +@end + diff --git a/Core/Transports/BoshWindowManager.m b/Core/Transports/BoshWindowManager.m new file mode 100644 index 0000000..b8a1432 --- /dev/null +++ b/Core/Transports/BoshWindowManager.m @@ -0,0 +1,121 @@ +// +// BoshWindowManager.m +// iPhoneXMPP +// +// Created by Satyam Shekhar on 3/28/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "BoshWindowManager.h" + +@interface RequestResponsePair : NSObject +{ +} + +@property(retain) NSXMLElement *request; +@property(retain) NSXMLElement *response; +- (id)initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response; +- (void)dealloc; +@end + +@implementation RequestResponsePair + +@synthesize request=request_; +@synthesize response=response_; + +- (id) initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response +{ + if( self = [super init]) { + request_ = request; + response_ = response; + } + return self; +} + +- (void)dealloc +{ + [request_ release]; + [response_ release]; + [super dealloc]; +} +@end + + +@implementation BoshWindowManager + +@synthesize windowSize; +@synthesize outstandingRequests; + +- (u_int32_t)getRidInBody:(NSXMLElement *)body +{ + return [[[body attributeForName:@"rid"] stringValue] intValue]; +} + +- (NSString *)stringFromInt:(u_int32_t)num +{ + return [NSString stringWithFormat:@"%u",num]; +} + +- (id)initWithDelegate:(id)del rid:(u_int32_t)rid +{ + if(self = [super init]) + { + window = [[NSMutableDictionary alloc] initWithCapacity:4]; + windowSize = 0; + outstandingRequests = 0; + maxRidSent = rid; + maxRidReceived = rid; + delegate = del; + } + return self; +} + +- (void)sentRequest:(NSXMLElement *)request +{ + NSAssert( [self canSendMoreRequests], @"Sending request when should not be: Exceeding request count" ); + u_int32_t requestRid = [self getRidInBody:request]; + NSAssert ( requestRid == maxRidSent + 1, @"Sending request with rid = %u greater than expected rid = %u", requestRid, maxRidSent + 1); + ++maxRidSent; + [window setValue:[[RequestResponsePair alloc] initWithRequest:request response:nil] forKey:[self stringFromInt:requestRid]]; +} + +- (void)processResponses +{ + while (true) + { + RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:(maxRidReceived+1)]]; + if( requestResponsePair.response == nil ) break; + [[requestResponsePair.response retain] autorelease]; + [window removeObjectForKey:[ self stringFromInt:(maxRidReceived + 1) ]]; + ++maxRidReceived; + [delegate broadcastStanzas:requestResponsePair.response]; + } +} + +- (void)recievedResponse:(NSXMLElement *)response +{ + u_int32_t responseRid = [self getRidInBody:response]; + NSAssert(responseRid > maxRidReceived, @"Recieving response for rid = %u where maxRidReceived = %u", responseRid, maxRidReceived); + NSAssert(responseRid < maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %u, maxRidReceived = %u, windowSize = %u", responseRid, maxRidReceived, windowSize); + RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:responseRid]]; + NSAssert( requestResponsePair != nil, @"Response rid not in queue"); + requestResponsePair.response = response; + [self processResponses]; +} + +- (BOOL)canSendMoreRequests +{ + return (maxRidSent - maxRidReceived) < windowSize; +} + +- (BOOL)canLetServerHoldRequests:(u_int32_t)hold +{ + return (maxRidSent - maxRidReceived) < hold; +} +- (NSXMLElement *)getRequestForRid:(u_int32_t)rid +{ + NSAssert( rid - maxRidReceived > 0 && [window count] > (rid - maxRidReceived), @"Error Access request for rid = %u, where maxRidReceived = %u and [requestQueue count] = %u", rid, maxRidReceived, [window count]); + return [window valueForKey:[self stringFromInt:rid]]; +} + +@end diff --git a/Xcode/iPhoneXMPP/BoshTransport.h b/Xcode/iPhoneXMPP/BoshTransport.h deleted file mode 100644 index b070410..0000000 --- a/Xcode/iPhoneXMPP/BoshTransport.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// BoshTransport.h -// iPhoneXMPP -// -// Created by Satyam Shekhar on 3/17/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import -#import "XMPPTransportProtocol.h" - - -typedef enum { - DISCONNECTED = 0, - CONNECTING = 1, - CONNECTED = 2 -} BoshConnectionState; - -@interface BoshTransport : NSObject { - NSString *version; - NSInteger wait; - NSInteger hold; - NSString *from; - NSInteger ack; - - u_int32_t rid; - NSInteger sid; - NSString *lang; - NSString *contentType; - NSString *jid; - NSInteger polling; - NSInteger inactivity; - NSInteger requests; - NSString *to; - id delegate; -} - -@property(copy) NSString *jid; -@property(retain) id delegate; -@property(assign) NSInteger wait; -@property(assign) NSInteger hold; -@property(copy) NSString *lang; -@property(copy) NSString *contentType; - -- (id)init; -- (id)initWithDelegate:(id)delegate; -- (BOOL)connect:(NSError **)error; -- (BOOL)disconnect; -- (BOOL)sendStanza:(NSXMLElement *)stanza; - -@end \ No newline at end of file diff --git a/Xcode/iPhoneXMPP/BoshTransport.m b/Xcode/iPhoneXMPP/BoshTransport.m deleted file mode 100644 index 9c529c3..0000000 --- a/Xcode/iPhoneXMPP/BoshTransport.m +++ /dev/null @@ -1,116 +0,0 @@ -// -// BoshTransport.m -// iPhoneXMPP -// -// Created by Satyam Shekhar on 3/17/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "BoshTransport.h" -#import "DDXML.h" -#import "NSXMLElementAdditions.h" - -@interface BoshTransport() -- (void) sendData; -- (u_int32_t) generateRid; -- (NSString *) newRequestWithPayload:(NSXMLElement *)payload attributes:(NSArray *)attributes namespaces:(NSArray *)namespaces; -- (NSArray *) createAttribureArrayFromDictionary:(NSDictionary *)attributes; -- (NSArray *) createNamespaceArrayFromDictionary:(NSDictionary *)namespacesDictionary; -@end - -@implementation BoshTransport - -@synthesize jid; -@synthesize delegate; -@synthesize wait; -@synthesize hold; -@synthesize lang; -@synthesize contentType; - -- (u_int32_t) generateRid { - return (arc4random() % 1000000000LL + 1000000001LL); -} - -- (NSArray *) createArrayFromDictionary:(NSDictionary *)dictionary of:(NSString *)type { - NSMutableArray *array = [[NSMutableArray alloc] init]; - NSString *key; - for (key in dictionary) { - NSString *value = [dictionary objectForKey:key]; - NSXMLNode *node; - if([type isEqualToString:@"attributes"]) { - node = [NSXMLNode attributeWithName:key stringValue:value]; - } - else { - node = [NSXMLNode namespaceWithName:key stringValue:value]; - } - [array addObject:node]; - } - return array; -} - -- (NSArray *) createArributeArrayFromDictionary:(NSDictionary *)attributesDictionary { - return [self createArrayFromDictionary:attributesDictionary of:@"attributes"]; -} - -- (NSArray *) createNamespaceArrayFromDictionary:(NSDictionary *)namespacesDictionary { - return [self createArrayFromDictionary:namespacesDictionary of:@"namespaces"]; -} - --(NSString *) newRequestWithPayload:(NSXMLElement *)payload attributes:(NSArray *)attributes namespaces:(NSArray *)namespaces { - NSXMLElement *boshRequest = [[NSXMLElement alloc] initWithName:@"body"]; - - [boshRequest setNamespaces:namespaces]; - [boshRequest setAttributes:attributes]; - [boshRequest addChild:payload]; - - NSString *serializedRequest = [boshRequest compactXMLString]; - [boshRequest release]; - return serializedRequest; -} - -- (void) sendData { - return; -} - -- (id) init { - return [self initWithDelegate:nil]; -} - -- (id)initWithDelegate:(id)delegateToSet { - self = [super init]; - if(self) { - version = @"1.6"; - lang = @"en"; - contentType = @"text/xml; charset=utf-8"; - wait = 60; - hold = 1; - ack = 1; - - rid = [self generateRid]; - delegate = delegateToSet; - - sid = 0; - polling = 0; - inactivity = 0; - requests = 0; - } - return self; -} - -- (BOOL)connect:(NSError **)error { - NSArray *objects = [[NSArray alloc] initWithObjects:@"content", @"hold", @"rid", @"to", @"ver", @"wait", @"ack", @"xml:lang", nil]; - NSArray *keys = [[NSArray alloc] initWithObjects:contentType, [NSString stringWithFormat:@"%d", hold], [NSString stringWithFormat:@"%d", rid], ; - NSDictionary *headers = [[NSDictionary alloc] initWithObjects:objects forKeys:keys]; - [self newRequestWithPayload:nil attributes:headers]; - return TRUE; -} - -- (BOOL)disconnect { - return TRUE; -}; - -- (BOOL) sendStanza:(NSXMLElement *)stanza { - return TRUE; -} - -@end diff --git a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.h b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.h index a51a858..ebbd52b 100644 --- a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.h +++ b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.h @@ -8,7 +8,7 @@ @interface iPhoneXMPPAppDelegate : NSObject { - XMPPSocketTransport *transport; + id transport; XMPPStream *xmppStream; XMPPRoster *xmppRoster; XMPPRosterCoreDataStorage *xmppRosterStorage; diff --git a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m index d6ad35b..e9c3681 100644 --- a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m +++ b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m @@ -4,6 +4,7 @@ #import "XMPP.h" #import "XMPPRosterCoreDataStorage.h" #import "XMPPSocketTransport.h" +#import "BoshTransport.h" #import @@ -18,33 +19,37 @@ @implementation iPhoneXMPPAppDelegate - (void)applicationDidFinishLaunching:(UIApplication *)application { + transport = [[BoshTransport alloc] initWithUrl:@"http://localhost:5280/http-bind" forHost:@"directi.com"]; + //[transport setMyJID:[XMPPJID jidWithString:@"satyam.s@directi.com"]]; + + //173.255.205.197 // Replace me with the proper host name and port - //transport = [[XMPPSocketTransport alloc] initWithHost:@"talk.google.com" port:5222]; - transport = [[XMPPSocketTransport alloc] init]; - xmppStream = [[XMPPStream alloc] initWithTransport:transport]; - xmppRosterStorage = [[XMPPRosterCoreDataStorage alloc] init]; - xmppRoster = [[XMPPRoster alloc] initWithStream:xmppStream rosterStorage:xmppRosterStorage]; - + //transport = [[XMPPSocketTransport alloc] initWithHost:@"internal.chat.pw" port:5222]; + //transport = [[XMPPSocketTransport alloc] init]; + xmppStream = [[XMPPStream alloc] initWithTransport:transport]; + xmppRosterStorage = [[XMPPRosterCoreDataStorage alloc] init]; + xmppRoster = [[XMPPRoster alloc] initWithStream:xmppStream rosterStorage:xmppRosterStorage]; + [transport addDelegate:self]; - [xmppStream addDelegate:self]; - [xmppRoster addDelegate:self]; - - [xmppRoster setAutoRoster:YES]; - - // Replace me with the proper JID and password - //[xmppStream setMyJID:[XMPPJID jidWithString:@"user@gmail.com/iPhoneTest"]]; - //password = @"password"; - - // You may need to alter these settings depending on the server you're connecting to + [xmppStream addDelegate:self]; + [xmppRoster addDelegate:self]; + + [xmppRoster setAutoRoster:YES]; + + // Replace me with the proper JID and password + [xmppStream setMyJID:[XMPPJID jidWithString:@"satyam.s@directi.com/iPhoneTest"]]; + password = @"shekhar123"; + + // You may need to alter these settings depending on the server you're connecting to allowSelfSignedCertificates = NO; allowSSLHostNameMismatch = NO; // Uncomment me when the proper information has been entered above. -// NSError *error = nil; -// if (![xmppStream connect:&error]) -// { -// NSLog(@"Error connecting: %@", error); -// } + NSError *error = nil; + if (![xmppStream connect:&error]) + { + NSLog(@"Error connecting: %@", error); + } [window addSubview:[navigationController view]]; [window makeKeyAndVisible]; @@ -123,7 +128,7 @@ - (void)transport:(XMPPSocketTransport *)sender willSecureWithSettings:(NSMutabl NSString *expectedCertName = nil; - NSString *serverDomain = transport.host; + NSString *serverDomain = @"saf"; NSString *virtualDomain = [transport.myJID domain]; if ([serverDomain hasPrefix:@"talk"] && [serverDomain hasSuffix:@"google.com"]) diff --git a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj index 7aca4d3..73dbd04 100755 --- a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj +++ b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj @@ -17,7 +17,6 @@ 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; }; 28C286E10D94DF7D0034E888 /* RootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C286E00D94DF7D0034E888 /* RootViewController.m */; }; 28F335F11007B36200424DE2 /* RootViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28F335F01007B36200424DE2 /* RootViewController.xib */; }; - 4A97CB60133201C6009028BE /* BoshTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB5F133201C6009028BE /* BoshTransport.m */; }; 4A97CB751332094B009028BE /* ASIAuthenticationDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB621332094B009028BE /* ASIAuthenticationDialog.m */; }; 4A97CB761332094B009028BE /* ASIDataCompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB651332094B009028BE /* ASIDataCompressor.m */; }; 4A97CB771332094B009028BE /* ASIDataDecompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB671332094B009028BE /* ASIDataDecompressor.m */; }; @@ -29,6 +28,8 @@ 4A97CB7F1332096A009028BE /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB7E1332096A009028BE /* Reachability.m */; }; 4A97CBDB1332314C009028BE /* libz.1.2.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A97CBDA1332314C009028BE /* libz.1.2.3.dylib */; }; 4A97CBDF13323191009028BE /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A97CBDE13323191009028BE /* MobileCoreServices.framework */; }; + 4AD0F4E3133C82F600F442D9 /* BoshTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD0F4E2133C82F600F442D9 /* BoshTransport.m */; }; + 4AD0F6511340F88800F442D9 /* BoshWindowManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD0F6501340F88800F442D9 /* BoshWindowManager.m */; }; 54FDBEC41330DA030099D4DB /* XMPPSocketTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FDBEC31330DA030099D4DB /* XMPPSocketTransport.m */; }; DC1F97E21152CA2D00138A8F /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1F97E11152CA2D00138A8F /* libxml2.dylib */; }; DC1F97E81152CA4E00138A8F /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1F97E71152CA4E00138A8F /* CFNetwork.framework */; }; @@ -79,8 +80,6 @@ 28C286E00D94DF7D0034E888 /* RootViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RootViewController.m; sourceTree = ""; }; 28F335F01007B36200424DE2 /* RootViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RootViewController.xib; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 4A97CB5E133201C6009028BE /* BoshTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BoshTransport.h; sourceTree = ""; }; - 4A97CB5F133201C6009028BE /* BoshTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BoshTransport.m; sourceTree = ""; }; 4A97CB611332094B009028BE /* ASIAuthenticationDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIAuthenticationDialog.h; path = "../../../asi-http-request/Classes/ASIAuthenticationDialog.h"; sourceTree = SOURCE_ROOT; }; 4A97CB621332094B009028BE /* ASIAuthenticationDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIAuthenticationDialog.m; path = "../../../asi-http-request/Classes/ASIAuthenticationDialog.m"; sourceTree = SOURCE_ROOT; }; 4A97CB631332094B009028BE /* ASICacheDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASICacheDelegate.h; path = "../../../asi-http-request/Classes/ASICacheDelegate.h"; sourceTree = SOURCE_ROOT; }; @@ -105,6 +104,10 @@ 4A97CB7E1332096A009028BE /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Reachability.m; path = "../../../asi-http-request/External/Reachability/Reachability.m"; sourceTree = SOURCE_ROOT; }; 4A97CBDA1332314C009028BE /* libz.1.2.3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.1.2.3.dylib; path = usr/lib/libz.1.2.3.dylib; sourceTree = SDKROOT; }; 4A97CBDE13323191009028BE /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; + 4AD0F4E1133C82F600F442D9 /* BoshTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BoshTransport.h; path = ../../Core/Transports/BoshTransport.h; sourceTree = SOURCE_ROOT; }; + 4AD0F4E2133C82F600F442D9 /* BoshTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BoshTransport.m; path = ../../Core/Transports/BoshTransport.m; sourceTree = SOURCE_ROOT; }; + 4AD0F64F1340F88800F442D9 /* BoshWindowManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BoshWindowManager.h; path = ../../Core/Transports/BoshWindowManager.h; sourceTree = SOURCE_ROOT; }; + 4AD0F6501340F88800F442D9 /* BoshWindowManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BoshWindowManager.m; path = ../../Core/Transports/BoshWindowManager.m; sourceTree = SOURCE_ROOT; }; 54FDBEC01330D8460099D4DB /* XMPPTransportProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPTransportProtocol.h; path = ../../Core/XMPPTransportProtocol.h; sourceTree = ""; }; 54FDBEC21330DA030099D4DB /* XMPPSocketTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPSocketTransport.h; path = ../../Core/Transports/XMPPSocketTransport.h; sourceTree = ""; }; 54FDBEC31330DA030099D4DB /* XMPPSocketTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPSocketTransport.m; path = ../../Core/Transports/XMPPSocketTransport.m; sourceTree = ""; }; @@ -296,10 +299,12 @@ 54FDBEC51330DA0C0099D4DB /* Transports */ = { isa = PBXGroup; children = ( + 4AD0F64F1340F88800F442D9 /* BoshWindowManager.h */, + 4AD0F6501340F88800F442D9 /* BoshWindowManager.m */, + 4AD0F4E1133C82F600F442D9 /* BoshTransport.h */, + 4AD0F4E2133C82F600F442D9 /* BoshTransport.m */, 54FDBEC21330DA030099D4DB /* XMPPSocketTransport.h */, 54FDBEC31330DA030099D4DB /* XMPPSocketTransport.m */, - 4A97CB5E133201C6009028BE /* BoshTransport.h */, - 4A97CB5F133201C6009028BE /* BoshTransport.m */, ); name = Transports; sourceTree = ""; @@ -562,7 +567,6 @@ 0771B81D1315E9FB0018D5C6 /* XMPPRosterCoreDataStorage+shared.m in Sources */, 0771B81E1315E9FC0018D5C6 /* XMPPStreamCoreDataStorage.m in Sources */, 54FDBEC41330DA030099D4DB /* XMPPSocketTransport.m in Sources */, - 4A97CB60133201C6009028BE /* BoshTransport.m in Sources */, 4A97CB751332094B009028BE /* ASIAuthenticationDialog.m in Sources */, 4A97CB761332094B009028BE /* ASIDataCompressor.m in Sources */, 4A97CB771332094B009028BE /* ASIDataDecompressor.m in Sources */, @@ -572,6 +576,8 @@ 4A97CB7B1332094B009028BE /* ASIInputStream.m in Sources */, 4A97CB7C1332094B009028BE /* ASINetworkQueue.m in Sources */, 4A97CB7F1332096A009028BE /* Reachability.m in Sources */, + 4AD0F4E3133C82F600F442D9 /* BoshTransport.m in Sources */, + 4AD0F6511340F88800F442D9 /* BoshWindowManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 3b2ad11ceafa9e1d91ce1a3fb44ae6c7ea3d4373 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Tue, 29 Mar 2011 16:44:24 +0530 Subject: [PATCH 037/180] Refactoring: changing variable names --- Core/Transports/BoshTransport.h | 44 ++-------- Core/Transports/BoshTransport.m | 126 +++++++++------------------- Core/Transports/BoshWindowManager.m | 4 +- 3 files changed, 47 insertions(+), 127 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 0630b1e..842ae83 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -13,44 +13,20 @@ #import "ASIHTTPRequest.h" #import "BoshWindowManager.h" -typedef enum { - DISCONNECTED = 0, - CONNECTING = 1, - CONNECTED = 2 -} BoshConnectionState; - @interface BoshTransport : NSObject { NSString *boshVersion; - NSNumber *wait; - NSNumber *hold; - NSString *from; NSNumber *ack; - NSString *sid; - NSString *lang; NSString *content; - XMPPJID *myJID; - NSNumber *polling; - NSNumber *inactivity; - NSNumber *requests; - NSString *to; - - NSString *STREAM_NS; - NSString *CLIENT_NS; - NSString *STANZA_NS; - NSString *SASL_NS; - NSString *BIND_NS; - NSString *SESSION_NS; - NSString *BODY_NS; - NSString *XMPP_NS; + NSString *STREAM_NS; + NSString *BODY_NS; + NSString *XMPP_NS; - u_int32_t nextRidToSend; + long long nextRidToSend; NSMutableArray *pendingXMPPStanzas; BoshWindowManager *boshWindowManager; - BOOL secure; - NSString *boshUrl; MulticastDelegate *multicastDelegate; } @@ -58,18 +34,15 @@ typedef enum { @property(retain) NSNumber *wait; @property(retain) NSNumber *hold; @property(copy) NSString *lang; -@property(copy) NSString *content; @property(copy) NSString *host; @property(readonly) NSNumber *inactivity; @property(readonly) BOOL secure; -@property(readonly) NSString *authid; -@property(readonly) NSString *sid; @property(readonly) NSNumber *requests; +@property(copy) NSString *authid; +@property(copy) NSString *sid; +@property(copy) NSString *url; /* init Methods */ -#pragma mark - -#pragma mark init Methods - - (id)initWithUrl:(NSString *)url forHost:(NSString *)host; - (id)initWithUrl:(NSString *)url forHost:(NSString *)host withDelegate:(id)delegate; @@ -78,9 +51,6 @@ typedef enum { - (void)requestFailed:(ASIHTTPRequest *)request; /* Protocol Methods */ -#pragma mark - -#pragma mark Protocol Methods - - (void)addDelegate:(id)delegate; - (void)removeDelegate:(id)delegate; - (XMPPJID *)myJID; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 74a8faa..4b5a90f 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -9,31 +9,21 @@ #import "BoshTransport.h" #import "DDXML.h" #import "NSXMLElementAdditions.h" -#import "MulticastDelegate.h" -#import "ASIHTTPRequest.h" -#import "BoshWindowManager.h" - -typedef enum { - ATTRIBUTE_TYPE, - NAMESPACE_TYPE -} NODE_TYPE; #pragma mark - #pragma mark Private Accessor Methods @interface BoshTransport() - (void)setInactivity:(NSString *)givenInactivity; - (void)setSecure:(NSString *)isSecure; -- (void)setAuthid:(NSString *)givenAuthid; -- (void)setSid:(NSString *)newSID; - (void)setRequests:(NSString *)maxRequests; -- (NSNumber *)numberFromString:(NSString *)stringNumber; @end #pragma mark Private utilities @interface BoshTransport() -- (u_int32_t) generateRid; +- (long long) generateRid; - (NSArray *) convertToStrings:(NSArray *)array; - (SEL)setterForProperty:(NSString *)property; +- (NSNumber *)numberFromString:(NSString *)stringNumber; @end #pragma mark http @@ -47,7 +37,7 @@ - (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)att #pragma mark xml @interface BoshTransport() -- (u_int32_t)getRidInRequest:(NSXMLElement *)body; +- (long long)getRidInRequest:(NSXMLElement *)body; - (NSXMLElement *) newRequestWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; - (NSArray *) createAttributeArrayFromDictionary:(NSDictionary *)attributes; - (NSArray *) createNamespaceArrayFromDictionary:(NSDictionary *)namespacesDictionary; @@ -58,17 +48,16 @@ - (NSXMLElement *) parseXMLString:(NSString *)xml; @implementation BoshTransport -@synthesize wait; -@synthesize hold; -@synthesize lang; -//@synthesize content; -@synthesize host=to; -@synthesize myJID; - +@synthesize wait = wait_; +@synthesize hold = hold_; +@synthesize lang = lang_; +@synthesize host = host_; +@synthesize myJID = myJID_; +@synthesize sid = sid_; +@synthesize url = url_; @synthesize inactivity; @synthesize secure; @synthesize authid; -@synthesize sid; @synthesize requests; #pragma mark - @@ -94,19 +83,6 @@ - (void)setSecure:(NSString *)isSecure else secure = NO; } -- (void)setAuthid:(NSString *)givenAuthid -{ - [authid autorelease]; - authid = [givenAuthid copy]; -} - -- (void)setSid:(NSString *)newSID -{ - [sid autorelease]; - sid = [newSID copy]; -} - - #pragma mark - #pragma mark init @@ -121,10 +97,10 @@ - (id)initWithUrl:(NSString *)url forHost:(NSString *)host withDelegate:(id Date: Tue, 29 Mar 2011 19:54:03 +0530 Subject: [PATCH 038/180] Adding ASIHTTPRequest to xmppframework --- .../ASIHTTPRequest/ASIAuthenticationDialog.h | 35 + .../ASIHTTPRequest/ASIAuthenticationDialog.m | 487 ++ Vendor/ASIHTTPRequest/ASICacheDelegate.h | 93 + Vendor/ASIHTTPRequest/ASIDataCompressor.h | 42 + Vendor/ASIHTTPRequest/ASIDataCompressor.m | 227 + Vendor/ASIHTTPRequest/ASIDataDecompressor.h | 41 + Vendor/ASIHTTPRequest/ASIDataDecompressor.m | 226 + Vendor/ASIHTTPRequest/ASIDownloadCache.h | 47 + Vendor/ASIHTTPRequest/ASIDownloadCache.m | 494 ++ Vendor/ASIHTTPRequest/ASIFormDataRequest.h | 76 + Vendor/ASIHTTPRequest/ASIFormDataRequest.m | 355 ++ Vendor/ASIHTTPRequest/ASIHTTPRequest.h | 989 ++++ Vendor/ASIHTTPRequest/ASIHTTPRequest.m | 4779 +++++++++++++++++ Vendor/ASIHTTPRequest/ASIHTTPRequestConfig.h | 32 + .../ASIHTTPRequest/ASIHTTPRequestDelegate.h | 35 + Vendor/ASIHTTPRequest/ASIInputStream.h | 26 + Vendor/ASIHTTPRequest/ASIInputStream.m | 136 + Vendor/ASIHTTPRequest/ASINetworkQueue.h | 108 + Vendor/ASIHTTPRequest/ASINetworkQueue.m | 343 ++ Vendor/ASIHTTPRequest/ASIProgressDelegate.h | 38 + Vendor/ASIHTTPRequest/Reachability.h | 193 + Vendor/ASIHTTPRequest/Reachability.m | 814 +++ .../iPhoneXMPP.xcodeproj/project.pbxproj | 130 +- 23 files changed, 9681 insertions(+), 65 deletions(-) create mode 100644 Vendor/ASIHTTPRequest/ASIAuthenticationDialog.h create mode 100644 Vendor/ASIHTTPRequest/ASIAuthenticationDialog.m create mode 100644 Vendor/ASIHTTPRequest/ASICacheDelegate.h create mode 100644 Vendor/ASIHTTPRequest/ASIDataCompressor.h create mode 100644 Vendor/ASIHTTPRequest/ASIDataCompressor.m create mode 100644 Vendor/ASIHTTPRequest/ASIDataDecompressor.h create mode 100644 Vendor/ASIHTTPRequest/ASIDataDecompressor.m create mode 100644 Vendor/ASIHTTPRequest/ASIDownloadCache.h create mode 100644 Vendor/ASIHTTPRequest/ASIDownloadCache.m create mode 100644 Vendor/ASIHTTPRequest/ASIFormDataRequest.h create mode 100644 Vendor/ASIHTTPRequest/ASIFormDataRequest.m create mode 100644 Vendor/ASIHTTPRequest/ASIHTTPRequest.h create mode 100644 Vendor/ASIHTTPRequest/ASIHTTPRequest.m create mode 100644 Vendor/ASIHTTPRequest/ASIHTTPRequestConfig.h create mode 100644 Vendor/ASIHTTPRequest/ASIHTTPRequestDelegate.h create mode 100644 Vendor/ASIHTTPRequest/ASIInputStream.h create mode 100644 Vendor/ASIHTTPRequest/ASIInputStream.m create mode 100644 Vendor/ASIHTTPRequest/ASINetworkQueue.h create mode 100644 Vendor/ASIHTTPRequest/ASINetworkQueue.m create mode 100644 Vendor/ASIHTTPRequest/ASIProgressDelegate.h create mode 100644 Vendor/ASIHTTPRequest/Reachability.h create mode 100644 Vendor/ASIHTTPRequest/Reachability.m diff --git a/Vendor/ASIHTTPRequest/ASIAuthenticationDialog.h b/Vendor/ASIHTTPRequest/ASIAuthenticationDialog.h new file mode 100644 index 0000000..6bbb282 --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIAuthenticationDialog.h @@ -0,0 +1,35 @@ +// +// ASIAuthenticationDialog.h +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 21/08/2009. +// Copyright 2009 All-Seeing Interactive. All rights reserved. +// + +#import +#import +@class ASIHTTPRequest; + +typedef enum _ASIAuthenticationType { + ASIStandardAuthenticationType = 0, + ASIProxyAuthenticationType = 1 +} ASIAuthenticationType; + +@interface ASIAutorotatingViewController : UIViewController +@end + +@interface ASIAuthenticationDialog : ASIAutorotatingViewController { + ASIHTTPRequest *request; + ASIAuthenticationType type; + UITableView *tableView; + UIViewController *presentingController; + BOOL didEnableRotationNotifications; +} ++ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)request; ++ (void)dismiss; + +@property (retain) ASIHTTPRequest *request; +@property (assign) ASIAuthenticationType type; +@property (assign) BOOL didEnableRotationNotifications; +@property (retain, nonatomic) UIViewController *presentingController; +@end diff --git a/Vendor/ASIHTTPRequest/ASIAuthenticationDialog.m b/Vendor/ASIHTTPRequest/ASIAuthenticationDialog.m new file mode 100644 index 0000000..8d9b17f --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIAuthenticationDialog.m @@ -0,0 +1,487 @@ +// +// ASIAuthenticationDialog.m +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 21/08/2009. +// Copyright 2009 All-Seeing Interactive. All rights reserved. +// + +#import "ASIAuthenticationDialog.h" +#import "ASIHTTPRequest.h" +#import + +static ASIAuthenticationDialog *sharedDialog = nil; +BOOL isDismissing = NO; +static NSMutableArray *requestsNeedingAuthentication = nil; + +static const NSUInteger kUsernameRow = 0; +static const NSUInteger kUsernameSection = 0; +static const NSUInteger kPasswordRow = 1; +static const NSUInteger kPasswordSection = 0; +static const NSUInteger kDomainRow = 0; +static const NSUInteger kDomainSection = 1; + + +@implementation ASIAutorotatingViewController + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation +{ + return YES; +} + +@end + + +@interface ASIAuthenticationDialog () +- (void)showTitle; +- (void)show; +- (NSArray *)requestsRequiringTheseCredentials; +- (void)presentNextDialog; +- (void)keyboardWillShow:(NSNotification *)notification; +- (void)orientationChanged:(NSNotification *)notification; +- (void)cancelAuthenticationFromDialog:(id)sender; +- (void)loginWithCredentialsFromDialog:(id)sender; +@property (retain) UITableView *tableView; +@end + +@implementation ASIAuthenticationDialog + +#pragma mark init / dealloc + ++ (void)initialize +{ + if (self == [ASIAuthenticationDialog class]) { + requestsNeedingAuthentication = [[NSMutableArray array] retain]; + } +} + ++ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)theRequest +{ + // No need for a lock here, this will always be called on the main thread + if (!sharedDialog) { + sharedDialog = [[self alloc] init]; + [sharedDialog setRequest:theRequest]; + if ([theRequest authenticationNeeded] == ASIProxyAuthenticationNeeded) { + [sharedDialog setType:ASIProxyAuthenticationType]; + } else { + [sharedDialog setType:ASIStandardAuthenticationType]; + } + [sharedDialog show]; + } else { + [requestsNeedingAuthentication addObject:theRequest]; + } +} + +- (id)init +{ + if ((self = [self initWithNibName:nil bundle:nil])) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { +#endif + if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) { + [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + [self setDidEnableRotationNotifications:YES]; + } + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil]; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + } +#endif + } + return self; +} + +- (void)dealloc +{ + if ([self didEnableRotationNotifications]) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; + } + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; + + [request release]; + [tableView release]; + [presentingController.view removeFromSuperview]; + [presentingController release]; + [super dealloc]; +} + +#pragma mark keyboard notifications + +- (void)keyboardWillShow:(NSNotification *)notification +{ +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { +#endif +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2 + NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey]; +#else + NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey]; +#endif + CGRect keyboardBounds; + [keyboardBoundsValue getValue:&keyboardBounds]; + UIEdgeInsets e = UIEdgeInsetsMake(0, 0, keyboardBounds.size.height, 0); + [[self tableView] setScrollIndicatorInsets:e]; + [[self tableView] setContentInset:e]; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + } +#endif +} + +// Manually handles orientation changes on iPhone +- (void)orientationChanged:(NSNotification *)notification +{ + [self showTitle]; + + UIDeviceOrientation o = [[UIApplication sharedApplication] statusBarOrientation]; + CGFloat angle = 0; + switch (o) { + case UIDeviceOrientationLandscapeLeft: angle = 90; break; + case UIDeviceOrientationLandscapeRight: angle = -90; break; + case UIDeviceOrientationPortraitUpsideDown: angle = 180; break; + default: break; + } + + CGRect f = [[UIScreen mainScreen] applicationFrame]; + + // Swap the frame height and width if necessary + if (UIDeviceOrientationIsLandscape(o)) { + CGFloat t; + t = f.size.width; + f.size.width = f.size.height; + f.size.height = t; + } + + CGAffineTransform previousTransform = self.view.layer.affineTransform; + CGAffineTransform newTransform = CGAffineTransformMakeRotation((CGFloat)(angle * M_PI / 180.0)); + + // Reset the transform so we can set the size + self.view.layer.affineTransform = CGAffineTransformIdentity; + self.view.frame = (CGRect){ { 0, 0 }, f.size}; + + // Revert to the previous transform for correct animation + self.view.layer.affineTransform = previousTransform; + + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationDuration:0.3]; + + // Set the new transform + self.view.layer.affineTransform = newTransform; + + // Fix the view origin + self.view.frame = (CGRect){ { f.origin.x, f.origin.y },self.view.frame.size}; + [UIView commitAnimations]; +} + +#pragma mark utilities + +- (UIViewController *)presentingController +{ + if (!presentingController) { + presentingController = [[ASIAutorotatingViewController alloc] initWithNibName:nil bundle:nil]; + + // Attach to the window, but don't interfere. + UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:0]; + [window addSubview:[presentingController view]]; + [[presentingController view] setFrame:CGRectZero]; + [[presentingController view] setUserInteractionEnabled:NO]; + } + + return presentingController; +} + +- (UITextField *)textFieldInRow:(NSUInteger)row section:(NSUInteger)section +{ + return [[[[[self tableView] cellForRowAtIndexPath: + [NSIndexPath indexPathForRow:row inSection:section]] + contentView] subviews] objectAtIndex:0]; +} + +- (UITextField *)usernameField +{ + return [self textFieldInRow:kUsernameRow section:kUsernameSection]; +} + +- (UITextField *)passwordField +{ + return [self textFieldInRow:kPasswordRow section:kPasswordSection]; +} + +- (UITextField *)domainField +{ + return [self textFieldInRow:kDomainRow section:kDomainSection]; +} + +#pragma mark show / dismiss + ++ (void)dismiss +{ + [[sharedDialog parentViewController] dismissModalViewControllerAnimated:YES]; +} + +- (void)viewDidDisappear:(BOOL)animated +{ + [self retain]; + [sharedDialog release]; + sharedDialog = nil; + [self performSelector:@selector(presentNextDialog) withObject:nil afterDelay:0]; + [self release]; +} + +- (void)dismiss +{ + if (self == sharedDialog) { + [[self class] dismiss]; + } else { + [[self parentViewController] dismissModalViewControllerAnimated:YES]; + } +} + +- (void)showTitle +{ + UINavigationBar *navigationBar = [[[self view] subviews] objectAtIndex:0]; + UINavigationItem *navItem = [[navigationBar items] objectAtIndex:0]; + if (UIInterfaceOrientationIsPortrait([[UIDevice currentDevice] orientation])) { + // Setup the title + if ([self type] == ASIProxyAuthenticationType) { + [navItem setPrompt:@"Login to this secure proxy server."]; + } else { + [navItem setPrompt:@"Login to this secure server."]; + } + } else { + [navItem setPrompt:nil]; + } + [navigationBar sizeToFit]; + CGRect f = [[self view] bounds]; + f.origin.y = [navigationBar frame].size.height; + f.size.height -= f.origin.y; + [[self tableView] setFrame:f]; +} + +- (void)show +{ + // Remove all subviews + UIView *v; + while ((v = [[[self view] subviews] lastObject])) { + [v removeFromSuperview]; + } + + // Setup toolbar + UINavigationBar *bar = [[[UINavigationBar alloc] init] autorelease]; + [bar setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; + + UINavigationItem *navItem = [[[UINavigationItem alloc] init] autorelease]; + bar.items = [NSArray arrayWithObject:navItem]; + + [[self view] addSubview:bar]; + + [self showTitle]; + + // Setup toolbar buttons + if ([self type] == ASIProxyAuthenticationType) { + [navItem setTitle:[[self request] proxyHost]]; + } else { + [navItem setTitle:[[[self request] url] host]]; + } + + [navItem setLeftBarButtonItem:[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAuthenticationFromDialog:)] autorelease]]; + [navItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Login" style:UIBarButtonItemStyleDone target:self action:@selector(loginWithCredentialsFromDialog:)] autorelease]]; + + // We show the login form in a table view, similar to Safari's authentication dialog + [bar sizeToFit]; + CGRect f = [[self view] bounds]; + f.origin.y = [bar frame].size.height; + f.size.height -= f.origin.y; + + [self setTableView:[[[UITableView alloc] initWithFrame:f style:UITableViewStyleGrouped] autorelease]]; + [[self tableView] setDelegate:self]; + [[self tableView] setDataSource:self]; + [[self tableView] setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; + [[self view] addSubview:[self tableView]]; + + // Force reload the table content, and focus the first field to show the keyboard + [[self tableView] reloadData]; + [[[[[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].contentView subviews] objectAtIndex:0] becomeFirstResponder]; + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [self setModalPresentationStyle:UIModalPresentationFormSheet]; + } +#endif + + [[self presentingController] presentModalViewController:self animated:YES]; +} + +#pragma mark button callbacks + +- (void)cancelAuthenticationFromDialog:(id)sender +{ + for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) { + [theRequest cancelAuthentication]; + [requestsNeedingAuthentication removeObject:theRequest]; + } + [self dismiss]; +} + +- (NSArray *)requestsRequiringTheseCredentials +{ + NSMutableArray *requestsRequiringTheseCredentials = [NSMutableArray array]; + NSURL *requestURL = [[self request] url]; + for (ASIHTTPRequest *otherRequest in requestsNeedingAuthentication) { + NSURL *theURL = [otherRequest url]; + if (([otherRequest authenticationNeeded] == [[self request] authenticationNeeded]) && [[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]] && ((![otherRequest authenticationRealm] && ![[self request] authenticationRealm]) || ([otherRequest authenticationRealm] && [[self request] authenticationRealm] && [[[self request] authenticationRealm] isEqualToString:[otherRequest authenticationRealm]]))) { + [requestsRequiringTheseCredentials addObject:otherRequest]; + } + } + [requestsRequiringTheseCredentials addObject:[self request]]; + return requestsRequiringTheseCredentials; +} + +- (void)presentNextDialog +{ + if ([requestsNeedingAuthentication count]) { + ASIHTTPRequest *nextRequest = [requestsNeedingAuthentication objectAtIndex:0]; + [requestsNeedingAuthentication removeObjectAtIndex:0]; + [[self class] presentAuthenticationDialogForRequest:nextRequest]; + } +} + + +- (void)loginWithCredentialsFromDialog:(id)sender +{ + for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) { + + NSString *username = [[self usernameField] text]; + NSString *password = [[self passwordField] text]; + + if (username == nil) { username = @""; } + if (password == nil) { password = @""; } + + if ([self type] == ASIProxyAuthenticationType) { + [theRequest setProxyUsername:username]; + [theRequest setProxyPassword:password]; + } else { + [theRequest setUsername:username]; + [theRequest setPassword:password]; + } + + // Handle NTLM domains + NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme]; + if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) { + NSString *domain = [[self domainField] text]; + if ([self type] == ASIProxyAuthenticationType) { + [theRequest setProxyDomain:domain]; + } else { + [theRequest setDomain:domain]; + } + } + + [theRequest retryUsingSuppliedCredentials]; + [requestsNeedingAuthentication removeObject:theRequest]; + } + [self dismiss]; +} + +#pragma mark table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView +{ + NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme]; + if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) { + return 2; + } + return 1; +} + +- (CGFloat)tableView:(UITableView *)aTableView heightForFooterInSection:(NSInteger)section +{ + if (section == [self numberOfSectionsInTableView:aTableView]-1) { + return 30; + } + return 0; +} + +- (CGFloat)tableView:(UITableView *)aTableView heightForHeaderInSection:(NSInteger)section +{ + if (section == 0) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + return 54; + } +#endif + return 30; + } + return 0; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +{ + if (section == 0) { + return [[self request] authenticationRealm]; + } + return nil; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_0 + UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease]; +#else + UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,0,0) reuseIdentifier:nil] autorelease]; +#endif + + [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; + + CGRect f = CGRectInset([cell bounds], 10, 10); + UITextField *textField = [[[UITextField alloc] initWithFrame:f] autorelease]; + [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; + [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone]; + [textField setAutocorrectionType:UITextAutocorrectionTypeNo]; + + NSUInteger s = [indexPath section]; + NSUInteger r = [indexPath row]; + + if (s == kUsernameSection && r == kUsernameRow) { + [textField setPlaceholder:@"User"]; + } else if (s == kPasswordSection && r == kPasswordRow) { + [textField setPlaceholder:@"Password"]; + [textField setSecureTextEntry:YES]; + } else if (s == kDomainSection && r == kDomainRow) { + [textField setPlaceholder:@"Domain"]; + } + [cell.contentView addSubview:textField]; + + return cell; +} + +- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section +{ + if (section == 0) { + return 2; + } else { + return 1; + } +} + +- (NSString *)tableView:(UITableView *)aTableView titleForFooterInSection:(NSInteger)section +{ + if (section == [self numberOfSectionsInTableView:aTableView]-1) { + // If we're using Basic authentication and the connection is not using SSL, we'll show the plain text message + if ([[[self request] authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && ![[[[self request] url] scheme] isEqualToString:@"https"]) { + return @"Password will be sent in the clear."; + // We are using Digest, NTLM, or any scheme over SSL + } else { + return @"Password will be sent securely."; + } + } + return nil; +} + +#pragma mark - + +@synthesize request; +@synthesize type; +@synthesize tableView; +@synthesize didEnableRotationNotifications; +@synthesize presentingController; +@end diff --git a/Vendor/ASIHTTPRequest/ASICacheDelegate.h b/Vendor/ASIHTTPRequest/ASICacheDelegate.h new file mode 100644 index 0000000..452765b --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASICacheDelegate.h @@ -0,0 +1,93 @@ +// +// ASICacheDelegate.h +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 01/05/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// + +#import +@class ASIHTTPRequest; + +// Cache policies control the behaviour of a cache and how requests use the cache +// When setting a cache policy, you can use a combination of these values as a bitmask +// For example: [request setCachePolicy:ASIAskServerIfModifiedCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy|ASIDoNotWriteToCacheCachePolicy]; +// Note that some of the behaviours below are mutally exclusive - you cannot combine ASIAskServerIfModifiedWhenStaleCachePolicy and ASIAskServerIfModifiedCachePolicy, for example. +typedef enum _ASICachePolicy { + + // The default cache policy. When you set a request to use this, it will use the cache's defaultCachePolicy + // ASIDownloadCache's default cache policy is 'ASIAskServerIfModifiedWhenStaleCachePolicy' + ASIUseDefaultCachePolicy = 0, + + // Tell the request not to read from the cache + ASIDoNotReadFromCacheCachePolicy = 1, + + // The the request not to write to the cache + ASIDoNotWriteToCacheCachePolicy = 2, + + // Ask the server if there is an updated version of this resource (using a conditional GET) ONLY when the cached data is stale + ASIAskServerIfModifiedWhenStaleCachePolicy = 4, + + // Always ask the server if there is an updated version of this resource (using a conditional GET) + ASIAskServerIfModifiedCachePolicy = 8, + + // If cached data exists, use it even if it is stale. This means requests will not talk to the server unless the resource they are requesting is not in the cache + ASIOnlyLoadIfNotCachedCachePolicy = 16, + + // If cached data exists, use it even if it is stale. If cached data does not exist, stop (will not set an error on the request) + ASIDontLoadCachePolicy = 32, + + // Specifies that cached data may be used if the request fails. If cached data is used, the request will succeed without error. Usually used in combination with other options above. + ASIFallbackToCacheIfLoadFailsCachePolicy = 64 +} ASICachePolicy; + +// Cache storage policies control whether cached data persists between application launches (ASICachePermanentlyCacheStoragePolicy) or not (ASICacheForSessionDurationCacheStoragePolicy) +// Calling [ASIHTTPRequest clearSession] will remove any data stored using ASICacheForSessionDurationCacheStoragePolicy +typedef enum _ASICacheStoragePolicy { + ASICacheForSessionDurationCacheStoragePolicy = 0, + ASICachePermanentlyCacheStoragePolicy = 1 +} ASICacheStoragePolicy; + + +@protocol ASICacheDelegate + +@required + +// Should return the cache policy that will be used when requests have their cache policy set to ASIDefaultCachePolicy +- (ASICachePolicy)defaultCachePolicy; + +- (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request; + +// Should Remove cached data for a particular request +- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request; + +// Should return YES if the cache considers its cached response current for the request +// Should return NO is the data is not cached, or (for example) if the cached headers state the request should have expired +- (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request; + +// Should store the response for the passed request in the cache +// When a non-zero maxAge is passed, it should be used as the expiry time for the cached response +- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; + +// Should return an NSDictionary of cached headers for the passed URL, if it is stored in the cache +- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url; + +// Should return the cached body of a response for the passed URL, if it is stored in the cache +- (NSData *)cachedResponseDataForURL:(NSURL *)url; + +// Returns a path to the cached response data, if it exists +- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url; + +// Returns a path to the cached response headers, if they url +- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url; + +// Returns the location to use to store cached response headers for a particular request +- (NSString *)pathToStoreCachedResponseHeadersForRequest:(ASIHTTPRequest *)request; + +// Returns the location to use to store a cached response body for a particular request +- (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request; + +// Clear cached data stored for the passed storage policy +- (void)clearCachedResponsesForStoragePolicy:(ASICacheStoragePolicy)cachePolicy; + +@end diff --git a/Vendor/ASIHTTPRequest/ASIDataCompressor.h b/Vendor/ASIHTTPRequest/ASIDataCompressor.h new file mode 100644 index 0000000..ae0e441 --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIDataCompressor.h @@ -0,0 +1,42 @@ +// +// ASIDataCompressor.h +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 17/08/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// + +// This is a helper class used by ASIHTTPRequest to handle deflating (compressing) data in memory and on disk +// You may also find it helpful if you need to deflate data and files yourself - see the class methods below +// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net + +#import +#import + +@interface ASIDataCompressor : NSObject { + BOOL streamReady; + z_stream zStream; +} + +// Convenience constructor will call setupStream for you ++ (id)compressor; + +// Compress the passed chunk of data +// Passing YES for shouldFinish will finalize the deflated data - you must pass YES when you are on the last chunk of data +- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish; + +// Convenience method - pass it some data, and you'll get deflated data back ++ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err; + +// Convenience method - pass it a file containing the data to compress in sourcePath, and it will write deflated data to destinationPath ++ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err; + +// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'compressor' +- (NSError *)setupStream; + +// Tells zlib to clean up. You need to call this if you need to cancel deflating part way through +// If deflating finishes or fails, this method will be called automatically +- (NSError *)closeStream; + +@property (assign, readonly) BOOL streamReady; +@end diff --git a/Vendor/ASIHTTPRequest/ASIDataCompressor.m b/Vendor/ASIHTTPRequest/ASIDataCompressor.m new file mode 100644 index 0000000..94d678e --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIDataCompressor.m @@ -0,0 +1,227 @@ +// +// ASIDataCompressor.m +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 17/08/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// + +#import "ASIDataCompressor.h" +#import "ASIHTTPRequest.h" + +#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks +#define COMPRESSION_AMOUNT Z_DEFAULT_COMPRESSION + +@interface ASIDataCompressor () ++ (NSError *)deflateErrorWithCode:(int)code; +@end + +@implementation ASIDataCompressor + ++ (id)compressor +{ + ASIDataCompressor *compressor = [[[self alloc] init] autorelease]; + [compressor setupStream]; + return compressor; +} + +- (void)dealloc +{ + if (streamReady) { + [self closeStream]; + } + [super dealloc]; +} + +- (NSError *)setupStream +{ + if (streamReady) { + return nil; + } + // Setup the inflate stream + zStream.zalloc = Z_NULL; + zStream.zfree = Z_NULL; + zStream.opaque = Z_NULL; + zStream.avail_in = 0; + zStream.next_in = 0; + int status = deflateInit2(&zStream, COMPRESSION_AMOUNT, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY); + if (status != Z_OK) { + return [[self class] deflateErrorWithCode:status]; + } + streamReady = YES; + return nil; +} + +- (NSError *)closeStream +{ + if (!streamReady) { + return nil; + } + // Close the deflate stream + streamReady = NO; + int status = deflateEnd(&zStream); + if (status != Z_OK) { + return [[self class] deflateErrorWithCode:status]; + } + return nil; +} + +- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish +{ + if (length == 0) return nil; + + NSUInteger halfLength = length/2; + + // We'll take a guess that the compressed data will fit in half the size of the original (ie the max to compress at once is half DATA_CHUNK_SIZE), if not, we'll increase it below + NSMutableData *outputData = [NSMutableData dataWithLength:length/2]; + + int status; + + zStream.next_in = bytes; + zStream.avail_in = (unsigned int)length; + zStream.avail_out = 0; + NSError *theError = nil; + + NSInteger bytesProcessedAlready = zStream.total_out; + while (zStream.avail_in != 0) { + + if (zStream.total_out-bytesProcessedAlready >= [outputData length]) { + [outputData increaseLengthBy:halfLength]; + } + + zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; + zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready)); + status = deflate(&zStream, shouldFinish ? Z_FINISH : Z_NO_FLUSH); + + if (status == Z_STREAM_END) { + break; + } else if (status != Z_OK) { + if (err) { + *err = [[self class] deflateErrorWithCode:status]; + } + return NO; + } + } + + if (theError) { + if (err) { + *err = theError; + } + return nil; + } + + // Set real length + [outputData setLength: zStream.total_out-bytesProcessedAlready]; + return outputData; +} + + ++ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err +{ + NSError *theError = nil; + NSData *outputData = [[ASIDataCompressor compressor] compressBytes:(Bytef *)[uncompressedData bytes] length:[uncompressedData length] error:&theError shouldFinish:YES]; + if (theError) { + if (err) { + *err = theError; + } + return nil; + } + return outputData; +} + + + ++ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err +{ + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + // Create an empty file at the destination path + if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) { + if (err) { + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]]; + } + return NO; + } + + // Ensure the source file exists + if (![fileManager fileExistsAtPath:sourcePath]) { + if (err) { + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]]; + } + return NO; + } + + UInt8 inputData[DATA_CHUNK_SIZE]; + NSData *outputData; + NSInteger readLength; + NSError *theError = nil; + + ASIDataCompressor *compressor = [ASIDataCompressor compressor]; + + NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath]; + [inputStream open]; + NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO]; + [outputStream open]; + + while ([compressor streamReady]) { + + // Read some data from the file + readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE]; + + // Make sure nothing went wrong + if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { + if (err) { + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]]; + } + [compressor closeStream]; + return NO; + } + // Have we reached the end of the input data? + if (!readLength) { + break; + } + + // Attempt to deflate the chunk of data + outputData = [compressor compressBytes:inputData length:readLength error:&theError shouldFinish:readLength < DATA_CHUNK_SIZE ]; + if (theError) { + if (err) { + *err = theError; + } + [compressor closeStream]; + return NO; + } + + // Write the deflated data out to the destination file + [outputStream write:[outputData bytes] maxLength:[outputData length]]; + + // Make sure nothing went wrong + if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { + if (err) { + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; + } + [compressor closeStream]; + return NO; + } + + } + [inputStream close]; + [outputStream close]; + + NSError *error = [compressor closeStream]; + if (error) { + if (err) { + *err = error; + } + return NO; + } + + return YES; +} + ++ (NSError *)deflateErrorWithCode:(int)code +{ + return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of data failed with code %hi",code],NSLocalizedDescriptionKey,nil]]; +} + +@synthesize streamReady; +@end diff --git a/Vendor/ASIHTTPRequest/ASIDataDecompressor.h b/Vendor/ASIHTTPRequest/ASIDataDecompressor.h new file mode 100644 index 0000000..8be8f9b --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIDataDecompressor.h @@ -0,0 +1,41 @@ +// +// ASIDataDecompressor.h +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 17/08/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// + +// This is a helper class used by ASIHTTPRequest to handle inflating (decompressing) data in memory and on disk +// You may also find it helpful if you need to inflate data and files yourself - see the class methods below +// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net + +#import +#import + +@interface ASIDataDecompressor : NSObject { + BOOL streamReady; + z_stream zStream; +} + +// Convenience constructor will call setupStream for you ++ (id)decompressor; + +// Uncompress the passed chunk of data +- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err; + +// Convenience method - pass it some deflated data, and you'll get inflated data back ++ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err; + +// Convenience method - pass it a file containing deflated data in sourcePath, and it will write inflated data to destinationPath ++ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err; + +// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'decompressor' +- (NSError *)setupStream; + +// Tells zlib to clean up. You need to call this if you need to cancel inflating part way through +// If inflating finishes or fails, this method will be called automatically +- (NSError *)closeStream; + +@property (assign, readonly) BOOL streamReady; +@end diff --git a/Vendor/ASIHTTPRequest/ASIDataDecompressor.m b/Vendor/ASIHTTPRequest/ASIDataDecompressor.m new file mode 100644 index 0000000..89797c5 --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIDataDecompressor.m @@ -0,0 +1,226 @@ +// +// ASIDataDecompressor.m +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 17/08/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// + +#import "ASIDataDecompressor.h" +#import "ASIHTTPRequest.h" + +#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks + +@interface ASIDataDecompressor () ++ (NSError *)inflateErrorWithCode:(int)code; +@end; + +@implementation ASIDataDecompressor + ++ (id)decompressor +{ + ASIDataDecompressor *decompressor = [[[self alloc] init] autorelease]; + [decompressor setupStream]; + return decompressor; +} + +- (void)dealloc +{ + if (streamReady) { + [self closeStream]; + } + [super dealloc]; +} + +- (NSError *)setupStream +{ + if (streamReady) { + return nil; + } + // Setup the inflate stream + zStream.zalloc = Z_NULL; + zStream.zfree = Z_NULL; + zStream.opaque = Z_NULL; + zStream.avail_in = 0; + zStream.next_in = 0; + int status = inflateInit2(&zStream, (15+32)); + if (status != Z_OK) { + return [[self class] inflateErrorWithCode:status]; + } + streamReady = YES; + return nil; +} + +- (NSError *)closeStream +{ + if (!streamReady) { + return nil; + } + // Close the inflate stream + streamReady = NO; + int status = inflateEnd(&zStream); + if (status != Z_OK) { + return [[self class] inflateErrorWithCode:status]; + } + return nil; +} + +- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err +{ + if (length == 0) return nil; + + NSUInteger halfLength = length/2; + NSMutableData *outputData = [NSMutableData dataWithLength:length+halfLength]; + + int status; + + zStream.next_in = bytes; + zStream.avail_in = (unsigned int)length; + zStream.avail_out = 0; + NSError *theError = nil; + + NSInteger bytesProcessedAlready = zStream.total_out; + while (zStream.avail_in != 0) { + + if (zStream.total_out-bytesProcessedAlready >= [outputData length]) { + [outputData increaseLengthBy:halfLength]; + } + + zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; + zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready)); + + status = inflate(&zStream, Z_NO_FLUSH); + + if (status == Z_STREAM_END) { + break; + } else if (status != Z_OK) { + if (err) { + *err = [[self class] inflateErrorWithCode:status]; + } + return nil; + } + } + + if (theError) { + if (err) { + *err = theError; + } + return nil; + } + + // Set real length + [outputData setLength: zStream.total_out-bytesProcessedAlready]; + return outputData; +} + + ++ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err +{ + NSError *theError = nil; + NSData *outputData = [[ASIDataDecompressor decompressor] uncompressBytes:(Bytef *)[compressedData bytes] length:[compressedData length] error:&theError]; + if (theError) { + if (err) { + *err = theError; + } + return nil; + } + return outputData; +} + ++ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err +{ + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + // Create an empty file at the destination path + if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) { + if (err) { + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]]; + } + return NO; + } + + // Ensure the source file exists + if (![fileManager fileExistsAtPath:sourcePath]) { + if (err) { + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]]; + } + return NO; + } + + UInt8 inputData[DATA_CHUNK_SIZE]; + NSData *outputData; + NSInteger readLength; + NSError *theError = nil; + + + ASIDataDecompressor *decompressor = [ASIDataDecompressor decompressor]; + + NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath]; + [inputStream open]; + NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO]; + [outputStream open]; + + while ([decompressor streamReady]) { + + // Read some data from the file + readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE]; + + // Make sure nothing went wrong + if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { + if (err) { + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]]; + } + [decompressor closeStream]; + return NO; + } + // Have we reached the end of the input data? + if (!readLength) { + break; + } + + // Attempt to inflate the chunk of data + outputData = [decompressor uncompressBytes:inputData length:readLength error:&theError]; + if (theError) { + if (err) { + *err = theError; + } + [decompressor closeStream]; + return NO; + } + + // Write the inflated data out to the destination file + [outputStream write:[outputData bytes] maxLength:[outputData length]]; + + // Make sure nothing went wrong + if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { + if (err) { + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; + } + [decompressor closeStream]; + return NO; + } + + } + + [inputStream close]; + [outputStream close]; + + NSError *error = [decompressor closeStream]; + if (error) { + if (err) { + *err = error; + } + return NO; + } + + return YES; +} + + ++ (NSError *)inflateErrorWithCode:(int)code +{ + return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of data failed with code %hi",code],NSLocalizedDescriptionKey,nil]]; +} + +@synthesize streamReady; +@end diff --git a/Vendor/ASIHTTPRequest/ASIDownloadCache.h b/Vendor/ASIHTTPRequest/ASIDownloadCache.h new file mode 100644 index 0000000..f317a48 --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIDownloadCache.h @@ -0,0 +1,47 @@ +// +// ASIDownloadCache.h +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 01/05/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// + +#import +#import "ASICacheDelegate.h" + +@interface ASIDownloadCache : NSObject { + + // The default cache policy for this cache + // Requests that store data in the cache will use this cache policy if their cache policy is set to ASIUseDefaultCachePolicy + // Defaults to ASIAskServerIfModifiedWhenStaleCachePolicy + ASICachePolicy defaultCachePolicy; + + // The directory in which cached data will be stored + // Defaults to a directory called 'ASIHTTPRequestCache' in the temporary directory + NSString *storagePath; + + // Mediates access to the cache + NSRecursiveLock *accessLock; + + // When YES, the cache will look for cache-control / pragma: no-cache headers, and won't reuse store responses if it finds them + BOOL shouldRespectCacheControlHeaders; +} + +// Returns a static instance of an ASIDownloadCache +// In most circumstances, it will make sense to use this as a global cache, rather than creating your own cache +// To make ASIHTTPRequests use it automatically, use [ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]]; ++ (id)sharedCache; + +// A helper function that determines if the server has requested data should not be cached by looking at the request's response headers ++ (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request; + +// A date formatter that can be used to construct an RFC 1123 date +// The returned formatter is safe to use on the calling thread +// Do not use this formatter for parsing dates because the format can vary slightly - use ASIHTTPRequest's dateFromRFC1123String: class method instead ++ (NSDateFormatter *)rfc1123DateFormatter; + +@property (assign, nonatomic) ASICachePolicy defaultCachePolicy; +@property (retain, nonatomic) NSString *storagePath; +@property (retain) NSRecursiveLock *accessLock; +@property (assign) BOOL shouldRespectCacheControlHeaders; +@end diff --git a/Vendor/ASIHTTPRequest/ASIDownloadCache.m b/Vendor/ASIHTTPRequest/ASIDownloadCache.m new file mode 100644 index 0000000..c455094 --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIDownloadCache.m @@ -0,0 +1,494 @@ +// +// ASIDownloadCache.m +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 01/05/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// + +#import "ASIDownloadCache.h" +#import "ASIHTTPRequest.h" +#import + +static ASIDownloadCache *sharedCache = nil; + +static NSString *sessionCacheFolder = @"SessionStore"; +static NSString *permanentCacheFolder = @"PermanentStore"; + +@interface ASIDownloadCache () ++ (NSString *)keyForURL:(NSURL *)url; +@end + +@implementation ASIDownloadCache + +- (id)init +{ + self = [super init]; + [self setShouldRespectCacheControlHeaders:YES]; + [self setDefaultCachePolicy:ASIUseDefaultCachePolicy]; + [self setAccessLock:[[[NSRecursiveLock alloc] init] autorelease]]; + return self; +} + ++ (id)sharedCache +{ + if (!sharedCache) { + sharedCache = [[self alloc] init]; + [sharedCache setStoragePath:[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"ASIHTTPRequestCache"]]; + + } + return sharedCache; +} + +- (void)dealloc +{ + [storagePath release]; + [accessLock release]; + [super dealloc]; +} + +- (NSString *)storagePath +{ + [[self accessLock] lock]; + NSString *p = [[storagePath retain] autorelease]; + [[self accessLock] unlock]; + return p; +} + + +- (void)setStoragePath:(NSString *)path +{ + [[self accessLock] lock]; + [self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy]; + [storagePath release]; + storagePath = [path retain]; + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + BOOL isDirectory = NO; + NSArray *directories = [NSArray arrayWithObjects:path,[path stringByAppendingPathComponent:sessionCacheFolder],[path stringByAppendingPathComponent:permanentCacheFolder],nil]; + for (NSString *directory in directories) { + BOOL exists = [fileManager fileExistsAtPath:directory isDirectory:&isDirectory]; + if (exists && !isDirectory) { + [[self accessLock] unlock]; + [NSException raise:@"FileExistsAtCachePath" format:@"Cannot create a directory for the cache at '%@', because a file already exists",directory]; + } else if (!exists) { + [fileManager createDirectoryAtPath:directory withIntermediateDirectories:NO attributes:nil error:nil]; + if (![fileManager fileExistsAtPath:directory]) { + [[self accessLock] unlock]; + [NSException raise:@"FailedToCreateCacheDirectory" format:@"Failed to create a directory for the cache at '%@'",directory]; + } + } + } + [self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy]; + [[self accessLock] unlock]; +} + +- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge +{ + [[self accessLock] lock]; + + if ([request error] || ![request responseHeaders] || ([request responseStatusCode] != 200) || ([request cachePolicy] & ASIDoNotWriteToCacheCachePolicy)) { + [[self accessLock] unlock]; + return; + } + + if ([self shouldRespectCacheControlHeaders] && ![[self class] serverAllowsResponseCachingForRequest:request]) { + [[self accessLock] unlock]; + return; + } + + NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request]; + NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request]; + + NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]]; + if ([request isResponseCompressed]) { + [responseHeaders removeObjectForKey:@"Content-Encoding"]; + } + if (maxAge != 0) { + [responseHeaders removeObjectForKey:@"Expires"]; + [responseHeaders setObject:[NSString stringWithFormat:@"max-age=%i",(int)maxAge] forKey:@"Cache-Control"]; + } + // We use this special key to help expire the request when we get a max-age header + [responseHeaders setObject:[[[self class] rfc1123DateFormatter] stringFromDate:[NSDate date]] forKey:@"X-ASIHTTPRequest-Fetch-date"]; + [responseHeaders writeToFile:headerPath atomically:NO]; + + if ([request responseData]) { + [[request responseData] writeToFile:dataPath atomically:NO]; + } else if ([request downloadDestinationPath] && ![[request downloadDestinationPath] isEqualToString:dataPath]) { + NSError *error = nil; + [[[[NSFileManager alloc] init] autorelease] copyItemAtPath:[request downloadDestinationPath] toPath:dataPath error:&error]; + } + [[self accessLock] unlock]; +} + +- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url +{ + NSString *path = [self pathToCachedResponseHeadersForURL:url]; + if (path) { + return [NSDictionary dictionaryWithContentsOfFile:path]; + } + return nil; +} + +- (NSData *)cachedResponseDataForURL:(NSURL *)url +{ + NSString *path = [self pathToCachedResponseDataForURL:url]; + if (path) { + return [NSData dataWithContentsOfFile:path]; + } + return nil; +} + +- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return nil; + } + // Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view + NSString *extension = [[url path] pathExtension]; + if (![extension length]) { + extension = @"html"; + } + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + // Look in the session store + NSString *path = [[self storagePath] stringByAppendingPathComponent:sessionCacheFolder]; + NSString *dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]]; + if ([fileManager fileExistsAtPath:dataPath]) { + [[self accessLock] unlock]; + return dataPath; + } + // Look in the permanent store + path = [[self storagePath] stringByAppendingPathComponent:permanentCacheFolder]; + dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]]; + if ([fileManager fileExistsAtPath:dataPath]) { + [[self accessLock] unlock]; + return dataPath; + } + [[self accessLock] unlock]; + return nil; +} + +- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return nil; + } + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + // Look in the session store + NSString *path = [[self storagePath] stringByAppendingPathComponent:sessionCacheFolder]; + NSString *dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"cachedheaders"]]; + if ([fileManager fileExistsAtPath:dataPath]) { + [[self accessLock] unlock]; + return dataPath; + } + // Look in the permanent store + path = [[self storagePath] stringByAppendingPathComponent:permanentCacheFolder]; + dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"cachedheaders"]]; + if ([fileManager fileExistsAtPath:dataPath]) { + [[self accessLock] unlock]; + return dataPath; + } + [[self accessLock] unlock]; + return nil; +} + +- (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return nil; + } + + NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)]; + + // Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view + NSString *extension = [[[request url] path] pathExtension]; + if (![extension length]) { + extension = @"html"; + } + path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:extension]]; + [[self accessLock] unlock]; + return path; +} + +- (NSString *)pathToStoreCachedResponseHeadersForRequest:(ASIHTTPRequest *)request +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return nil; + } + NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)]; + path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:@"cachedheaders"]]; + [[self accessLock] unlock]; + return path; +} + + +- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return; + } + + NSString *cachedHeadersPath = [self pathToCachedResponseHeadersForURL:[request url]]; + if (!cachedHeadersPath) { + [[self accessLock] unlock]; + return; + } + NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]]; + if (!dataPath) { + [[self accessLock] unlock]; + return; + } + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + [fileManager removeItemAtPath:cachedHeadersPath error:NULL]; + [fileManager removeItemAtPath:dataPath error:NULL]; + [[self accessLock] unlock]; +} + +- (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return NO; + } + NSDictionary *cachedHeaders = [self cachedResponseHeadersForURL:[request url]]; + if (!cachedHeaders) { + [[self accessLock] unlock]; + return NO; + } + NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]]; + if (!dataPath) { + [[self accessLock] unlock]; + return NO; + } + + // If we already have response headers for this request, check to see if the new content is different + if ([request responseHeaders]) { + + // New content is not different + if ([request responseStatusCode] == 304) { + [[self accessLock] unlock]; + return YES; + } + + // If the Etag or Last-Modified date are different from the one we have, we'll have to fetch this resource again + NSArray *headersToCompare = [NSArray arrayWithObjects:@"Etag",@"Last-Modified",nil]; + for (NSString *header in headersToCompare) { + if (![[[request responseHeaders] objectForKey:header] isEqualToString:[cachedHeaders objectForKey:header]]) { + [[self accessLock] unlock]; + return NO; + } + } + } + + if ([self shouldRespectCacheControlHeaders]) { + + // Look for a max-age header + NSString *cacheControl = [[cachedHeaders objectForKey:@"Cache-Control"] lowercaseString]; + if (cacheControl) { + NSScanner *scanner = [NSScanner scannerWithString:cacheControl]; + [scanner scanUpToString:@"max-age" intoString:NULL]; + if ([scanner scanString:@"max-age" intoString:NULL]) { + [scanner scanString:@"=" intoString:NULL]; + NSTimeInterval maxAge = 0; + [scanner scanDouble:&maxAge]; + + NSDate *fetchDate = [ASIHTTPRequest dateFromRFC1123String:[cachedHeaders objectForKey:@"X-ASIHTTPRequest-Fetch-date"]]; + NSDate *expiryDate = [[[NSDate alloc] initWithTimeInterval:maxAge sinceDate:fetchDate] autorelease]; + + if ([expiryDate timeIntervalSinceNow] >= 0) { + [[self accessLock] unlock]; + return YES; + } + // RFC 2612 says max-age must override any Expires header + [[self accessLock] unlock]; + return NO; + } + } + + // Look for an Expires header to see if the content is out of date + NSString *expires = [cachedHeaders objectForKey:@"Expires"]; + if (expires) { + if ([[ASIHTTPRequest dateFromRFC1123String:expires] timeIntervalSinceNow] >= 0) { + [[self accessLock] unlock]; + return YES; + } + } + + // No explicit expiration time sent by the server + [[self accessLock] unlock]; + return NO; + } + + + [[self accessLock] unlock]; + return YES; +} + +- (ASICachePolicy)defaultCachePolicy +{ + [[self accessLock] lock]; + ASICachePolicy cp = defaultCachePolicy; + [[self accessLock] unlock]; + return cp; +} + + +- (void)setDefaultCachePolicy:(ASICachePolicy)cachePolicy +{ + [[self accessLock] lock]; + if (!cachePolicy) { + defaultCachePolicy = ASIAskServerIfModifiedWhenStaleCachePolicy; + } else { + defaultCachePolicy = cachePolicy; + } + [[self accessLock] unlock]; +} + +- (void)clearCachedResponsesForStoragePolicy:(ASICacheStoragePolicy)storagePolicy +{ + [[self accessLock] lock]; + if (![self storagePath]) { + [[self accessLock] unlock]; + return; + } + NSString *path = [[self storagePath] stringByAppendingPathComponent:(storagePolicy == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)]; + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + BOOL isDirectory = NO; + BOOL exists = [fileManager fileExistsAtPath:path isDirectory:&isDirectory]; + if (!exists || !isDirectory) { + [[self accessLock] unlock]; + return; + } + NSError *error = nil; + NSArray *cacheFiles = [fileManager contentsOfDirectoryAtPath:path error:&error]; + if (error) { + [[self accessLock] unlock]; + [NSException raise:@"FailedToTraverseCacheDirectory" format:@"Listing cache directory failed at path '%@'",path]; + } + for (NSString *file in cacheFiles) { + [fileManager removeItemAtPath:[path stringByAppendingPathComponent:file] error:&error]; + if (error) { + [[self accessLock] unlock]; + [NSException raise:@"FailedToRemoveCacheFile" format:@"Failed to remove cached data at path '%@'",path]; + } + } + [[self accessLock] unlock]; +} + ++ (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request +{ + NSString *cacheControl = [[[request responseHeaders] objectForKey:@"Cache-Control"] lowercaseString]; + if (cacheControl) { + if ([cacheControl isEqualToString:@"no-cache"] || [cacheControl isEqualToString:@"no-store"]) { + return NO; + } + } + NSString *pragma = [[[request responseHeaders] objectForKey:@"Pragma"] lowercaseString]; + if (pragma) { + if ([pragma isEqualToString:@"no-cache"]) { + return NO; + } + } + return YES; +} + +// Borrowed from: http://stackoverflow.com/questions/652300/using-md5-hash-on-a-string-in-cocoa ++ (NSString *)keyForURL:(NSURL *)url +{ + NSString *urlString = [url absoluteString]; + // Strip trailing slashes so http://allseeing-i.com/ASIHTTPRequest/ is cached the same as http://allseeing-i.com/ASIHTTPRequest + if ([[urlString substringFromIndex:[urlString length]-1] isEqualToString:@"/"]) { + urlString = [urlString substringToIndex:[urlString length]-1]; + } + const char *cStr = [urlString UTF8String]; + unsigned char result[16]; + CC_MD5(cStr, (CC_LONG)strlen(cStr), result); + return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],result[8], result[9], result[10], result[11],result[12], result[13], result[14], result[15]]; +} + ++ (NSDateFormatter *)rfc1123DateFormatter +{ + NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary]; + NSDateFormatter *dateFormatter = [threadDict objectForKey:@"ASIDownloadCacheDateFormatter"]; + if (dateFormatter == nil) { + dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; + [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]]; + [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + [dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss 'GMT'"]; + [threadDict setObject:dateFormatter forKey:@"ASIDownloadCacheDateFormatter"]; + } + return dateFormatter; +} + + +- (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request +{ + // Ensure the request is allowed to read from the cache + if ([request cachePolicy] & ASIDoNotReadFromCacheCachePolicy) { + return NO; + + // If we don't want to load the request whatever happens, always pretend we have cached data even if we don't + } else if ([request cachePolicy] & ASIDontLoadCachePolicy) { + return YES; + } + + NSDictionary *headers = [self cachedResponseHeadersForURL:[request url]]; + if (!headers) { + return NO; + } + NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]]; + if (!dataPath) { + return NO; + } + + // If we get here, we have cached data + + // If we have cached data, we can use it + if ([request cachePolicy] & ASIOnlyLoadIfNotCachedCachePolicy) { + return YES; + + // If we want to fallback to the cache after an error + } else if ([request complete] && [request cachePolicy] & ASIFallbackToCacheIfLoadFailsCachePolicy) { + return YES; + + // If we have cached data that is current, we can use it + } else if ([request cachePolicy] & ASIAskServerIfModifiedWhenStaleCachePolicy) { + if ([self isCachedDataCurrentForRequest:request]) { + return YES; + } + + // If we've got headers from a conditional GET and the cached data is still current, we can use it + } else if ([request cachePolicy] & ASIAskServerIfModifiedCachePolicy) { + if (![request responseHeaders]) { + return NO; + } else if ([self isCachedDataCurrentForRequest:request]) { + return YES; + } + } + return NO; +} + +@synthesize storagePath; +@synthesize defaultCachePolicy; +@synthesize accessLock; +@synthesize shouldRespectCacheControlHeaders; +@end diff --git a/Vendor/ASIHTTPRequest/ASIFormDataRequest.h b/Vendor/ASIHTTPRequest/ASIFormDataRequest.h new file mode 100644 index 0000000..e206fd2 --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIFormDataRequest.h @@ -0,0 +1,76 @@ +// +// ASIFormDataRequest.h +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 07/11/2008. +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved. +// + +#import +#import "ASIHTTPRequest.h" +#import "ASIHTTPRequestConfig.h" + +typedef enum _ASIPostFormat { + ASIMultipartFormDataPostFormat = 0, + ASIURLEncodedPostFormat = 1 + +} ASIPostFormat; + +@interface ASIFormDataRequest : ASIHTTPRequest { + + // Parameters that will be POSTed to the url + NSMutableArray *postData; + + // Files that will be POSTed to the url + NSMutableArray *fileData; + + ASIPostFormat postFormat; + + NSStringEncoding stringEncoding; + +#if DEBUG_FORM_DATA_REQUEST + // Will store a string version of the request body that will be printed to the console when ASIHTTPREQUEST_DEBUG is set in GCC_PREPROCESSOR_DEFINITIONS + NSString *debugBodyString; +#endif + +} + +#pragma mark utilities +- (NSString*)encodeURL:(NSString *)string; + +#pragma mark setup request + +// Add a POST variable to the request +- (void)addPostValue:(id )value forKey:(NSString *)key; + +// Set a POST variable for this request, clearing any others with the same key +- (void)setPostValue:(id )value forKey:(NSString *)key; + +// Add the contents of a local file to the request +- (void)addFile:(NSString *)filePath forKey:(NSString *)key; + +// Same as above, but you can specify the content-type and file name +- (void)addFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; + +// Add the contents of a local file to the request, clearing any others with the same key +- (void)setFile:(NSString *)filePath forKey:(NSString *)key; + +// Same as above, but you can specify the content-type and file name +- (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; + +// Add the contents of an NSData object to the request +- (void)addData:(NSData *)data forKey:(NSString *)key; + +// Same as above, but you can specify the content-type and file name +- (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; + +// Add the contents of an NSData object to the request, clearing any others with the same key +- (void)setData:(NSData *)data forKey:(NSString *)key; + +// Same as above, but you can specify the content-type and file name +- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; + + +@property (assign) ASIPostFormat postFormat; +@property (assign) NSStringEncoding stringEncoding; +@end diff --git a/Vendor/ASIHTTPRequest/ASIFormDataRequest.m b/Vendor/ASIHTTPRequest/ASIFormDataRequest.m new file mode 100644 index 0000000..b3ea8ed --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIFormDataRequest.m @@ -0,0 +1,355 @@ +// +// ASIFormDataRequest.m +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 07/11/2008. +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved. +// + +#import "ASIFormDataRequest.h" + + +// Private stuff +@interface ASIFormDataRequest () +- (void)buildMultipartFormDataPostBody; +- (void)buildURLEncodedPostBody; +- (void)appendPostString:(NSString *)string; + +@property (retain) NSMutableArray *postData; +@property (retain) NSMutableArray *fileData; + +#if DEBUG_FORM_DATA_REQUEST +- (void)addToDebugBody:(NSString *)string; +@property (retain, nonatomic) NSString *debugBodyString; +#endif + +@end + +@implementation ASIFormDataRequest + +#pragma mark utilities +- (NSString*)encodeURL:(NSString *)string +{ + NSString *newString = NSMakeCollectable([(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding([self stringEncoding])) autorelease]); + if (newString) { + return newString; + } + return @""; +} + +#pragma mark init / dealloc + ++ (id)requestWithURL:(NSURL *)newURL +{ + return [[[self alloc] initWithURL:newURL] autorelease]; +} + +- (id)initWithURL:(NSURL *)newURL +{ + self = [super initWithURL:newURL]; + [self setPostFormat:ASIURLEncodedPostFormat]; + [self setStringEncoding:NSUTF8StringEncoding]; + return self; +} + +- (void)dealloc +{ +#if DEBUG_FORM_DATA_REQUEST + [debugBodyString release]; +#endif + + [postData release]; + [fileData release]; + [super dealloc]; +} + +#pragma mark setup request + +- (void)addPostValue:(id )value forKey:(NSString *)key +{ + if (![self postData]) { + [self setPostData:[NSMutableArray array]]; + } + [[self postData] addObject:[NSDictionary dictionaryWithObjectsAndKeys:[value description],@"value",key,@"key",nil]]; +} + +- (void)setPostValue:(id )value forKey:(NSString *)key +{ + // Remove any existing value + NSUInteger i; + for (i=0; i<[[self postData] count]; i++) { + NSDictionary *val = [[self postData] objectAtIndex:i]; + if ([[val objectForKey:@"key"] isEqualToString:key]) { + [[self postData] removeObjectAtIndex:i]; + i--; + } + } + [self addPostValue:value forKey:key]; +} + + +- (void)addFile:(NSString *)filePath forKey:(NSString *)key +{ + [self addFile:filePath withFileName:nil andContentType:nil forKey:key]; +} + +- (void)addFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key +{ + if (![self fileData]) { + [self setFileData:[NSMutableArray array]]; + } + + // If data is a path to a local file + if ([data isKindOfClass:[NSString class]]) { + BOOL isDirectory = NO; + BOOL fileExists = [[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:(NSString *)data isDirectory:&isDirectory]; + if (!fileExists || isDirectory) { + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@",data],NSLocalizedDescriptionKey,nil]]]; + } + + // If the caller didn't specify a custom file name, we'll use the file name of the file we were passed + if (!fileName) { + fileName = [(NSString *)data lastPathComponent]; + } + + // If we were given the path to a file, and the user didn't specify a mime type, we can detect it from the file extension + if (!contentType) { + contentType = [ASIHTTPRequest mimeTypeForFileAtPath:data]; + } + } + + NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", key, @"key", nil]; + [[self fileData] addObject:fileInfo]; +} + + +- (void)setFile:(NSString *)filePath forKey:(NSString *)key +{ + [self setFile:filePath withFileName:nil andContentType:nil forKey:key]; +} + +- (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key +{ + // Remove any existing value + NSUInteger i; + for (i=0; i<[[self fileData] count]; i++) { + NSDictionary *val = [[self fileData] objectAtIndex:i]; + if ([[val objectForKey:@"key"] isEqualToString:key]) { + [[self fileData] removeObjectAtIndex:i]; + i--; + } + } + [self addFile:data withFileName:fileName andContentType:contentType forKey:key]; +} + +- (void)addData:(NSData *)data forKey:(NSString *)key +{ + [self addData:data withFileName:@"file" andContentType:nil forKey:key]; +} + +- (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key +{ + if (![self fileData]) { + [self setFileData:[NSMutableArray array]]; + } + if (!contentType) { + contentType = @"application/octet-stream"; + } + + NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", key, @"key", nil]; + [[self fileData] addObject:fileInfo]; +} + +- (void)setData:(NSData *)data forKey:(NSString *)key +{ + [self setData:data withFileName:@"file" andContentType:nil forKey:key]; +} + +- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key +{ + // Remove any existing value + NSUInteger i; + for (i=0; i<[[self fileData] count]; i++) { + NSDictionary *val = [[self fileData] objectAtIndex:i]; + if ([[val objectForKey:@"key"] isEqualToString:key]) { + [[self fileData] removeObjectAtIndex:i]; + i--; + } + } + [self addData:data withFileName:fileName andContentType:contentType forKey:key]; +} + +- (void)buildPostBody +{ + if ([self haveBuiltPostBody]) { + return; + } + +#if DEBUG_FORM_DATA_REQUEST + [self setDebugBodyString:@""]; +#endif + + if (![self postData] && ![self fileData]) { + [super buildPostBody]; + return; + } + if ([[self fileData] count] > 0) { + [self setShouldStreamPostDataFromDisk:YES]; + } + + if ([self postFormat] == ASIURLEncodedPostFormat) { + [self buildURLEncodedPostBody]; + } else { + [self buildMultipartFormDataPostBody]; + } + + [super buildPostBody]; + +#if DEBUG_FORM_DATA_REQUEST + NSLog(@"%@",[self debugBodyString]); + [self setDebugBodyString:nil]; +#endif +} + + +- (void)buildMultipartFormDataPostBody +{ +#if DEBUG_FORM_DATA_REQUEST + [self addToDebugBody:@"\r\n==== Building a multipart/form-data body ====\r\n"]; +#endif + + NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding])); + + // Set your own boundary string only if really obsessive. We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does. + NSString *stringBoundary = @"0xKhTmLbOuNdArY"; + + [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]]; + + [self appendPostString:[NSString stringWithFormat:@"--%@\r\n",stringBoundary]]; + + // Adds post data + NSString *endItemBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary]; + NSUInteger i=0; + for (NSDictionary *val in [self postData]) { + [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",[val objectForKey:@"key"]]]; + [self appendPostString:[val objectForKey:@"value"]]; + i++; + if (i != [[self postData] count] || [[self fileData] count] > 0) { //Only add the boundary if this is not the last item in the post body + [self appendPostString:endItemBoundary]; + } + } + + // Adds files to upload + i=0; + for (NSDictionary *val in [self fileData]) { + + [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", [val objectForKey:@"key"], [val objectForKey:@"fileName"]]]; + [self appendPostString:[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", [val objectForKey:@"contentType"]]]; + + id data = [val objectForKey:@"data"]; + if ([data isKindOfClass:[NSString class]]) { + [self appendPostDataFromFile:data]; + } else { + [self appendPostData:data]; + } + i++; + // Only add the boundary if this is not the last item in the post body + if (i != [[self fileData] count]) { + [self appendPostString:endItemBoundary]; + } + } + + [self appendPostString:[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary]]; + +#if DEBUG_FORM_DATA_REQUEST + [self addToDebugBody:@"==== End of multipart/form-data body ====\r\n"]; +#endif +} + +- (void)buildURLEncodedPostBody +{ + + // We can't post binary data using application/x-www-form-urlencoded + if ([[self fileData] count] > 0) { + [self setPostFormat:ASIMultipartFormDataPostFormat]; + [self buildMultipartFormDataPostBody]; + return; + } + +#if DEBUG_FORM_DATA_REQUEST + [self addToDebugBody:@"\r\n==== Building an application/x-www-form-urlencoded body ====\r\n"]; +#endif + + + NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding])); + + [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]]; + + + NSUInteger i=0; + NSUInteger count = [[self postData] count]-1; + for (NSDictionary *val in [self postData]) { + NSString *data = [NSString stringWithFormat:@"%@=%@%@", [self encodeURL:[val objectForKey:@"key"]], [self encodeURL:[val objectForKey:@"value"]],(i +#if TARGET_OS_IPHONE + #import + #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 + #import // Necessary for background task support + #endif +#endif + +#import +#import "ASIHTTPRequestConfig.h" +#import "ASIHTTPRequestDelegate.h" +#import "ASIProgressDelegate.h" +#import "ASICacheDelegate.h" + +@class ASIDataDecompressor; + +extern NSString *ASIHTTPRequestVersion; + +// Make targeting different platforms more reliable +// See: http://www.blumtnwerx.com/blog/2009/06/cross-sdk-code-hygiene-in-xcode/ +#ifndef __IPHONE_3_2 + #define __IPHONE_3_2 30200 +#endif +#ifndef __IPHONE_4_0 + #define __IPHONE_4_0 40000 +#endif +#ifndef __MAC_10_5 + #define __MAC_10_5 1050 +#endif +#ifndef __MAC_10_6 + #define __MAC_10_6 1060 +#endif + +typedef enum _ASIAuthenticationState { + ASINoAuthenticationNeededYet = 0, + ASIHTTPAuthenticationNeeded = 1, + ASIProxyAuthenticationNeeded = 2 +} ASIAuthenticationState; + +typedef enum _ASINetworkErrorType { + ASIConnectionFailureErrorType = 1, + ASIRequestTimedOutErrorType = 2, + ASIAuthenticationErrorType = 3, + ASIRequestCancelledErrorType = 4, + ASIUnableToCreateRequestErrorType = 5, + ASIInternalErrorWhileBuildingRequestType = 6, + ASIInternalErrorWhileApplyingCredentialsType = 7, + ASIFileManagementError = 8, + ASITooMuchRedirectionErrorType = 9, + ASIUnhandledExceptionError = 10, + ASICompressionError = 11 + +} ASINetworkErrorType; + + +// The error domain that all errors generated by ASIHTTPRequest use +extern NSString* const NetworkRequestErrorDomain; + +// You can use this number to throttle upload and download bandwidth in iPhone OS apps send or receive a large amount of data +// This may help apps that might otherwise be rejected for inclusion into the app store for using excessive bandwidth +// This number is not official, as far as I know there is no officially documented bandwidth limit +extern unsigned long const ASIWWANBandwidthThrottleAmount; + +#if NS_BLOCKS_AVAILABLE +typedef void (^ASIBasicBlock)(void); +typedef void (^ASIHeadersBlock)(NSDictionary *responseHeaders); +typedef void (^ASISizeBlock)(long long size); +typedef void (^ASIProgressBlock)(unsigned long long size, unsigned long long total); +typedef void (^ASIDataBlock)(NSData *data); +#endif + +@interface ASIHTTPRequest : NSOperation { + + // The url for this operation, should include GET params in the query string where appropriate + NSURL *url; + + // Will always contain the original url used for making the request (the value of url can change when a request is redirected) + NSURL *originalURL; + + // Temporarily stores the url we are about to redirect to. Will be nil again when we do redirect + NSURL *redirectURL; + + // The delegate, you need to manage setting and talking to your delegate in your subclasses + id delegate; + + // Another delegate that is also notified of request status changes and progress updates + // Generally, you won't use this directly, but ASINetworkQueue sets itself as the queue so it can proxy updates to its own delegates + // NOTE: WILL BE RETAINED BY THE REQUEST + id queue; + + // HTTP method to use (GET / POST / PUT / DELETE / HEAD). Defaults to GET + NSString *requestMethod; + + // Request body - only used when the whole body is stored in memory (shouldStreamPostDataFromDisk is false) + NSMutableData *postBody; + + // gzipped request body used when shouldCompressRequestBody is YES + NSData *compressedPostBody; + + // When true, post body will be streamed from a file on disk, rather than loaded into memory at once (useful for large uploads) + // Automatically set to true in ASIFormDataRequests when using setFile:forKey: + BOOL shouldStreamPostDataFromDisk; + + // Path to file used to store post body (when shouldStreamPostDataFromDisk is true) + // You can set this yourself - useful if you want to PUT a file from local disk + NSString *postBodyFilePath; + + // Path to a temporary file used to store a deflated post body (when shouldCompressPostBody is YES) + NSString *compressedPostBodyFilePath; + + // Set to true when ASIHTTPRequest automatically created a temporary file containing the request body (when true, the file at postBodyFilePath will be deleted at the end of the request) + BOOL didCreateTemporaryPostDataFile; + + // Used when writing to the post body when shouldStreamPostDataFromDisk is true (via appendPostData: or appendPostDataFromFile:) + NSOutputStream *postBodyWriteStream; + + // Used for reading from the post body when sending the request + NSInputStream *postBodyReadStream; + + // Dictionary for custom HTTP request headers + NSMutableDictionary *requestHeaders; + + // Set to YES when the request header dictionary has been populated, used to prevent this happening more than once + BOOL haveBuiltRequestHeaders; + + // Will be populated with HTTP response headers from the server + NSDictionary *responseHeaders; + + // Can be used to manually insert cookie headers to a request, but it's more likely that sessionCookies will do this for you + NSMutableArray *requestCookies; + + // Will be populated with cookies + NSArray *responseCookies; + + // If use useCookiePersistence is true, network requests will present valid cookies from previous requests + BOOL useCookiePersistence; + + // If useKeychainPersistence is true, network requests will attempt to read credentials from the keychain, and will save them in the keychain when they are successfully presented + BOOL useKeychainPersistence; + + // If useSessionPersistence is true, network requests will save credentials and reuse for the duration of the session (until clearSession is called) + BOOL useSessionPersistence; + + // If allowCompressedResponse is true, requests will inform the server they can accept compressed data, and will automatically decompress gzipped responses. Default is true. + BOOL allowCompressedResponse; + + // If shouldCompressRequestBody is true, the request body will be gzipped. Default is false. + // You will probably need to enable this feature on your webserver to make this work. Tested with apache only. + BOOL shouldCompressRequestBody; + + // When downloadDestinationPath is set, the result of this request will be downloaded to the file at this location + // If downloadDestinationPath is not set, download data will be stored in memory + NSString *downloadDestinationPath; + + // The location that files will be downloaded to. Once a download is complete, files will be decompressed (if necessary) and moved to downloadDestinationPath + NSString *temporaryFileDownloadPath; + + // If the response is gzipped and shouldWaitToInflateCompressedResponses is NO, a file will be created at this path containing the inflated response as it comes in + NSString *temporaryUncompressedDataDownloadPath; + + // Used for writing data to a file when downloadDestinationPath is set + NSOutputStream *fileDownloadOutputStream; + + NSOutputStream *inflatedFileDownloadOutputStream; + + // When the request fails or completes successfully, complete will be true + BOOL complete; + + // external "finished" indicator, subject of KVO notifications; updates after 'complete' + BOOL finished; + + // True if our 'cancel' selector has been called + BOOL cancelled; + + // If an error occurs, error will contain an NSError + // If error code is = ASIConnectionFailureErrorType (1, Connection failure occurred) - inspect [[error userInfo] objectForKey:NSUnderlyingErrorKey] for more information + NSError *error; + + // Username and password used for authentication + NSString *username; + NSString *password; + + // Domain used for NTLM authentication + NSString *domain; + + // Username and password used for proxy authentication + NSString *proxyUsername; + NSString *proxyPassword; + + // Domain used for NTLM proxy authentication + NSString *proxyDomain; + + // Delegate for displaying upload progress (usually an NSProgressIndicator, but you can supply a different object and handle this yourself) + id uploadProgressDelegate; + + // Delegate for displaying download progress (usually an NSProgressIndicator, but you can supply a different object and handle this yourself) + id downloadProgressDelegate; + + // Whether we've seen the headers of the response yet + BOOL haveExaminedHeaders; + + // Data we receive will be stored here. Data may be compressed unless allowCompressedResponse is false - you should use [request responseData] instead in most cases + NSMutableData *rawResponseData; + + // Used for sending and receiving data + CFHTTPMessageRef request; + NSInputStream *readStream; + + // Used for authentication + CFHTTPAuthenticationRef requestAuthentication; + NSDictionary *requestCredentials; + + // Used during NTLM authentication + int authenticationRetryCount; + + // Authentication scheme (Basic, Digest, NTLM) + NSString *authenticationScheme; + + // Realm for authentication when credentials are required + NSString *authenticationRealm; + + // When YES, ASIHTTPRequest will present a dialog allowing users to enter credentials when no-matching credentials were found for a server that requires authentication + // The dialog will not be shown if your delegate responds to authenticationNeededForRequest: + // Default is NO. + BOOL shouldPresentAuthenticationDialog; + + // When YES, ASIHTTPRequest will present a dialog allowing users to enter credentials when no-matching credentials were found for a proxy server that requires authentication + // The dialog will not be shown if your delegate responds to proxyAuthenticationNeededForRequest: + // Default is YES (basically, because most people won't want the hassle of adding support for authenticating proxies to their apps) + BOOL shouldPresentProxyAuthenticationDialog; + + // Used for proxy authentication + CFHTTPAuthenticationRef proxyAuthentication; + NSDictionary *proxyCredentials; + + // Used during authentication with an NTLM proxy + int proxyAuthenticationRetryCount; + + // Authentication scheme for the proxy (Basic, Digest, NTLM) + NSString *proxyAuthenticationScheme; + + // Realm for proxy authentication when credentials are required + NSString *proxyAuthenticationRealm; + + // HTTP status code, eg: 200 = OK, 404 = Not found etc + int responseStatusCode; + + // Description of the HTTP status code + NSString *responseStatusMessage; + + // Size of the response + unsigned long long contentLength; + + // Size of the partially downloaded content + unsigned long long partialDownloadSize; + + // Size of the POST payload + unsigned long long postLength; + + // The total amount of downloaded data + unsigned long long totalBytesRead; + + // The total amount of uploaded data + unsigned long long totalBytesSent; + + // Last amount of data read (used for incrementing progress) + unsigned long long lastBytesRead; + + // Last amount of data sent (used for incrementing progress) + unsigned long long lastBytesSent; + + // This lock prevents the operation from being cancelled at an inopportune moment + NSRecursiveLock *cancelledLock; + + // Called on the delegate (if implemented) when the request starts. Default is requestStarted: + SEL didStartSelector; + + // Called on the delegate (if implemented) when the request receives response headers. Default is request:didReceiveResponseHeaders: + SEL didReceiveResponseHeadersSelector; + + // Called on the delegate (if implemented) when the request receives a Location header and shouldRedirect is YES + // The delegate can then change the url if needed, and can restart the request by calling [request resume], or simply cancel it + SEL willRedirectSelector; + + // Called on the delegate (if implemented) when the request completes successfully. Default is requestFinished: + SEL didFinishSelector; + + // Called on the delegate (if implemented) when the request fails. Default is requestFailed: + SEL didFailSelector; + + // Called on the delegate (if implemented) when the request receives data. Default is request:didReceiveData: + // If you set this and implement the method in your delegate, you must handle the data yourself - ASIHTTPRequest will not populate responseData or write the data to downloadDestinationPath + SEL didReceiveDataSelector; + + // Used for recording when something last happened during the request, we will compare this value with the current date to time out requests when appropriate + NSDate *lastActivityTime; + + // Number of seconds to wait before timing out - default is 10 + NSTimeInterval timeOutSeconds; + + // Will be YES when a HEAD request will handle the content-length before this request starts + BOOL shouldResetUploadProgress; + BOOL shouldResetDownloadProgress; + + // Used by HEAD requests when showAccurateProgress is YES to preset the content-length for this request + ASIHTTPRequest *mainRequest; + + // When NO, this request will only update the progress indicator when it completes + // When YES, this request will update the progress indicator according to how much data it has received so far + // The default for requests is YES + // Also see the comments in ASINetworkQueue.h + BOOL showAccurateProgress; + + // Used to ensure the progress indicator is only incremented once when showAccurateProgress = NO + BOOL updatedProgress; + + // Prevents the body of the post being built more than once (largely for subclasses) + BOOL haveBuiltPostBody; + + // Used internally, may reflect the size of the internal buffer used by CFNetwork + // POST / PUT operations with body sizes greater than uploadBufferSize will not timeout unless more than uploadBufferSize bytes have been sent + // Likely to be 32KB on iPhone 3.0, 128KB on Mac OS X Leopard and iPhone 2.2.x + unsigned long long uploadBufferSize; + + // Text encoding for responses that do not send a Content-Type with a charset value. Defaults to NSISOLatin1StringEncoding + NSStringEncoding defaultResponseEncoding; + + // The text encoding of the response, will be defaultResponseEncoding if the server didn't specify. Can't be set. + NSStringEncoding responseEncoding; + + // Tells ASIHTTPRequest not to delete partial downloads, and allows it to use an existing file to resume a download. Defaults to NO. + BOOL allowResumeForFileDownloads; + + // Custom user information associated with the request + NSDictionary *userInfo; + + // Use HTTP 1.0 rather than 1.1 (defaults to false) + BOOL useHTTPVersionOne; + + // When YES, requests will automatically redirect when they get a HTTP 30x header (defaults to YES) + BOOL shouldRedirect; + + // Used internally to tell the main loop we need to stop and retry with a new url + BOOL needsRedirect; + + // Incremented every time this request redirects. When it reaches 5, we give up + int redirectCount; + + // When NO, requests will not check the secure certificate is valid (use for self-signed certificates during development, DO NOT USE IN PRODUCTION) Default is YES + BOOL validatesSecureCertificate; + + // If not nil and the URL scheme is https, CFNetwork configured to supply a client certificate + SecIdentityRef clientCertificateIdentity; + NSArray *clientCertificates; + + // Details on the proxy to use - you could set these yourself, but it's probably best to let ASIHTTPRequest detect the system proxy settings + NSString *proxyHost; + int proxyPort; + + // ASIHTTPRequest will assume kCFProxyTypeHTTP if the proxy type could not be automatically determined + // Set to kCFProxyTypeSOCKS if you are manually configuring a SOCKS proxy + NSString *proxyType; + + // URL for a PAC (Proxy Auto Configuration) file. If you want to set this yourself, it's probably best if you use a local file + NSURL *PACurl; + + // See ASIAuthenticationState values above. 0 == default == No authentication needed yet + ASIAuthenticationState authenticationNeeded; + + // When YES, ASIHTTPRequests will present credentials from the session store for requests to the same server before being asked for them + // This avoids an extra round trip for requests after authentication has succeeded, which is much for efficient for authenticated requests with large bodies, or on slower connections + // Set to NO to only present credentials when explicitly asked for them + // This only affects credentials stored in the session cache when useSessionPersistence is YES. Credentials from the keychain are never presented unless the server asks for them + // Default is YES + BOOL shouldPresentCredentialsBeforeChallenge; + + // YES when the request hasn't finished yet. Will still be YES even if the request isn't doing anything (eg it's waiting for delegate authentication). READ-ONLY + BOOL inProgress; + + // Used internally to track whether the stream is scheduled on the run loop or not + // Bandwidth throttling can unschedule the stream to slow things down while a request is in progress + BOOL readStreamIsScheduled; + + // Set to allow a request to automatically retry itself on timeout + // Default is zero - timeout will stop the request + int numberOfTimesToRetryOnTimeout; + + // The number of times this request has retried (when numberOfTimesToRetryOnTimeout > 0) + int retryCount; + + // When YES, requests will keep the connection to the server alive for a while to allow subsequent requests to re-use it for a substantial speed-boost + // Persistent connections will not be used if the server explicitly closes the connection + // Default is YES + BOOL shouldAttemptPersistentConnection; + + // Number of seconds to keep an inactive persistent connection open on the client side + // Default is 60 + // If we get a keep-alive header, this is this value is replaced with how long the server told us to keep the connection around + // A future date is created from this and used for expiring the connection, this is stored in connectionInfo's expires value + NSTimeInterval persistentConnectionTimeoutSeconds; + + // Set to yes when an appropriate keep-alive header is found + BOOL connectionCanBeReused; + + // Stores information about the persistent connection that is currently in use. + // It may contain: + // * The id we set for a particular connection, incremented every time we want to specify that we need a new connection + // * The date that connection should expire + // * A host, port and scheme for the connection. These are used to determine whether that connection can be reused by a subsequent request (all must match the new request) + // * An id for the request that is currently using the connection. This is used for determining if a connection is available or not (we store a number rather than a reference to the request so we don't need to hang onto a request until the connection expires) + // * A reference to the stream that is currently using the connection. This is necessary because we need to keep the old stream open until we've opened a new one. + // The stream will be closed + released either when another request comes to use the connection, or when the timer fires to tell the connection to expire + NSMutableDictionary *connectionInfo; + + // When set to YES, 301 and 302 automatic redirects will use the original method and and body, according to the HTTP 1.1 standard + // Default is NO (to follow the behaviour of most browsers) + BOOL shouldUseRFC2616RedirectBehaviour; + + // Used internally to record when a request has finished downloading data + BOOL downloadComplete; + + // An ID that uniquely identifies this request - primarily used for debugging persistent connections + NSNumber *requestID; + + // Will be ASIHTTPRequestRunLoopMode for synchronous requests, NSDefaultRunLoopMode for all other requests + NSString *runLoopMode; + + // This timer checks up on the request every 0.25 seconds, and updates progress + NSTimer *statusTimer; + + + // The download cache that will be used for this request (use [ASIHTTPRequest setDefaultCache:cache] to configure a default cache + id downloadCache; + + // The cache policy that will be used for this request - See ASICacheDelegate.h for possible values + ASICachePolicy cachePolicy; + + // The cache storage policy that will be used for this request - See ASICacheDelegate.h for possible values + ASICacheStoragePolicy cacheStoragePolicy; + + // Will be true when the response was pulled from the cache rather than downloaded + BOOL didUseCachedResponse; + + // Set secondsToCache to use a custom time interval for expiring the response when it is stored in a cache + NSTimeInterval secondsToCache; + + #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 + BOOL shouldContinueWhenAppEntersBackground; + UIBackgroundTaskIdentifier backgroundTask; + #endif + + + // When downloading a gzipped response, the request will use this helper object to inflate the response + ASIDataDecompressor *dataDecompressor; + + // Controls how responses with a gzipped encoding are inflated (decompressed) + // When set to YES (This is the default): + // * gzipped responses for requests without a downloadDestinationPath will be inflated only when [request responseData] / [request responseString] is called + // * gzipped responses for requests with a downloadDestinationPath set will be inflated only when the request completes + // + // When set to NO + // All requests will inflate the response as it comes in + // * If the request has no downloadDestinationPath set, the raw (compressed) response is discarded and rawResponseData will contain the decompressed response + // * If the request has a downloadDestinationPath, the raw response will be stored in temporaryFileDownloadPath as normal, the inflated response will be stored in temporaryUncompressedDataDownloadPath + // Once the request completes successfully, the contents of temporaryUncompressedDataDownloadPath are moved into downloadDestinationPath + // + // Setting this to NO may be especially useful for users using ASIHTTPRequest in conjunction with a streaming parser, as it will allow partial gzipped responses to be inflated and passed on to the parser while the request is still running + BOOL shouldWaitToInflateCompressedResponses; + + // Will be YES if this is a request created behind the scenes to download a PAC file - these requests do not attempt to configure their own proxies + BOOL isPACFileRequest; + + // Used for downloading PAC files from http / https webservers + ASIHTTPRequest *PACFileRequest; + + // Used for asynchronously reading PAC files from file:// URLs + NSInputStream *PACFileReadStream; + + // Used for storing PAC data from file URLs as it is downloaded + NSMutableData *PACFileData; + + // Set to YES in startSynchronous. Currently used by proxy detection to download PAC files synchronously when appropriate + BOOL isSynchronous; + + #if NS_BLOCKS_AVAILABLE + //block to execute when request starts + ASIBasicBlock startedBlock; + + //block to execute when headers are received + ASIHeadersBlock headersReceivedBlock; + + //block to execute when request completes successfully + ASIBasicBlock completionBlock; + + //block to execute when request fails + ASIBasicBlock failureBlock; + + //block for when bytes are received + ASIProgressBlock bytesReceivedBlock; + + //block for when bytes are sent + ASIProgressBlock bytesSentBlock; + + //block for when download size is incremented + ASISizeBlock downloadSizeIncrementedBlock; + + //block for when upload size is incremented + ASISizeBlock uploadSizeIncrementedBlock; + + //block for handling raw bytes received + ASIDataBlock dataReceivedBlock; + + //block for handling authentication + ASIBasicBlock authenticationNeededBlock; + + //block for handling proxy authentication + ASIBasicBlock proxyAuthenticationNeededBlock; + + //block for handling redirections, if you want to + ASIBasicBlock requestRedirectedBlock; + #endif +} + +#pragma mark init / dealloc + +// Should be an HTTP or HTTPS url, may include username and password if appropriate +- (id)initWithURL:(NSURL *)newURL; + +// Convenience constructor ++ (id)requestWithURL:(NSURL *)newURL; + ++ (id)requestWithURL:(NSURL *)newURL usingCache:(id )cache; ++ (id)requestWithURL:(NSURL *)newURL usingCache:(id )cache andCachePolicy:(ASICachePolicy)policy; + +#if NS_BLOCKS_AVAILABLE +- (void)setStartedBlock:(ASIBasicBlock)aStartedBlock; +- (void)setHeadersReceivedBlock:(ASIHeadersBlock)aReceivedBlock; +- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock; +- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock; +- (void)setBytesReceivedBlock:(ASIProgressBlock)aBytesReceivedBlock; +- (void)setBytesSentBlock:(ASIProgressBlock)aBytesSentBlock; +- (void)setDownloadSizeIncrementedBlock:(ASISizeBlock) aDownloadSizeIncrementedBlock; +- (void)setUploadSizeIncrementedBlock:(ASISizeBlock) anUploadSizeIncrementedBlock; +- (void)setDataReceivedBlock:(ASIDataBlock)aReceivedBlock; +- (void)setAuthenticationNeededBlock:(ASIBasicBlock)anAuthenticationBlock; +- (void)setProxyAuthenticationNeededBlock:(ASIBasicBlock)aProxyAuthenticationBlock; +- (void)setRequestRedirectedBlock:(ASIBasicBlock)aRedirectBlock; +#endif + +#pragma mark setup request + +// Add a custom header to the request +- (void)addRequestHeader:(NSString *)header value:(NSString *)value; + +// Called during buildRequestHeaders and after a redirect to create a cookie header from request cookies and the global store +- (void)applyCookieHeader; + +// Populate the request headers dictionary. Called before a request is started, or by a HEAD request that needs to borrow them +- (void)buildRequestHeaders; + +// Used to apply authorization header to a request before it is sent (when shouldPresentCredentialsBeforeChallenge is YES) +- (void)applyAuthorizationHeader; + + +// Create the post body +- (void)buildPostBody; + +// Called to add data to the post body. Will append to postBody when shouldStreamPostDataFromDisk is false, or write to postBodyWriteStream when true +- (void)appendPostData:(NSData *)data; +- (void)appendPostDataFromFile:(NSString *)file; + +#pragma mark get information about this request + +// Returns the contents of the result as an NSString (not appropriate for binary data - used responseData instead) +- (NSString *)responseString; + +// Response data, automatically uncompressed where appropriate +- (NSData *)responseData; + +// Returns true if the response was gzip compressed +- (BOOL)isResponseCompressed; + +#pragma mark running a request + + +// Run a request synchronously, and return control when the request completes or fails +- (void)startSynchronous; + +// Run request in the background +- (void)startAsynchronous; + +// Clears all delegates and blocks, then cancels the request +- (void)clearDelegatesAndCancel; + +#pragma mark HEAD request + +// Used by ASINetworkQueue to create a HEAD request appropriate for this request with the same headers (though you can use it yourself) +- (ASIHTTPRequest *)HEADRequest; + +#pragma mark upload/download progress + +// Called approximately every 0.25 seconds to update the progress delegates +- (void)updateProgressIndicators; + +// Updates upload progress (notifies the queue and/or uploadProgressDelegate of this request) +- (void)updateUploadProgress; + +// Updates download progress (notifies the queue and/or uploadProgressDelegate of this request) +- (void)updateDownloadProgress; + +// Called when authorisation is needed, as we only find out we don't have permission to something when the upload is complete +- (void)removeUploadProgressSoFar; + +// Called when we get a content-length header and shouldResetDownloadProgress is true +- (void)incrementDownloadSizeBy:(long long)length; + +// Called when a request starts and shouldResetUploadProgress is true +// Also called (with a negative length) to remove the size of the underlying buffer used for uploading +- (void)incrementUploadSizeBy:(long long)length; + +// Helper method for interacting with progress indicators to abstract the details of different APIS (NSProgressIndicator and UIProgressView) ++ (void)updateProgressIndicator:(id *)indicator withProgress:(unsigned long long)progress ofTotal:(unsigned long long)total; + +// Helper method used for performing invocations on the main thread (used for progress) ++ (void)performSelector:(SEL)selector onTarget:(id *)target withObject:(id)object amount:(void *)amount callerToRetain:(id)caller; + +#pragma mark talking to delegates + +// Called when a request starts, lets the delegate know via didStartSelector +- (void)requestStarted; + +// Called when a request receives response headers, lets the delegate know via didReceiveResponseHeadersSelector +- (void)requestReceivedResponseHeaders:(NSDictionary *)newHeaders; + +// Called when a request completes successfully, lets the delegate know via didFinishSelector +- (void)requestFinished; + +// Called when a request fails, and lets the delegate know via didFailSelector +- (void)failWithError:(NSError *)theError; + +// Called to retry our request when our persistent connection is closed +// Returns YES if we haven't already retried, and connection will be restarted +// Otherwise, returns NO, and nothing will happen +- (BOOL)retryUsingNewConnection; + +// Can be called by delegates from inside their willRedirectSelector implementations to restart the request with a new url +- (void)redirectToURL:(NSURL *)newURL; + +#pragma mark parsing HTTP response headers + +// Reads the response headers to find the content length, encoding, cookies for the session +// Also initiates request redirection when shouldRedirect is true +// And works out if HTTP auth is required +- (void)readResponseHeaders; + +// Attempts to set the correct encoding by looking at the Content-Type header, if this is one +- (void)parseStringEncodingFromHeaders; + ++ (void)parseMimeType:(NSString **)mimeType andResponseEncoding:(NSStringEncoding *)stringEncoding fromContentType:(NSString *)contentType; + +#pragma mark http authentication stuff + +// Apply credentials to this request +- (BOOL)applyCredentials:(NSDictionary *)newCredentials; +- (BOOL)applyProxyCredentials:(NSDictionary *)newCredentials; + +// Attempt to obtain credentials for this request from the URL, username and password or keychain +- (NSMutableDictionary *)findCredentials; +- (NSMutableDictionary *)findProxyCredentials; + +// Unlock (unpause) the request thread so it can resume the request +// Should be called by delegates when they have populated the authentication information after an authentication challenge +- (void)retryUsingSuppliedCredentials; + +// Should be called by delegates when they wish to cancel authentication and stop +- (void)cancelAuthentication; + +// Apply authentication information and resume the request after an authentication challenge +- (void)attemptToApplyCredentialsAndResume; +- (void)attemptToApplyProxyCredentialsAndResume; + +// Attempt to show the built-in authentication dialog, returns YES if credentials were supplied, NO if user cancelled dialog / dialog is disabled / running on main thread +// Currently only used on iPhone OS +- (BOOL)showProxyAuthenticationDialog; +- (BOOL)showAuthenticationDialog; + +// Construct a basic authentication header from the username and password supplied, and add it to the request headers +// Used when shouldPresentCredentialsBeforeChallenge is YES +- (void)addBasicAuthenticationHeaderWithUsername:(NSString *)theUsername andPassword:(NSString *)thePassword; + +#pragma mark stream status handlers + +// CFnetwork event handlers +- (void)handleNetworkEvent:(CFStreamEventType)type; +- (void)handleBytesAvailable; +- (void)handleStreamComplete; +- (void)handleStreamError; + +#pragma mark cleanup + +// Cleans up and lets the queue know this operation is finished. +// Appears in this header for subclassing only, do not call this method from outside your request! +- (void)markAsFinished; + +// Cleans up temporary files. There's normally no reason to call these yourself, they are called automatically when a request completes or fails + +// Clean up the temporary file used to store the downloaded data when it comes in (if downloadDestinationPath is set) +- (BOOL)removeTemporaryDownloadFile; + +// Clean up the temporary file used to store data that is inflated (decompressed) as it comes in +- (BOOL)removeTemporaryUncompressedDownloadFile; + +// Clean up the temporary file used to store the request body (when shouldStreamPostDataFromDisk is YES) +- (BOOL)removeTemporaryUploadFile; + +// Clean up the temporary file used to store a deflated (compressed) request body when shouldStreamPostDataFromDisk is YES +- (BOOL)removeTemporaryCompressedUploadFile; + +// Remove a file on disk, returning NO and populating the passed error pointer if it fails ++ (BOOL)removeFileAtPath:(NSString *)path error:(NSError **)err; + +#pragma mark persistent connections + +// Get the ID of the connection this request used (only really useful in tests and debugging) +- (NSNumber *)connectionID; + +// Called automatically when a request is started to clean up any persistent connections that have expired ++ (void)expirePersistentConnections; + +#pragma mark default time out + ++ (NSTimeInterval)defaultTimeOutSeconds; ++ (void)setDefaultTimeOutSeconds:(NSTimeInterval)newTimeOutSeconds; + +#pragma mark client certificate + +- (void)setClientCertificateIdentity:(SecIdentityRef)anIdentity; + +#pragma mark session credentials + ++ (NSMutableArray *)sessionProxyCredentialsStore; ++ (NSMutableArray *)sessionCredentialsStore; + ++ (void)storeProxyAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials; ++ (void)storeAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials; + ++ (void)removeProxyAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials; ++ (void)removeAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials; + +- (NSDictionary *)findSessionProxyAuthenticationCredentials; +- (NSDictionary *)findSessionAuthenticationCredentials; + +#pragma mark keychain storage + +// Save credentials for this request to the keychain +- (void)saveCredentialsToKeychain:(NSDictionary *)newCredentials; + +// Save credentials to the keychain ++ (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm; ++ (void)saveCredentials:(NSURLCredential *)credentials forProxy:(NSString *)host port:(int)port realm:(NSString *)realm; + +// Return credentials from the keychain ++ (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm; ++ (NSURLCredential *)savedCredentialsForProxy:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm; + +// Remove credentials from the keychain ++ (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm; ++ (void)removeCredentialsForProxy:(NSString *)host port:(int)port realm:(NSString *)realm; + +// We keep track of any cookies we accept, so that we can remove them from the persistent store later ++ (void)setSessionCookies:(NSMutableArray *)newSessionCookies; ++ (NSMutableArray *)sessionCookies; + +// Adds a cookie to our list of cookies we've accepted, checking first for an old version of the same cookie and removing that ++ (void)addSessionCookie:(NSHTTPCookie *)newCookie; + +// Dump all session data (authentication and cookies) ++ (void)clearSession; + +#pragma mark get user agent + +// Will be used as a user agent if requests do not specify a custom user agent +// Is only used when you have specified a Bundle Display Name (CFDisplayBundleName) or Bundle Name (CFBundleName) in your plist ++ (NSString *)defaultUserAgentString; + +#pragma mark mime-type detection + +// Return the mime type for a file ++ (NSString *)mimeTypeForFileAtPath:(NSString *)path; + +#pragma mark bandwidth measurement / throttling + +// The maximum number of bytes ALL requests can send / receive in a second +// This is a rough figure. The actual amount used will be slightly more, this does not include HTTP headers ++ (unsigned long)maxBandwidthPerSecond; ++ (void)setMaxBandwidthPerSecond:(unsigned long)bytes; + +// Get a rough average (for the last 5 seconds) of how much bandwidth is being used, in bytes ++ (unsigned long)averageBandwidthUsedPerSecond; + +- (void)performThrottling; + +// Will return YES is bandwidth throttling is currently in use ++ (BOOL)isBandwidthThrottled; + +// Used internally to record bandwidth use, and by ASIInputStreams when uploading. It's probably best if you don't mess with this. ++ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes; + +// On iPhone, ASIHTTPRequest can automatically turn throttling on and off as the connection type changes between WWAN and WiFi + +#if TARGET_OS_IPHONE +// Set to YES to automatically turn on throttling when WWAN is connected, and automatically turn it off when it isn't ++ (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle; + +// Turns on throttling automatically when WWAN is connected using a custom limit, and turns it off automatically when it isn't ++ (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit; + +#pragma mark reachability + +// Returns YES when an iPhone OS device is connected via WWAN, false when connected via WIFI or not connected ++ (BOOL)isNetworkReachableViaWWAN; + +#endif + +#pragma mark queue + +// Returns the shared queue ++ (NSOperationQueue *)sharedQueue; + +#pragma mark cache + ++ (void)setDefaultCache:(id )cache; ++ (id )defaultCache; + +// Returns the maximum amount of data we can read as part of the current measurement period, and sleeps this thread if our allowance is used up ++ (unsigned long)maxUploadReadLength; + +#pragma mark network activity + ++ (BOOL)isNetworkInUse; + ++ (void)setShouldUpdateNetworkActivityIndicator:(BOOL)shouldUpdate; + +// Shows the network activity spinner thing on iOS. You may wish to override this to do something else in Mac projects ++ (void)showNetworkActivityIndicator; + +// Hides the network activity spinner thing on iOS ++ (void)hideNetworkActivityIndicator; + +#pragma mark miscellany + +// Used for generating Authorization header when using basic authentication when shouldPresentCredentialsBeforeChallenge is true +// And also by ASIS3Request ++ (NSString *)base64forData:(NSData *)theData; + +// Returns a date from a string in RFC1123 format ++ (NSDate *)dateFromRFC1123String:(NSString *)string; + + +// Used for detecting multitasking support at runtime (for backgrounding requests) +#if TARGET_OS_IPHONE ++ (BOOL)isMultitaskingSupported; +#endif + +#pragma mark threading behaviour + +// In the default implementation, all requests run in a single background thread +// Advanced users only: Override this method in a subclass for a different threading behaviour +// Eg: return [NSThread mainThread] to run all requests in the main thread +// Alternatively, you can create a thread on demand, or manage a pool of threads +// Threads returned by this method will need to run the runloop in default mode (eg CFRunLoopRun()) +// Requests will stop the runloop when they complete +// If you have multiple requests sharing the thread you'll need to restart the runloop when this happens ++ (NSThread *)threadForRequest:(ASIHTTPRequest *)request; + + +#pragma mark === + +@property (retain) NSString *username; +@property (retain) NSString *password; +@property (retain) NSString *domain; + +@property (retain) NSString *proxyUsername; +@property (retain) NSString *proxyPassword; +@property (retain) NSString *proxyDomain; + +@property (retain) NSString *proxyHost; +@property (assign) int proxyPort; +@property (retain) NSString *proxyType; + +@property (retain,setter=setURL:, nonatomic) NSURL *url; +@property (retain) NSURL *originalURL; +@property (assign, nonatomic) id delegate; +@property (retain, nonatomic) id queue; +@property (assign, nonatomic) id uploadProgressDelegate; +@property (assign, nonatomic) id downloadProgressDelegate; +@property (assign) BOOL useKeychainPersistence; +@property (assign) BOOL useSessionPersistence; +@property (retain) NSString *downloadDestinationPath; +@property (retain) NSString *temporaryFileDownloadPath; +@property (retain) NSString *temporaryUncompressedDataDownloadPath; +@property (assign) SEL didStartSelector; +@property (assign) SEL didReceiveResponseHeadersSelector; +@property (assign) SEL willRedirectSelector; +@property (assign) SEL didFinishSelector; +@property (assign) SEL didFailSelector; +@property (assign) SEL didReceiveDataSelector; +@property (retain,readonly) NSString *authenticationRealm; +@property (retain,readonly) NSString *proxyAuthenticationRealm; +@property (retain) NSError *error; +@property (assign,readonly) BOOL complete; +@property (retain) NSDictionary *responseHeaders; +@property (retain) NSMutableDictionary *requestHeaders; +@property (retain) NSMutableArray *requestCookies; +@property (retain,readonly) NSArray *responseCookies; +@property (assign) BOOL useCookiePersistence; +@property (retain) NSDictionary *requestCredentials; +@property (retain) NSDictionary *proxyCredentials; +@property (assign,readonly) int responseStatusCode; +@property (retain,readonly) NSString *responseStatusMessage; +@property (retain) NSMutableData *rawResponseData; +@property (assign) NSTimeInterval timeOutSeconds; +@property (retain) NSString *requestMethod; +@property (retain) NSMutableData *postBody; +@property (assign) unsigned long long contentLength; +@property (assign) unsigned long long postLength; +@property (assign) BOOL shouldResetDownloadProgress; +@property (assign) BOOL shouldResetUploadProgress; +@property (assign) ASIHTTPRequest *mainRequest; +@property (assign) BOOL showAccurateProgress; +@property (assign) unsigned long long totalBytesRead; +@property (assign) unsigned long long totalBytesSent; +@property (assign) NSStringEncoding defaultResponseEncoding; +@property (assign) NSStringEncoding responseEncoding; +@property (assign) BOOL allowCompressedResponse; +@property (assign) BOOL allowResumeForFileDownloads; +@property (retain) NSDictionary *userInfo; +@property (retain) NSString *postBodyFilePath; +@property (assign) BOOL shouldStreamPostDataFromDisk; +@property (assign) BOOL didCreateTemporaryPostDataFile; +@property (assign) BOOL useHTTPVersionOne; +@property (assign, readonly) unsigned long long partialDownloadSize; +@property (assign) BOOL shouldRedirect; +@property (assign) BOOL validatesSecureCertificate; +@property (assign) BOOL shouldCompressRequestBody; +@property (retain) NSURL *PACurl; +@property (retain) NSString *authenticationScheme; +@property (retain) NSString *proxyAuthenticationScheme; +@property (assign) BOOL shouldPresentAuthenticationDialog; +@property (assign) BOOL shouldPresentProxyAuthenticationDialog; +@property (assign, readonly) ASIAuthenticationState authenticationNeeded; +@property (assign) BOOL shouldPresentCredentialsBeforeChallenge; +@property (assign, readonly) int authenticationRetryCount; +@property (assign, readonly) int proxyAuthenticationRetryCount; +@property (assign) BOOL haveBuiltRequestHeaders; +@property (assign, nonatomic) BOOL haveBuiltPostBody; +@property (assign, readonly) BOOL inProgress; +@property (assign) int numberOfTimesToRetryOnTimeout; +@property (assign, readonly) int retryCount; +@property (assign) BOOL shouldAttemptPersistentConnection; +@property (assign) NSTimeInterval persistentConnectionTimeoutSeconds; +@property (assign) BOOL shouldUseRFC2616RedirectBehaviour; +@property (assign, readonly) BOOL connectionCanBeReused; +@property (retain, readonly) NSNumber *requestID; +@property (assign) id downloadCache; +@property (assign) ASICachePolicy cachePolicy; +@property (assign) ASICacheStoragePolicy cacheStoragePolicy; +@property (assign, readonly) BOOL didUseCachedResponse; +@property (assign) NSTimeInterval secondsToCache; +@property (retain) NSArray *clientCertificates; +#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 +@property (assign) BOOL shouldContinueWhenAppEntersBackground; +#endif +@property (retain) ASIDataDecompressor *dataDecompressor; +@property (assign) BOOL shouldWaitToInflateCompressedResponses; + +@end diff --git a/Vendor/ASIHTTPRequest/ASIHTTPRequest.m b/Vendor/ASIHTTPRequest/ASIHTTPRequest.m new file mode 100644 index 0000000..deff3a8 --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIHTTPRequest.m @@ -0,0 +1,4779 @@ +// +// ASIHTTPRequest.m +// +// Created by Ben Copsey on 04/10/2007. +// Copyright 2007-2010 All-Seeing Interactive. All rights reserved. +// +// A guide to the main features is available at: +// http://allseeing-i.com/ASIHTTPRequest +// +// Portions are based on the ImageClient example from Apple: +// See: http://developer.apple.com/samplecode/ImageClient/listing37.html + +#import "ASIHTTPRequest.h" + +#if TARGET_OS_IPHONE +#import "Reachability.h" +#import "ASIAuthenticationDialog.h" +#import +#else +#import +#endif +#import "ASIInputStream.h" +#import "ASIDataDecompressor.h" +#import "ASIDataCompressor.h" + +// Automatically set on build +NSString *ASIHTTPRequestVersion = @"v1.8-56 2011-02-06"; + +NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; + +static NSString *ASIHTTPRequestRunLoopMode = @"ASIHTTPRequestRunLoopMode"; + +static const CFOptionFlags kNetworkEvents = kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred; + +// In memory caches of credentials, used on when useSessionPersistence is YES +static NSMutableArray *sessionCredentialsStore = nil; +static NSMutableArray *sessionProxyCredentialsStore = nil; + +// This lock mediates access to session credentials +static NSRecursiveLock *sessionCredentialsLock = nil; + +// We keep track of cookies we have received here so we can remove them from the sharedHTTPCookieStorage later +static NSMutableArray *sessionCookies = nil; + +// The number of times we will allow requests to redirect before we fail with a redirection error +const int RedirectionLimit = 5; + +// The default number of seconds to use for a timeout +static NSTimeInterval defaultTimeOutSeconds = 10; + +static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) { + [((ASIHTTPRequest*)clientCallBackInfo) handleNetworkEvent: type]; +} + +// This lock prevents the operation from being cancelled while it is trying to update the progress, and vice versa +static NSRecursiveLock *progressLock; + +static NSError *ASIRequestCancelledError; +static NSError *ASIRequestTimedOutError; +static NSError *ASIAuthenticationError; +static NSError *ASIUnableToCreateRequestError; +static NSError *ASITooMuchRedirectionError; + +static NSMutableArray *bandwidthUsageTracker = nil; +static unsigned long averageBandwidthUsedPerSecond = 0; + + +// These are used for queuing persistent connections on the same connection + +// Incremented every time we specify we want a new connection +static unsigned int nextConnectionNumberToCreate = 0; + +// An array of connectionInfo dictionaries. +// When attempting a persistent connection, we look here to try to find an existing connection to the same server that is currently not in use +static NSMutableArray *persistentConnectionsPool = nil; + +// Mediates access to the persistent connections pool +static NSRecursiveLock *connectionsLock = nil; + +// Each request gets a new id, we store this rather than a ref to the request itself in the connectionInfo dictionary. +// We do this so we don't have to keep the request around while we wait for the connection to expire +static unsigned int nextRequestID = 0; + +// Records how much bandwidth all requests combined have used in the last second +static unsigned long bandwidthUsedInLastSecond = 0; + +// A date one second in the future from the time it was created +static NSDate *bandwidthMeasurementDate = nil; + +// Since throttling variables are shared among all requests, we'll use a lock to mediate access +static NSLock *bandwidthThrottlingLock = nil; + +// the maximum number of bytes that can be transmitted in one second +static unsigned long maxBandwidthPerSecond = 0; + +// A default figure for throttling bandwidth on mobile devices +unsigned long const ASIWWANBandwidthThrottleAmount = 14800; + +#if TARGET_OS_IPHONE +// YES when bandwidth throttling is active +// This flag does not denote whether throttling is turned on - rather whether it is currently in use +// It will be set to NO when throttling was turned on with setShouldThrottleBandwidthForWWAN, but a WI-FI connection is active +static BOOL isBandwidthThrottled = NO; + +// When YES, bandwidth will be automatically throttled when using WWAN (3G/Edge/GPRS) +// Wifi will not be throttled +static BOOL shouldThrottleBandwithForWWANOnly = NO; +#endif + +// Mediates access to the session cookies so requests +static NSRecursiveLock *sessionCookiesLock = nil; + +// This lock ensures delegates only receive one notification that authentication is required at once +// When using ASIAuthenticationDialogs, it also ensures only one dialog is shown at once +// If a request can't acquire the lock immediately, it means a dialog is being shown or a delegate is handling the authentication challenge +// Once it gets the lock, it will try to look for existing credentials again rather than showing the dialog / notifying the delegate +// This is so it can make use of any credentials supplied for the other request, if they are appropriate +static NSRecursiveLock *delegateAuthenticationLock = nil; + +// When throttling bandwidth, Set to a date in future that we will allow all requests to wake up and reschedule their streams +static NSDate *throttleWakeUpTime = nil; + +static id defaultCache = nil; + + +// Used for tracking when requests are using the network +static unsigned int runningRequestCount = 0; + + +// You can use [ASIHTTPRequest setShouldUpdateNetworkActivityIndicator:NO] if you want to manage it yourself +// Alternatively, override showNetworkActivityIndicator / hideNetworkActivityIndicator +// By default this does nothing on Mac OS X, but again override the above methods for a different behaviour +static BOOL shouldUpdateNetworkActivityIndicator = YES; + + +//**Queue stuff**/ + +// The thread all requests will run on +// Hangs around forever, but will be blocked unless there are requests underway +static NSThread *networkThread = nil; + +static NSOperationQueue *sharedQueue = nil; + +// Private stuff +@interface ASIHTTPRequest () + +- (void)cancelLoad; + +- (void)destroyReadStream; +- (void)scheduleReadStream; +- (void)unscheduleReadStream; + +- (BOOL)willAskDelegateForCredentials; +- (BOOL)willAskDelegateForProxyCredentials; +- (void)askDelegateForProxyCredentials; +- (void)askDelegateForCredentials; +- (void)failAuthentication; + ++ (void)measureBandwidthUsage; ++ (void)recordBandwidthUsage; + +- (void)startRequest; +- (void)updateStatus:(NSTimer *)timer; +- (void)checkRequestStatus; +- (void)reportFailure; +- (void)reportFinished; +- (void)markAsFinished; +- (void)performRedirect; +- (BOOL)shouldTimeOut; + ++ (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease; ++ (void)hideNetworkActivityIndicatorAfterDelay; ++ (void)hideNetworkActivityIndicatorIfNeeeded; ++ (void)runRequests; + +// Handling Proxy autodetection and PAC file downloads +- (BOOL)configureProxies; +- (void)fetchPACFile; +- (void)finishedDownloadingPACFile:(ASIHTTPRequest *)theRequest; +- (void)runPACScript:(NSString *)script; +- (void)timeOutPACRead; + +- (void)useDataFromCache; + +// Called to update the size of a partial download when starting a request, or retrying after a timeout +- (void)updatePartialDownloadSize; + +#if TARGET_OS_IPHONE ++ (void)registerForNetworkReachabilityNotifications; ++ (void)unsubscribeFromNetworkReachabilityNotifications; +// Called when the status of the network changes ++ (void)reachabilityChanged:(NSNotification *)note; +#endif + +#if NS_BLOCKS_AVAILABLE +- (void)performBlockOnMainThread:(ASIBasicBlock)block; +- (void)releaseBlocksOnMainThread; ++ (void)releaseBlocks:(NSArray *)blocks; +- (void)callBlock:(ASIBasicBlock)block; +#endif + + + + + +@property (assign) BOOL complete; +@property (retain) NSArray *responseCookies; +@property (assign) int responseStatusCode; +@property (retain, nonatomic) NSDate *lastActivityTime; + +@property (assign) unsigned long long partialDownloadSize; +@property (assign, nonatomic) unsigned long long uploadBufferSize; +@property (retain, nonatomic) NSOutputStream *postBodyWriteStream; +@property (retain, nonatomic) NSInputStream *postBodyReadStream; +@property (assign, nonatomic) unsigned long long lastBytesRead; +@property (assign, nonatomic) unsigned long long lastBytesSent; +@property (retain) NSRecursiveLock *cancelledLock; +@property (retain, nonatomic) NSOutputStream *fileDownloadOutputStream; +@property (retain, nonatomic) NSOutputStream *inflatedFileDownloadOutputStream; +@property (assign) int authenticationRetryCount; +@property (assign) int proxyAuthenticationRetryCount; +@property (assign, nonatomic) BOOL updatedProgress; +@property (assign, nonatomic) BOOL needsRedirect; +@property (assign, nonatomic) int redirectCount; +@property (retain, nonatomic) NSData *compressedPostBody; +@property (retain, nonatomic) NSString *compressedPostBodyFilePath; +@property (retain) NSString *authenticationRealm; +@property (retain) NSString *proxyAuthenticationRealm; +@property (retain) NSString *responseStatusMessage; +@property (assign) BOOL inProgress; +@property (assign) int retryCount; +@property (assign) BOOL connectionCanBeReused; +@property (retain, nonatomic) NSMutableDictionary *connectionInfo; +@property (retain, nonatomic) NSInputStream *readStream; +@property (assign) ASIAuthenticationState authenticationNeeded; +@property (assign, nonatomic) BOOL readStreamIsScheduled; +@property (assign, nonatomic) BOOL downloadComplete; +@property (retain) NSNumber *requestID; +@property (assign, nonatomic) NSString *runLoopMode; +@property (retain, nonatomic) NSTimer *statusTimer; +@property (assign) BOOL didUseCachedResponse; +@property (retain, nonatomic) NSURL *redirectURL; + +@property (assign, nonatomic) BOOL isPACFileRequest; +@property (retain, nonatomic) ASIHTTPRequest *PACFileRequest; +@property (retain, nonatomic) NSInputStream *PACFileReadStream; +@property (retain, nonatomic) NSMutableData *PACFileData; + +@property (assign, nonatomic, setter=setSynchronous:) BOOL isSynchronous; +@end + + +@implementation ASIHTTPRequest + +#pragma mark init / dealloc + ++ (void)initialize +{ + if (self == [ASIHTTPRequest class]) { + persistentConnectionsPool = [[NSMutableArray alloc] init]; + connectionsLock = [[NSRecursiveLock alloc] init]; + progressLock = [[NSRecursiveLock alloc] init]; + bandwidthThrottlingLock = [[NSLock alloc] init]; + sessionCookiesLock = [[NSRecursiveLock alloc] init]; + sessionCredentialsLock = [[NSRecursiveLock alloc] init]; + delegateAuthenticationLock = [[NSRecursiveLock alloc] init]; + bandwidthUsageTracker = [[NSMutableArray alloc] initWithCapacity:5]; + ASIRequestTimedOutError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request timed out",NSLocalizedDescriptionKey,nil]]; + ASIAuthenticationError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed",NSLocalizedDescriptionKey,nil]]; + ASIRequestCancelledError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled",NSLocalizedDescriptionKey,nil]]; + ASIUnableToCreateRequestError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIUnableToCreateRequestErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create request (bad url?)",NSLocalizedDescriptionKey,nil]]; + ASITooMuchRedirectionError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASITooMuchRedirectionErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request failed because it redirected too many times",NSLocalizedDescriptionKey,nil]]; + sharedQueue = [[NSOperationQueue alloc] init]; + [sharedQueue setMaxConcurrentOperationCount:4]; + + } +} + + +- (id)initWithURL:(NSURL *)newURL +{ + self = [self init]; + [self setRequestMethod:@"GET"]; + + [self setRunLoopMode:NSDefaultRunLoopMode]; + [self setShouldAttemptPersistentConnection:YES]; + [self setPersistentConnectionTimeoutSeconds:60.0]; + [self setShouldPresentCredentialsBeforeChallenge:YES]; + [self setShouldRedirect:YES]; + [self setShowAccurateProgress:YES]; + [self setShouldResetDownloadProgress:YES]; + [self setShouldResetUploadProgress:YES]; + [self setAllowCompressedResponse:YES]; + [self setShouldWaitToInflateCompressedResponses:YES]; + [self setDefaultResponseEncoding:NSISOLatin1StringEncoding]; + [self setShouldPresentProxyAuthenticationDialog:YES]; + + [self setTimeOutSeconds:[ASIHTTPRequest defaultTimeOutSeconds]]; + [self setUseSessionPersistence:YES]; + [self setUseCookiePersistence:YES]; + [self setValidatesSecureCertificate:YES]; + [self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]]; + [self setDidStartSelector:@selector(requestStarted:)]; + [self setDidReceiveResponseHeadersSelector:@selector(request:didReceiveResponseHeaders:)]; + [self setWillRedirectSelector:@selector(request:willRedirectToURL:)]; + [self setDidFinishSelector:@selector(requestFinished:)]; + [self setDidFailSelector:@selector(requestFailed:)]; + [self setDidReceiveDataSelector:@selector(request:didReceiveData:)]; + [self setURL:newURL]; + [self setCancelledLock:[[[NSRecursiveLock alloc] init] autorelease]]; + [self setDownloadCache:[[self class] defaultCache]]; + return self; +} + ++ (id)requestWithURL:(NSURL *)newURL +{ + return [[[self alloc] initWithURL:newURL] autorelease]; +} + ++ (id)requestWithURL:(NSURL *)newURL usingCache:(id )cache +{ + return [self requestWithURL:newURL usingCache:cache andCachePolicy:ASIUseDefaultCachePolicy]; +} + ++ (id)requestWithURL:(NSURL *)newURL usingCache:(id )cache andCachePolicy:(ASICachePolicy)policy +{ + ASIHTTPRequest *request = [[[self alloc] initWithURL:newURL] autorelease]; + [request setDownloadCache:cache]; + [request setCachePolicy:policy]; + return request; +} + +- (void)dealloc +{ + [self setAuthenticationNeeded:ASINoAuthenticationNeededYet]; + if (requestAuthentication) { + CFRelease(requestAuthentication); + } + if (proxyAuthentication) { + CFRelease(proxyAuthentication); + } + if (request) { + CFRelease(request); + } + if (clientCertificateIdentity) { + CFRelease(clientCertificateIdentity); + } + [self cancelLoad]; + [redirectURL release]; + [statusTimer invalidate]; + [statusTimer release]; + [queue release]; + [userInfo release]; + [postBody release]; + [compressedPostBody release]; + [error release]; + [requestHeaders release]; + [requestCookies release]; + [downloadDestinationPath release]; + [temporaryFileDownloadPath release]; + [temporaryUncompressedDataDownloadPath release]; + [fileDownloadOutputStream release]; + [inflatedFileDownloadOutputStream release]; + [username release]; + [password release]; + [domain release]; + [authenticationRealm release]; + [authenticationScheme release]; + [requestCredentials release]; + [proxyHost release]; + [proxyType release]; + [proxyUsername release]; + [proxyPassword release]; + [proxyDomain release]; + [proxyAuthenticationRealm release]; + [proxyAuthenticationScheme release]; + [proxyCredentials release]; + [url release]; + [originalURL release]; + [lastActivityTime release]; + [responseCookies release]; + [rawResponseData release]; + [responseHeaders release]; + [requestMethod release]; + [cancelledLock release]; + [postBodyFilePath release]; + [compressedPostBodyFilePath release]; + [postBodyWriteStream release]; + [postBodyReadStream release]; + [PACurl release]; + [clientCertificates release]; + [responseStatusMessage release]; + [connectionInfo release]; + [requestID release]; + [dataDecompressor release]; + + #if NS_BLOCKS_AVAILABLE + [self releaseBlocksOnMainThread]; + #endif + + [super dealloc]; +} + +#if NS_BLOCKS_AVAILABLE +- (void)releaseBlocksOnMainThread +{ + NSMutableArray *blocks = [NSMutableArray array]; + if (completionBlock) { + [blocks addObject:completionBlock]; + [completionBlock release]; + completionBlock = nil; + } + if (failureBlock) { + [blocks addObject:failureBlock]; + [failureBlock release]; + failureBlock = nil; + } + if (startedBlock) { + [blocks addObject:startedBlock]; + [startedBlock release]; + startedBlock = nil; + } + if (headersReceivedBlock) { + [blocks addObject:headersReceivedBlock]; + [headersReceivedBlock release]; + headersReceivedBlock = nil; + } + if (bytesReceivedBlock) { + [blocks addObject:bytesReceivedBlock]; + [bytesReceivedBlock release]; + bytesReceivedBlock = nil; + } + if (bytesSentBlock) { + [blocks addObject:bytesSentBlock]; + [bytesSentBlock release]; + bytesSentBlock = nil; + } + if (downloadSizeIncrementedBlock) { + [blocks addObject:downloadSizeIncrementedBlock]; + [downloadSizeIncrementedBlock release]; + downloadSizeIncrementedBlock = nil; + } + if (uploadSizeIncrementedBlock) { + [blocks addObject:uploadSizeIncrementedBlock]; + [uploadSizeIncrementedBlock release]; + uploadSizeIncrementedBlock = nil; + } + if (dataReceivedBlock) { + [blocks addObject:dataReceivedBlock]; + [dataReceivedBlock release]; + dataReceivedBlock = nil; + } + if (proxyAuthenticationNeededBlock) { + [blocks addObject:proxyAuthenticationNeededBlock]; + [proxyAuthenticationNeededBlock release]; + proxyAuthenticationNeededBlock = nil; + } + if (authenticationNeededBlock) { + [blocks addObject:authenticationNeededBlock]; + [authenticationNeededBlock release]; + authenticationNeededBlock = nil; + } + [[self class] performSelectorOnMainThread:@selector(releaseBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]]; +} +// Always called on main thread ++ (void)releaseBlocks:(NSArray *)blocks +{ + // Blocks will be released when this method exits +} +#endif + + +#pragma mark setup request + +- (void)addRequestHeader:(NSString *)header value:(NSString *)value +{ + if (!requestHeaders) { + [self setRequestHeaders:[NSMutableDictionary dictionaryWithCapacity:1]]; + } + [requestHeaders setObject:value forKey:header]; +} + +// This function will be called either just before a request starts, or when postLength is needed, whichever comes first +// postLength must be set by the time this function is complete +- (void)buildPostBody +{ + + if ([self haveBuiltPostBody]) { + return; + } + + // Are we submitting the request body from a file on disk + if ([self postBodyFilePath]) { + + // If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write stream + if ([self postBodyWriteStream]) { + [[self postBodyWriteStream] close]; + [self setPostBodyWriteStream:nil]; + } + + + NSString *path; + if ([self shouldCompressRequestBody]) { + if (![self compressedPostBodyFilePath]) { + [self setCompressedPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; + + NSError *err = nil; + if (![ASIDataCompressor compressDataFromFile:[self postBodyFilePath] toFile:[self compressedPostBodyFilePath] error:&err]) { + [self failWithError:err]; + return; + } + } + path = [self compressedPostBodyFilePath]; + } else { + path = [self postBodyFilePath]; + } + NSError *err = nil; + [self setPostLength:[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:path error:&err] fileSize]]; + if (err) { + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '%@'",path],NSLocalizedDescriptionKey,error,NSUnderlyingErrorKey,nil]]]; + return; + } + + // Otherwise, we have an in-memory request body + } else { + if ([self shouldCompressRequestBody]) { + NSError *err = nil; + NSData *compressedBody = [ASIDataCompressor compressData:[self postBody] error:&err]; + if (err) { + [self failWithError:err]; + return; + } + [self setCompressedPostBody:compressedBody]; + [self setPostLength:[[self compressedPostBody] length]]; + } else { + [self setPostLength:[[self postBody] length]]; + } + } + + if ([self postLength] > 0) { + if ([requestMethod isEqualToString:@"GET"] || [requestMethod isEqualToString:@"DELETE"] || [requestMethod isEqualToString:@"HEAD"]) { + [self setRequestMethod:@"POST"]; + } + [self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",[self postLength]]]; + } + [self setHaveBuiltPostBody:YES]; + +} + +// Sets up storage for the post body +- (void)setupPostBody +{ + if ([self shouldStreamPostDataFromDisk]) { + if (![self postBodyFilePath]) { + [self setPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; + [self setDidCreateTemporaryPostDataFile:YES]; + } + if (![self postBodyWriteStream]) { + [self setPostBodyWriteStream:[[[NSOutputStream alloc] initToFileAtPath:[self postBodyFilePath] append:NO] autorelease]]; + [[self postBodyWriteStream] open]; + } + } else { + if (![self postBody]) { + [self setPostBody:[[[NSMutableData alloc] init] autorelease]]; + } + } +} + +- (void)appendPostData:(NSData *)data +{ + [self setupPostBody]; + if ([data length] == 0) { + return; + } + if ([self shouldStreamPostDataFromDisk]) { + [[self postBodyWriteStream] write:[data bytes] maxLength:[data length]]; + } else { + [[self postBody] appendData:data]; + } +} + +- (void)appendPostDataFromFile:(NSString *)file +{ + [self setupPostBody]; + NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:file] autorelease]; + [stream open]; + NSUInteger bytesRead; + while ([stream hasBytesAvailable]) { + + unsigned char buffer[1024*256]; + bytesRead = [stream read:buffer maxLength:sizeof(buffer)]; + if (bytesRead == 0) { + break; + } + if ([self shouldStreamPostDataFromDisk]) { + [[self postBodyWriteStream] write:buffer maxLength:bytesRead]; + } else { + [[self postBody] appendData:[NSData dataWithBytes:buffer length:bytesRead]]; + } + } + [stream close]; +} + +- (NSURL *)url +{ + [[self cancelledLock] lock]; + NSURL *u = url; + [[self cancelledLock] unlock]; + return u; +} + + +- (void)setURL:(NSURL *)newURL +{ + [[self cancelledLock] lock]; + if ([newURL isEqual:[self url]]) { + [[self cancelledLock] unlock]; + return; + } + [url release]; + url = [newURL retain]; + if (requestAuthentication) { + CFRelease(requestAuthentication); + requestAuthentication = NULL; + } + if (proxyAuthentication) { + CFRelease(proxyAuthentication); + proxyAuthentication = NULL; + } + if (request) { + CFRelease(request); + request = NULL; + } + [self setRedirectURL:nil]; + [[self cancelledLock] unlock]; +} + +- (id)delegate +{ + [[self cancelledLock] lock]; + id d = delegate; + [[self cancelledLock] unlock]; + return d; +} + +- (void)setDelegate:(id)newDelegate +{ + [[self cancelledLock] lock]; + delegate = newDelegate; + [[self cancelledLock] unlock]; +} + +- (id)queue +{ + [[self cancelledLock] lock]; + id q = queue; + [[self cancelledLock] unlock]; + return q; +} + + +- (void)setQueue:(id)newQueue +{ + [[self cancelledLock] lock]; + if (newQueue != queue) { + [queue release]; + queue = [newQueue retain]; + } + [[self cancelledLock] unlock]; +} + +#pragma mark get information about this request + +// cancel the request - this must be run on the same thread as the request is running on +- (void)cancelOnRequestThread +{ + #if DEBUG_REQUEST_STATUS + NSLog(@"Request cancelled: %@",self); + #endif + + [[self cancelledLock] lock]; + + if ([self isCancelled] || [self complete]) { + [[self cancelledLock] unlock]; + return; + } + [self failWithError:ASIRequestCancelledError]; + [self setComplete:YES]; + [self cancelLoad]; + + CFRetain(self); + [self willChangeValueForKey:@"isCancelled"]; + cancelled = YES; + [self didChangeValueForKey:@"isCancelled"]; + + [[self cancelledLock] unlock]; + CFRelease(self); +} + +- (void)cancel +{ + [self performSelector:@selector(cancelOnRequestThread) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; +} + +- (void)clearDelegatesAndCancel +{ + [[self cancelledLock] lock]; + + // Clear delegates + [self setDelegate:nil]; + [self setQueue:nil]; + [self setDownloadProgressDelegate:nil]; + [self setUploadProgressDelegate:nil]; + + #if NS_BLOCKS_AVAILABLE + // Clear blocks + [self releaseBlocksOnMainThread]; + #endif + + [[self cancelledLock] unlock]; + [self cancel]; +} + + +- (BOOL)isCancelled +{ + BOOL result; + + [[self cancelledLock] lock]; + result = cancelled; + [[self cancelledLock] unlock]; + + return result; +} + +// Call this method to get the received data as an NSString. Don't use for binary data! +- (NSString *)responseString +{ + NSData *data = [self responseData]; + if (!data) { + return nil; + } + + return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease]; +} + +- (BOOL)isResponseCompressed +{ + NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"]; + return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound; +} + +- (NSData *)responseData +{ + if ([self isResponseCompressed] && [self shouldWaitToInflateCompressedResponses]) { + return [ASIDataDecompressor uncompressData:[self rawResponseData] error:NULL]; + } else { + return [self rawResponseData]; + } + return nil; +} + +#pragma mark running a request + +- (void)startSynchronous +{ +#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING + NSLog(@"Starting synchronous request %@",self); +#endif + [self setSynchronous:YES]; + [self setRunLoopMode:ASIHTTPRequestRunLoopMode]; + [self setInProgress:YES]; + + if (![self isCancelled] && ![self complete]) { + [self main]; + while (!complete) { + [[NSRunLoop currentRunLoop] runMode:[self runLoopMode] beforeDate:[NSDate distantFuture]]; + } + } + + [self setInProgress:NO]; +} + +- (void)start +{ + [self setInProgress:YES]; + [self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; +} + +- (void)startAsynchronous +{ +#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING + NSLog(@"Starting asynchronous request %@",self); +#endif + [sharedQueue addOperation:self]; +} + +#pragma mark concurrency + +- (BOOL)isConcurrent +{ + return YES; +} + +- (BOOL)isFinished +{ + return finished; +} + +- (BOOL)isExecuting { + return [self inProgress]; +} + +#pragma mark request logic + +// Create the request +- (void)main +{ + @try { + + [[self cancelledLock] lock]; + + #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 + if ([ASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) { + backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + // Synchronize the cleanup call on the main thread in case + // the task actually finishes at around the same time. + dispatch_async(dispatch_get_main_queue(), ^{ + if (backgroundTask != UIBackgroundTaskInvalid) + { + [[UIApplication sharedApplication] endBackgroundTask:backgroundTask]; + backgroundTask = UIBackgroundTaskInvalid; + [self cancel]; + } + }); + }]; + } + #endif + + + // A HEAD request generated by an ASINetworkQueue may have set the error already. If so, we should not proceed. + if ([self error]) { + [self setComplete:YES]; + [self markAsFinished]; + return; + } + + [self setComplete:NO]; + [self setDidUseCachedResponse:NO]; + + if (![self url]) { + [self failWithError:ASIUnableToCreateRequestError]; + return; + } + + // Must call before we create the request so that the request method can be set if needs be + if (![self mainRequest]) { + [self buildPostBody]; + } + + if (![[self requestMethod] isEqualToString:@"GET"]) { + [self setDownloadCache:nil]; + } + + + // If we're redirecting, we'll already have a CFHTTPMessageRef + if (request) { + CFRelease(request); + } + + // Create a new HTTP request. + request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self requestMethod], (CFURLRef)[self url], [self useHTTPVersionOne] ? kCFHTTPVersion1_0 : kCFHTTPVersion1_1); + if (!request) { + [self failWithError:ASIUnableToCreateRequestError]; + return; + } + + //If this is a HEAD request generated by an ASINetworkQueue, we need to let the main request generate its headers first so we can use them + if ([self mainRequest]) { + [[self mainRequest] buildRequestHeaders]; + } + + // Even if this is a HEAD request with a mainRequest, we still need to call to give subclasses a chance to add their own to HEAD requests (ASIS3Request does this) + [self buildRequestHeaders]; + + if ([self downloadCache]) { + + // If this request should use the default policy, set its policy to the download cache's default policy + if (![self cachePolicy]) { + [self setCachePolicy:[[self downloadCache] defaultCachePolicy]]; + } + + // If have have cached data that is valid for this request, use that and stop + if ([[self downloadCache] canUseCachedDataForRequest:self]) { + [self useDataFromCache]; + return; + } + + // If cached data is stale, or we have been told to ask the server if it has been modified anyway, we need to add headers for a conditional GET + if ([self cachePolicy] & (ASIAskServerIfModifiedWhenStaleCachePolicy|ASIAskServerIfModifiedCachePolicy)) { + + NSDictionary *cachedHeaders = [[self downloadCache] cachedResponseHeadersForURL:[self url]]; + if (cachedHeaders) { + NSString *etag = [cachedHeaders objectForKey:@"Etag"]; + if (etag) { + [[self requestHeaders] setObject:etag forKey:@"If-None-Match"]; + } + NSString *lastModified = [cachedHeaders objectForKey:@"Last-Modified"]; + if (lastModified) { + [[self requestHeaders] setObject:lastModified forKey:@"If-Modified-Since"]; + } + } + } + } + + [self applyAuthorizationHeader]; + + + NSString *header; + for (header in [self requestHeaders]) { + CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[[self requestHeaders] objectForKey:header]); + } + + // If we immediately have access to proxy settings, start the request + // Otherwise, we'll start downloading the proxy PAC file, and call startRequest once that process is complete + if ([self configureProxies]) { + [self startRequest]; + } + + } @catch (NSException *exception) { + NSError *underlyingError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[exception userInfo]]; + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[exception name],NSLocalizedDescriptionKey,[exception reason],NSLocalizedFailureReasonErrorKey,underlyingError,NSUnderlyingErrorKey,nil]]]; + + } @finally { + [[self cancelledLock] unlock]; + } +} + +- (void)applyAuthorizationHeader +{ + // Do we want to send credentials before we are asked for them? + if (![self shouldPresentCredentialsBeforeChallenge]) { + return; + } + + // First, see if we have any credentials we can use in the session store + NSDictionary *credentials = nil; + if ([self useSessionPersistence]) { + credentials = [self findSessionAuthenticationCredentials]; + } + + + // Are any credentials set on this request that might be used for basic authentication? + if ([self username] && [self password] && ![self domain]) { + + // If we know this request should use Basic auth, we'll add an Authorization header with basic credentials + if ([[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) { + [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]]; + } + } + + if (credentials && ![[self requestHeaders] objectForKey:@"Authorization"]) { + + // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them + // (credentials for Digest and NTLM will always be stored like this) + if ([credentials objectForKey:@"Authentication"]) { + + // If we've already talked to this server and have valid credentials, let's apply them to the request + if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) { + [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; + } + + // If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication + // When this happens, we'll need to create the Authorization header ourselves + } else { + NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"]; + [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]]; + } + } + if ([self useSessionPersistence]) { + credentials = [self findSessionProxyAuthenticationCredentials]; + if (credentials) { + if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) { + [[self class] removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; + } + } + } +} + +- (void)applyCookieHeader +{ + // Add cookies from the persistent (mac os global) store + if ([self useCookiePersistence]) { + NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[[self url] absoluteURL]]; + if (cookies) { + [[self requestCookies] addObjectsFromArray:cookies]; + } + } + + // Apply request cookies + NSArray *cookies; + if ([self mainRequest]) { + cookies = [[self mainRequest] requestCookies]; + } else { + cookies = [self requestCookies]; + } + if ([cookies count] > 0) { + NSHTTPCookie *cookie; + NSString *cookieHeader = nil; + for (cookie in cookies) { + if (!cookieHeader) { + cookieHeader = [NSString stringWithFormat: @"%@=%@",[cookie name],[cookie value]]; + } else { + cookieHeader = [NSString stringWithFormat: @"%@; %@=%@",cookieHeader,[cookie name],[cookie value]]; + } + } + if (cookieHeader) { + [self addRequestHeader:@"Cookie" value:cookieHeader]; + } + } +} + +- (void)buildRequestHeaders +{ + if ([self haveBuiltRequestHeaders]) { + return; + } + [self setHaveBuiltRequestHeaders:YES]; + + if ([self mainRequest]) { + for (NSString *header in [[self mainRequest] requestHeaders]) { + [self addRequestHeader:header value:[[[self mainRequest] requestHeaders] valueForKey:header]]; + } + return; + } + + [self applyCookieHeader]; + + // Build and set the user agent string if the request does not already have a custom user agent specified + if (![[self requestHeaders] objectForKey:@"User-Agent"]) { + NSString *userAgentString = [ASIHTTPRequest defaultUserAgentString]; + if (userAgentString) { + [self addRequestHeader:@"User-Agent" value:userAgentString]; + } + } + + + // Accept a compressed response + if ([self allowCompressedResponse]) { + [self addRequestHeader:@"Accept-Encoding" value:@"gzip"]; + } + + // Configure a compressed request body + if ([self shouldCompressRequestBody]) { + [self addRequestHeader:@"Content-Encoding" value:@"gzip"]; + } + + // Should this request resume an existing download? + [self updatePartialDownloadSize]; + if ([self partialDownloadSize]) { + [self addRequestHeader:@"Range" value:[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]]; + } +} + +- (void)updatePartialDownloadSize +{ + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [fileManager fileExistsAtPath:[self temporaryFileDownloadPath]]) { + NSError *err = nil; + [self setPartialDownloadSize:[[fileManager attributesOfItemAtPath:[self temporaryFileDownloadPath] error:&err] fileSize]]; + if (err) { + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '%@'",[self temporaryFileDownloadPath]],NSLocalizedDescriptionKey,error,NSUnderlyingErrorKey,nil]]]; + return; + } + } +} + +- (void)startRequest +{ + if ([self isCancelled]) { + return; + } + + [self performSelectorOnMainThread:@selector(requestStarted) withObject:nil waitUntilDone:[NSThread isMainThread]]; + + [self setDownloadComplete:NO]; + [self setComplete:NO]; + [self setTotalBytesRead:0]; + [self setLastBytesRead:0]; + + if ([self redirectCount] == 0) { + [self setOriginalURL:[self url]]; + } + + // If we're retrying a request, let's remove any progress we made + if ([self lastBytesSent] > 0) { + [self removeUploadProgressSoFar]; + } + + [self setLastBytesSent:0]; + [self setContentLength:0]; + [self setResponseHeaders:nil]; + if (![self downloadDestinationPath]) { + [self setRawResponseData:[[[NSMutableData alloc] init] autorelease]]; + } + + + // + // Create the stream for the request + // + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + [self setReadStreamIsScheduled:NO]; + + // Do we need to stream the request body from disk + if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [fileManager fileExistsAtPath:[self postBodyFilePath]]) { + + // Are we gzipping the request body? + if ([self compressedPostBodyFilePath] && [fileManager fileExistsAtPath:[self compressedPostBodyFilePath]]) { + [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath] request:self]]; + } else { + [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]]; + } + [self setReadStream:[(NSInputStream *)CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]) autorelease]]; + } else { + + // If we have a request body, we'll stream it from memory using our custom stream, so that we can measure bandwidth use and it can be bandwidth-throttled if necessary + if ([self postBody] && [[self postBody] length] > 0) { + if ([self shouldCompressRequestBody] && [self compressedPostBody]) { + [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody] request:self]]; + } else if ([self postBody]) { + [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody] request:self]]; + } + [self setReadStream:[(NSInputStream *)CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]) autorelease]]; + + } else { + [self setReadStream:[(NSInputStream *)CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request) autorelease]]; + } + } + + if (![self readStream]) { + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]]; + return; + } + + + + + // + // Handle SSL certificate settings + // + + if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) { + + NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1]; + + // Tell CFNetwork not to validate SSL certificates + if (![self validatesSecureCertificate]) { + [sslProperties setObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]; + } + + // Tell CFNetwork to use a client certificate + if (clientCertificateIdentity) { + + NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1]; + + // The first object in the array is our SecIdentityRef + [certificates addObject:(id)clientCertificateIdentity]; + + // If we've added any additional certificates, add them too + for (id cert in clientCertificates) { + [certificates addObject:cert]; + } + [sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates]; + } + + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties); + } + + // + // Handle proxy settings + // + + if ([self proxyHost] && [self proxyPort]) { + NSString *hostKey; + NSString *portKey; + + if (![self proxyType]) { + [self setProxyType:(NSString *)kCFProxyTypeHTTP]; + } + + if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) { + hostKey = (NSString *)kCFStreamPropertySOCKSProxyHost; + portKey = (NSString *)kCFStreamPropertySOCKSProxyPort; + } else { + hostKey = (NSString *)kCFStreamPropertyHTTPProxyHost; + portKey = (NSString *)kCFStreamPropertyHTTPProxyPort; + if ([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) { + hostKey = (NSString *)kCFStreamPropertyHTTPSProxyHost; + portKey = (NSString *)kCFStreamPropertyHTTPSProxyPort; + } + } + NSMutableDictionary *proxyToUse = [NSMutableDictionary dictionaryWithObjectsAndKeys:[self proxyHost],hostKey,[NSNumber numberWithInt:[self proxyPort]],portKey,nil]; + + if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) { + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySOCKSProxy, proxyToUse); + } else { + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPProxy, proxyToUse); + } + } + + + // + // Handle persistent connections + // + + [ASIHTTPRequest expirePersistentConnections]; + + [connectionsLock lock]; + + + if (![[self url] host] || ![[self url] scheme]) { + [self setConnectionInfo:nil]; + [self setShouldAttemptPersistentConnection:NO]; + } + + // Will store the old stream that was using this connection (if there was one) so we can clean it up once we've opened our own stream + NSInputStream *oldStream = nil; + + // Use a persistent connection if possible + if ([self shouldAttemptPersistentConnection]) { + + + // If we are redirecting, we will re-use the current connection only if we are connecting to the same server + if ([self connectionInfo]) { + + if (![[[self connectionInfo] objectForKey:@"host"] isEqualToString:[[self url] host]] || ![[[self connectionInfo] objectForKey:@"scheme"] isEqualToString:[[self url] scheme]] || [(NSNumber *)[[self connectionInfo] objectForKey:@"port"] intValue] != [[[self url] port] intValue]) { + [self setConnectionInfo:nil]; + + // Check if we should have expired this connection + } else if ([[[self connectionInfo] objectForKey:@"expires"] timeIntervalSinceNow] < 0) { + #if DEBUG_PERSISTENT_CONNECTIONS + NSLog(@"Not re-using connection #%i because it has expired",[[[self connectionInfo] objectForKey:@"id"] intValue]); + #endif + [persistentConnectionsPool removeObject:[self connectionInfo]]; + [self setConnectionInfo:nil]; + } + } + + + + if (![self connectionInfo] && [[self url] host] && [[self url] scheme]) { // We must have a proper url with a host and scheme, or this will explode + + // Look for a connection to the same server in the pool + for (NSMutableDictionary *existingConnection in persistentConnectionsPool) { + if (![existingConnection objectForKey:@"request"] && [[existingConnection objectForKey:@"host"] isEqualToString:[[self url] host]] && [[existingConnection objectForKey:@"scheme"] isEqualToString:[[self url] scheme]] && [(NSNumber *)[existingConnection objectForKey:@"port"] intValue] == [[[self url] port] intValue]) { + [self setConnectionInfo:existingConnection]; + } + } + } + + if ([[self connectionInfo] objectForKey:@"stream"]) { + oldStream = [[[self connectionInfo] objectForKey:@"stream"] retain]; + + } + + // No free connection was found in the pool matching the server/scheme/port we're connecting to, we'll need to create a new one + if (![self connectionInfo]) { + [self setConnectionInfo:[NSMutableDictionary dictionary]]; + nextConnectionNumberToCreate++; + [[self connectionInfo] setObject:[NSNumber numberWithInt:nextConnectionNumberToCreate] forKey:@"id"]; + [[self connectionInfo] setObject:[[self url] host] forKey:@"host"]; + [[self connectionInfo] setObject:[NSNumber numberWithInt:[[[self url] port] intValue]] forKey:@"port"]; + [[self connectionInfo] setObject:[[self url] scheme] forKey:@"scheme"]; + [persistentConnectionsPool addObject:[self connectionInfo]]; + } + + // If we are retrying this request, it will already have a requestID + if (![self requestID]) { + nextRequestID++; + [self setRequestID:[NSNumber numberWithUnsignedInt:nextRequestID]]; + } + [[self connectionInfo] setObject:[self requestID] forKey:@"request"]; + [[self connectionInfo] setObject:[self readStream] forKey:@"stream"]; + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue); + + #if DEBUG_PERSISTENT_CONNECTIONS + NSLog(@"Request #%@ will use connection #%i",[self requestID],[[[self connectionInfo] objectForKey:@"id"] intValue]); + #endif + + + // Tag the stream with an id that tells it which connection to use behind the scenes + // See http://lists.apple.com/archives/macnetworkprog/2008/Dec/msg00001.html for details on this approach + + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]); + + } + + [connectionsLock unlock]; + + // Schedule the stream + if (![self readStreamIsScheduled] && (!throttleWakeUpTime || [throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] < 0)) { + [self scheduleReadStream]; + } + + BOOL streamSuccessfullyOpened = NO; + + + // Start the HTTP connection + CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL}; + if (CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt)) { + if (CFReadStreamOpen((CFReadStreamRef)[self readStream])) { + streamSuccessfullyOpened = YES; + } + } + + // Here, we'll close the stream that was previously using this connection, if there was one + // We've kept it open until now (when we've just opened a new stream) so that the new stream can make use of the old connection + // http://lists.apple.com/archives/Macnetworkprog/2006/Mar/msg00119.html + if (oldStream) { + [oldStream close]; + [oldStream release]; + oldStream = nil; + } + + if (!streamSuccessfullyOpened) { + [self setConnectionCanBeReused:NO]; + [self destroyReadStream]; + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]]; + return; + } + + if (![self mainRequest]) { + if ([self shouldResetUploadProgress]) { + if ([self showAccurateProgress]) { + [self incrementUploadSizeBy:[self postLength]]; + } else { + [self incrementUploadSizeBy:1]; + } + [ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:1]; + } + if ([self shouldResetDownloadProgress] && ![self partialDownloadSize]) { + [ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:0 ofTotal:1]; + } + } + + + // Record when the request started, so we can timeout if nothing happens + [self setLastActivityTime:[NSDate date]]; + [self setStatusTimer:[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES]]; + [[NSRunLoop currentRunLoop] addTimer:[self statusTimer] forMode:[self runLoopMode]]; +} + +- (void)setStatusTimer:(NSTimer *)timer +{ + CFRetain(self); + // We must invalidate the old timer here, not before we've created and scheduled a new timer + // This is because the timer may be the only thing retaining an asynchronous request + if (statusTimer && timer != statusTimer) { + [statusTimer invalidate]; + [statusTimer release]; + } + statusTimer = [timer retain]; + CFRelease(self); +} + +// This gets fired every 1/4 of a second to update the progress and work out if we need to timeout +- (void)updateStatus:(NSTimer*)timer +{ + [self checkRequestStatus]; + if (![self inProgress]) { + [self setStatusTimer:nil]; + } +} + +- (void)performRedirect +{ + [self setURL:[self redirectURL]]; + [self setComplete:YES]; + [self setNeedsRedirect:NO]; + [self setRedirectCount:[self redirectCount]+1]; + + if ([self redirectCount] > RedirectionLimit) { + // Some naughty / badly coded website is trying to force us into a redirection loop. This is not cool. + [self failWithError:ASITooMuchRedirectionError]; + [self setComplete:YES]; + } else { + // Go all the way back to the beginning and build the request again, so that we can apply any new cookies + [self main]; + } +} + +// Called by delegate to resume loading with a new url after the delegate received request:willRedirectToURL: +- (void)redirectToURL:(NSURL *)newURL +{ + [self setRedirectURL:newURL]; + [self performSelector:@selector(performRedirect) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; +} + +- (BOOL)shouldTimeOut +{ + NSTimeInterval secondsSinceLastActivity = [[NSDate date] timeIntervalSinceDate:lastActivityTime]; + // See if we need to timeout + if ([self readStream] && [self readStreamIsScheduled] && [self lastActivityTime] && [self timeOutSeconds] > 0 && secondsSinceLastActivity > [self timeOutSeconds]) { + + // We have no body, or we've sent more than the upload buffer size,so we can safely time out here + if ([self postLength] == 0 || ([self uploadBufferSize] > 0 && [self totalBytesSent] > [self uploadBufferSize])) { + return YES; + + // ***Black magic warning*** + // We have a body, but we've taken longer than timeOutSeconds to upload the first small chunk of data + // Since there's no reliable way to track upload progress for the first 32KB (iPhone) or 128KB (Mac) with CFNetwork, we'll be slightly more forgiving on the timeout, as there's a strong chance our connection is just very slow. + } else if (secondsSinceLastActivity > [self timeOutSeconds]*1.5) { + return YES; + } + } + return NO; +} + +- (void)checkRequestStatus +{ + // We won't let the request cancel while we're updating progress / checking for a timeout + [[self cancelledLock] lock]; + // See if our NSOperationQueue told us to cancel + if ([self isCancelled] || [self complete]) { + [[self cancelledLock] unlock]; + return; + } + + [self performThrottling]; + + if ([self shouldTimeOut]) { + // Do we need to auto-retry this request? + if ([self numberOfTimesToRetryOnTimeout] > [self retryCount]) { + + // If we are resuming a download, we may need to update the Range header to take account of data we've just downloaded + [self updatePartialDownloadSize]; + if ([self partialDownloadSize]) { + CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)@"Range", (CFStringRef)[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]); + } + [self setRetryCount:[self retryCount]+1]; + [self unscheduleReadStream]; + [[self cancelledLock] unlock]; + [self startRequest]; + return; + } + [self failWithError:ASIRequestTimedOutError]; + [self cancelLoad]; + [self setComplete:YES]; + [[self cancelledLock] unlock]; + return; + } + + // readStream will be null if we aren't currently running (perhaps we're waiting for a delegate to supply credentials) + if ([self readStream]) { + + // If we have a post body + if ([self postLength]) { + + [self setLastBytesSent:totalBytesSent]; + + // Find out how much data we've uploaded so far + [self setTotalBytesSent:[NSMakeCollectable([(NSNumber *)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease]) unsignedLongLongValue]]; + if (totalBytesSent > lastBytesSent) { + + // We've uploaded more data, reset the timeout + [self setLastActivityTime:[NSDate date]]; + [ASIHTTPRequest incrementBandwidthUsedInLastSecond:(unsigned long)(totalBytesSent-lastBytesSent)]; + + #if DEBUG_REQUEST_STATUS + if ([self totalBytesSent] == [self postLength]) { + NSLog(@"Request %@ finished uploading data",self); + } + #endif + } + } + + [self updateProgressIndicators]; + + } + + [[self cancelledLock] unlock]; +} + + +// Cancel loading and clean up. DO NOT USE THIS TO CANCEL REQUESTS - use [request cancel] instead +- (void)cancelLoad +{ + // If we're in the middle of downloading a PAC file, let's stop that first + if (PACFileReadStream) { + [PACFileReadStream setDelegate:nil]; + [PACFileReadStream close]; + [self setPACFileReadStream:nil]; + [self setPACFileData:nil]; + } else if (PACFileRequest) { + [PACFileRequest setDelegate:nil]; + [PACFileRequest cancel]; + [self setPACFileRequest:nil]; + } + + [self destroyReadStream]; + + [[self postBodyReadStream] close]; + [self setPostBodyReadStream:nil]; + + if ([self rawResponseData]) { + [self setRawResponseData:nil]; + + // If we were downloading to a file + } else if ([self temporaryFileDownloadPath]) { + [[self fileDownloadOutputStream] close]; + [self setFileDownloadOutputStream:nil]; + + [[self inflatedFileDownloadOutputStream] close]; + [self setInflatedFileDownloadOutputStream:nil]; + + // If we haven't said we might want to resume, let's remove the temporary file too + if (![self allowResumeForFileDownloads]) { + [self removeTemporaryDownloadFile]; + } + [self removeTemporaryUncompressedDownloadFile]; + } + + // Clean up any temporary file used to store request body for streaming + if (![self authenticationNeeded] && [self didCreateTemporaryPostDataFile]) { + [self removeTemporaryUploadFile]; + [self removeTemporaryCompressedUploadFile]; + [self setDidCreateTemporaryPostDataFile:NO]; + } + + [self setResponseHeaders:nil]; +} + +#pragma mark HEAD request + +// Used by ASINetworkQueue to create a HEAD request appropriate for this request with the same headers (though you can use it yourself) +- (ASIHTTPRequest *)HEADRequest +{ + ASIHTTPRequest *headRequest = [[self class] requestWithURL:[self url]]; + + // Copy the properties that make sense for a HEAD request + [headRequest setRequestHeaders:[[[self requestHeaders] mutableCopy] autorelease]]; + [headRequest setRequestCookies:[[[self requestCookies] mutableCopy] autorelease]]; + [headRequest setUseCookiePersistence:[self useCookiePersistence]]; + [headRequest setUseKeychainPersistence:[self useKeychainPersistence]]; + [headRequest setUseSessionPersistence:[self useSessionPersistence]]; + [headRequest setAllowCompressedResponse:[self allowCompressedResponse]]; + [headRequest setUsername:[self username]]; + [headRequest setPassword:[self password]]; + [headRequest setDomain:[self domain]]; + [headRequest setProxyUsername:[self proxyUsername]]; + [headRequest setProxyPassword:[self proxyPassword]]; + [headRequest setProxyDomain:[self proxyDomain]]; + [headRequest setProxyHost:[self proxyHost]]; + [headRequest setProxyPort:[self proxyPort]]; + [headRequest setProxyType:[self proxyType]]; + [headRequest setShouldPresentAuthenticationDialog:[self shouldPresentAuthenticationDialog]]; + [headRequest setShouldPresentProxyAuthenticationDialog:[self shouldPresentProxyAuthenticationDialog]]; + [headRequest setTimeOutSeconds:[self timeOutSeconds]]; + [headRequest setUseHTTPVersionOne:[self useHTTPVersionOne]]; + [headRequest setValidatesSecureCertificate:[self validatesSecureCertificate]]; + [headRequest setClientCertificateIdentity:clientCertificateIdentity]; + [headRequest setClientCertificates:[[clientCertificates copy] autorelease]]; + [headRequest setPACurl:[self PACurl]]; + [headRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]]; + [headRequest setNumberOfTimesToRetryOnTimeout:[self numberOfTimesToRetryOnTimeout]]; + [headRequest setShouldUseRFC2616RedirectBehaviour:[self shouldUseRFC2616RedirectBehaviour]]; + [headRequest setShouldAttemptPersistentConnection:[self shouldAttemptPersistentConnection]]; + [headRequest setPersistentConnectionTimeoutSeconds:[self persistentConnectionTimeoutSeconds]]; + + [headRequest setMainRequest:self]; + [headRequest setRequestMethod:@"HEAD"]; + return headRequest; +} + + +#pragma mark upload/download progress + + +- (void)updateProgressIndicators +{ + //Only update progress if this isn't a HEAD request used to preset the content-length + if (![self mainRequest]) { + if ([self showAccurateProgress] || ([self complete] && ![self updatedProgress])) { + [self updateUploadProgress]; + [self updateDownloadProgress]; + } + } +} + +- (id)uploadProgressDelegate +{ + [[self cancelledLock] lock]; + id d = [[uploadProgressDelegate retain] autorelease]; + [[self cancelledLock] unlock]; + return d; +} + +- (void)setUploadProgressDelegate:(id)newDelegate +{ + [[self cancelledLock] lock]; + uploadProgressDelegate = newDelegate; + + #if !TARGET_OS_IPHONE + // If the uploadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can update it as if it were a UIProgressView + double max = 1.0; + [ASIHTTPRequest performSelector:@selector(setMaxValue:) onTarget:&uploadProgressDelegate withObject:nil amount:&max callerToRetain:nil]; + #endif + [[self cancelledLock] unlock]; +} + +- (id)downloadProgressDelegate +{ + [[self cancelledLock] lock]; + id d = [[downloadProgressDelegate retain] autorelease]; + [[self cancelledLock] unlock]; + return d; +} + +- (void)setDownloadProgressDelegate:(id)newDelegate +{ + [[self cancelledLock] lock]; + downloadProgressDelegate = newDelegate; + + #if !TARGET_OS_IPHONE + // If the downloadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can update it as if it were a UIProgressView + double max = 1.0; + [ASIHTTPRequest performSelector:@selector(setMaxValue:) onTarget:&downloadProgressDelegate withObject:nil amount:&max callerToRetain:nil]; + #endif + [[self cancelledLock] unlock]; +} + + +- (void)updateDownloadProgress +{ + // We won't update download progress until we've examined the headers, since we might need to authenticate + if (![self responseHeaders] || [self needsRedirect] || !([self contentLength] || [self complete])) { + return; + } + + unsigned long long bytesReadSoFar = [self totalBytesRead]+[self partialDownloadSize]; + unsigned long long value = 0; + + if ([self showAccurateProgress] && [self contentLength]) { + value = bytesReadSoFar-[self lastBytesRead]; + if (value == 0) { + return; + } + } else { + value = 1; + [self setUpdatedProgress:YES]; + } + if (!value) { + return; + } + + [ASIHTTPRequest performSelector:@selector(request:didReceiveBytes:) onTarget:&queue withObject:self amount:&value callerToRetain:self]; + [ASIHTTPRequest performSelector:@selector(request:didReceiveBytes:) onTarget:&downloadProgressDelegate withObject:self amount:&value callerToRetain:self]; + + [ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:[self totalBytesRead]+[self partialDownloadSize] ofTotal:[self contentLength]+[self partialDownloadSize]]; + + #if NS_BLOCKS_AVAILABLE + if (bytesReceivedBlock) { + unsigned long long totalSize = [self contentLength] + [self partialDownloadSize]; + [self performBlockOnMainThread:^{ if (bytesReceivedBlock) { bytesReceivedBlock(value, totalSize); }}]; + } + #endif + [self setLastBytesRead:bytesReadSoFar]; +} + +- (void)updateUploadProgress +{ + if ([self isCancelled] || [self totalBytesSent] == 0) { + return; + } + + // If this is the first time we've written to the buffer, totalBytesSent will be the size of the buffer (currently seems to be 128KB on both Leopard and iPhone 2.2.1, 32KB on iPhone 3.0) + // If request body is less than the buffer size, totalBytesSent will be the total size of the request body + // We will remove this from any progress display, as kCFStreamPropertyHTTPRequestBytesWrittenCount does not tell us how much data has actually be written + if ([self uploadBufferSize] == 0 && [self totalBytesSent] != [self postLength]) { + [self setUploadBufferSize:[self totalBytesSent]]; + [self incrementUploadSizeBy:-[self uploadBufferSize]]; + } + + unsigned long long value = 0; + + if ([self showAccurateProgress]) { + if ([self totalBytesSent] == [self postLength] || [self lastBytesSent] > 0) { + value = [self totalBytesSent]-[self lastBytesSent]; + } else { + return; + } + } else { + value = 1; + [self setUpdatedProgress:YES]; + } + + if (!value) { + return; + } + + [ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&queue withObject:self amount:&value callerToRetain:self]; + [ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&uploadProgressDelegate withObject:self amount:&value callerToRetain:self]; + [ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:[self totalBytesSent]-[self uploadBufferSize] ofTotal:[self postLength]-[self uploadBufferSize]]; + + #if NS_BLOCKS_AVAILABLE + if(bytesSentBlock){ + unsigned long long totalSize = [self postLength]; + [self performBlockOnMainThread:^{ if (bytesSentBlock) { bytesSentBlock(value, totalSize); }}]; + } + #endif +} + + +- (void)incrementDownloadSizeBy:(long long)length +{ + [ASIHTTPRequest performSelector:@selector(request:incrementDownloadSizeBy:) onTarget:&queue withObject:self amount:&length callerToRetain:self]; + [ASIHTTPRequest performSelector:@selector(request:incrementDownloadSizeBy:) onTarget:&downloadProgressDelegate withObject:self amount:&length callerToRetain:self]; + + #if NS_BLOCKS_AVAILABLE + if(downloadSizeIncrementedBlock){ + [self performBlockOnMainThread:^{ if (downloadSizeIncrementedBlock) { downloadSizeIncrementedBlock(length); }}]; + } + #endif +} + +- (void)incrementUploadSizeBy:(long long)length +{ + [ASIHTTPRequest performSelector:@selector(request:incrementUploadSizeBy:) onTarget:&queue withObject:self amount:&length callerToRetain:self]; + [ASIHTTPRequest performSelector:@selector(request:incrementUploadSizeBy:) onTarget:&uploadProgressDelegate withObject:self amount:&length callerToRetain:self]; + + #if NS_BLOCKS_AVAILABLE + if(uploadSizeIncrementedBlock) { + [self performBlockOnMainThread:^{ if (uploadSizeIncrementedBlock) { uploadSizeIncrementedBlock(length); }}]; + } + #endif +} + + +-(void)removeUploadProgressSoFar +{ + long long progressToRemove = -[self totalBytesSent]; + [ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&queue withObject:self amount:&progressToRemove callerToRetain:self]; + [ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&uploadProgressDelegate withObject:self amount:&progressToRemove callerToRetain:self]; + [ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:[self postLength]]; + + #if NS_BLOCKS_AVAILABLE + if(bytesSentBlock){ + unsigned long long totalSize = [self postLength]; + [self performBlockOnMainThread:^{ if (bytesSentBlock) { bytesSentBlock(progressToRemove, totalSize); }}]; + } + #endif +} + +#if NS_BLOCKS_AVAILABLE +- (void)performBlockOnMainThread:(ASIBasicBlock)block +{ + [self performSelectorOnMainThread:@selector(callBlock:) withObject:[[block copy] autorelease] waitUntilDone:[NSThread isMainThread]]; +} + +- (void)callBlock:(ASIBasicBlock)block +{ + block(); +} +#endif + + ++ (void)performSelector:(SEL)selector onTarget:(id *)target withObject:(id)object amount:(void *)amount callerToRetain:(id)callerToRetain +{ + if ([*target respondsToSelector:selector]) { + NSMethodSignature *signature = nil; + signature = [*target methodSignatureForSelector:selector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + + [invocation setSelector:selector]; + + int argumentNumber = 2; + + // If we got an object parameter, we pass a pointer to the object pointer + if (object) { + [invocation setArgument:&object atIndex:argumentNumber]; + argumentNumber++; + } + + // For the amount we'll just pass the pointer directly so NSInvocation will call the method using the number itself rather than a pointer to it + if (amount) { + [invocation setArgument:amount atIndex:argumentNumber]; + } + + SEL callback = @selector(performInvocation:onTarget:releasingObject:); + NSMethodSignature *cbSignature = [ASIHTTPRequest methodSignatureForSelector:callback]; + NSInvocation *cbInvocation = [NSInvocation invocationWithMethodSignature:cbSignature]; + [cbInvocation setSelector:callback]; + [cbInvocation setTarget:self]; + [cbInvocation setArgument:&invocation atIndex:2]; + [cbInvocation setArgument:&target atIndex:3]; + if (callerToRetain) { + [cbInvocation setArgument:&callerToRetain atIndex:4]; + } + + CFRetain(invocation); + + // Used to pass in a request that we must retain until after the call + // We're using CFRetain rather than [callerToRetain retain] so things to avoid earthquakes when using garbage collection + if (callerToRetain) { + CFRetain(callerToRetain); + } + [cbInvocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:[NSThread isMainThread]]; + } +} + ++ (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease +{ + if (*target && [*target respondsToSelector:[invocation selector]]) { + [invocation invokeWithTarget:*target]; + } + CFRelease(invocation); + if (objectToRelease) { + CFRelease(objectToRelease); + } +} + + ++ (void)updateProgressIndicator:(id *)indicator withProgress:(unsigned long long)progress ofTotal:(unsigned long long)total +{ + #if TARGET_OS_IPHONE + // Cocoa Touch: UIProgressView + SEL selector = @selector(setProgress:); + float progressAmount = (float)((progress*1.0)/(total*1.0)); + + #else + // Cocoa: NSProgressIndicator + double progressAmount = progressAmount = (progress*1.0)/(total*1.0); + SEL selector = @selector(setDoubleValue:); + #endif + + if (![*indicator respondsToSelector:selector]) { + return; + } + + [progressLock lock]; + [ASIHTTPRequest performSelector:selector onTarget:indicator withObject:nil amount:&progressAmount callerToRetain:nil]; + [progressLock unlock]; +} + + +#pragma mark talking to delegates / calling blocks + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)requestStarted +{ + if ([self error] || [self mainRequest]) { + return; + } + if (delegate && [delegate respondsToSelector:didStartSelector]) { + [delegate performSelector:didStartSelector withObject:self]; + } + if (queue && [queue respondsToSelector:@selector(requestStarted:)]) { + [queue performSelector:@selector(requestStarted:) withObject:self]; + } + #if NS_BLOCKS_AVAILABLE + if(startedBlock){ + startedBlock(); + } + #endif +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)requestRedirected +{ + if ([self error] || [self mainRequest]) { + return; + } + + if([[self delegate] respondsToSelector:@selector(requestRedirected:)]){ + [[self delegate] performSelector:@selector(requestRedirected:) withObject:self]; + } + #if NS_BLOCKS_AVAILABLE + if(requestRedirectedBlock){ + requestRedirectedBlock(); + } + #endif +} + + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)requestReceivedResponseHeaders:(NSMutableDictionary *)newResponseHeaders +{ + if ([self error] || [self mainRequest]) { + return; + } + + if (delegate && [delegate respondsToSelector:didReceiveResponseHeadersSelector]) { + [delegate performSelector:didReceiveResponseHeadersSelector withObject:self withObject:newResponseHeaders]; + } + if (queue && [queue respondsToSelector:@selector(request:didReceiveResponseHeaders:)]) { + [queue performSelector:@selector(request:didReceiveResponseHeaders:) withObject:self withObject:newResponseHeaders]; + } + + #if NS_BLOCKS_AVAILABLE + if(headersReceivedBlock){ + headersReceivedBlock(newResponseHeaders); + } + #endif +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)requestWillRedirectToURL:(NSURL *)newURL +{ + if ([self error] || [self mainRequest]) { + return; + } + if (delegate && [delegate respondsToSelector:willRedirectSelector]) { + [delegate performSelector:willRedirectSelector withObject:self withObject:newURL]; + } + if (queue && [queue respondsToSelector:@selector(request:willRedirectToURL:)]) { + [queue performSelector:@selector(request:willRedirectToURL:) withObject:self withObject:newURL]; + } +} + +// Subclasses might override this method to process the result in the same thread +// If you do this, don't forget to call [super requestFinished] to let the queue / delegate know we're done +- (void)requestFinished +{ +#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING + NSLog(@"Request finished: %@",self); +#endif + if ([self error] || [self mainRequest]) { + return; + } + if ([self isPACFileRequest]) { + [self reportFinished]; + } else { + [self performSelectorOnMainThread:@selector(reportFinished) withObject:nil waitUntilDone:[NSThread isMainThread]]; + } +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)reportFinished +{ + if (delegate && [delegate respondsToSelector:didFinishSelector]) { + [delegate performSelector:didFinishSelector withObject:self]; + } + if (queue && [queue respondsToSelector:@selector(requestFinished:)]) { + [queue performSelector:@selector(requestFinished:) withObject:self]; + } +#if NS_BLOCKS_AVAILABLE + if(completionBlock){ + completionBlock(); + } +#endif +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)reportFailure +{ + if (delegate && [delegate respondsToSelector:didFailSelector]) { + [delegate performSelector:didFailSelector withObject:self]; + } + if (queue && [queue respondsToSelector:@selector(requestFailed:)]) { + [queue performSelector:@selector(requestFailed:) withObject:self]; + } + #if NS_BLOCKS_AVAILABLE + if(failureBlock){ + failureBlock(); + } + #endif +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)passOnReceivedData:(NSData *)data +{ + if (delegate && [delegate respondsToSelector:didReceiveDataSelector]) { + [delegate performSelector:didReceiveDataSelector withObject:self withObject:data]; + } + + #if NS_BLOCKS_AVAILABLE + if (dataReceivedBlock) { + dataReceivedBlock(data); + } + #endif +} + +// Subclasses might override this method to perform error handling in the same thread +// If you do this, don't forget to call [super failWithError:] to let the queue / delegate know we're done +- (void)failWithError:(NSError *)theError +{ +#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING + NSLog(@"Request %@: %@",self,(theError == ASIRequestCancelledError ? @"Cancelled" : @"Failed")); +#endif + [self setComplete:YES]; + + // Invalidate the current connection so subsequent requests don't attempt to reuse it + if (theError && [theError code] != ASIAuthenticationErrorType && [theError code] != ASITooMuchRedirectionErrorType) { + [connectionsLock lock]; + #if DEBUG_PERSISTENT_CONNECTIONS + NSLog(@"Request #%@ failed and will invalidate connection #%@",[self requestID],[[self connectionInfo] objectForKey:@"id"]); + #endif + [[self connectionInfo] removeObjectForKey:@"request"]; + [persistentConnectionsPool removeObject:[self connectionInfo]]; + [connectionsLock unlock]; + [self destroyReadStream]; + } + if ([self connectionCanBeReused]) { + [[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:[self persistentConnectionTimeoutSeconds]] forKey:@"expires"]; + } + + if ([self isCancelled] || [self error]) { + return; + } + + // If we have cached data, use it and ignore the error when using ASIFallbackToCacheIfLoadFailsCachePolicy + if ([self downloadCache] && ([self cachePolicy] & ASIFallbackToCacheIfLoadFailsCachePolicy)) { + if ([[self downloadCache] canUseCachedDataForRequest:self]) { + [self useDataFromCache]; + return; + } + } + + + [self setError:theError]; + + ASIHTTPRequest *failedRequest = self; + + // If this is a HEAD request created by an ASINetworkQueue or compatible queue delegate, make the main request fail + if ([self mainRequest]) { + failedRequest = [self mainRequest]; + [failedRequest setError:theError]; + } + + if ([self isPACFileRequest]) { + [failedRequest reportFailure]; + } else { + [failedRequest performSelectorOnMainThread:@selector(reportFailure) withObject:nil waitUntilDone:[NSThread isMainThread]]; + } + + if (!inProgress) + { + // if we're not in progress, we can't notify the queue we've finished (doing so can cause a crash later on) + // "markAsFinished" will be at the start of main() when we are started + return; + } + [self markAsFinished]; +} + +#pragma mark parsing HTTP response headers + +- (void)readResponseHeaders +{ + [self setAuthenticationNeeded:ASINoAuthenticationNeededYet]; + + CFHTTPMessageRef message = (CFHTTPMessageRef)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPResponseHeader); + if (!message) { + return; + } + + // Make sure we've received all the headers + if (!CFHTTPMessageIsHeaderComplete(message)) { + CFRelease(message); + return; + } + + #if DEBUG_REQUEST_STATUS + if ([self totalBytesSent] == [self postLength]) { + NSLog(@"Request %@ received response headers",self); + } + #endif + + CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(message); + [self setResponseHeaders:(NSDictionary *)headerFields]; + + CFRelease(headerFields); + + [self setResponseStatusCode:(int)CFHTTPMessageGetResponseStatusCode(message)]; + [self setResponseStatusMessage:[(NSString *)CFHTTPMessageCopyResponseStatusLine(message) autorelease]]; + + if ([self downloadCache] && ([[self downloadCache] canUseCachedDataForRequest:self])) { + // Read the response from the cache + [self useDataFromCache]; + // Update the response headers (this will usually move the expiry date into the future) + [[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]]; + CFRelease(message); + return; + } + + // Is the server response a challenge for credentials? + if ([self responseStatusCode] == 401) { + [self setAuthenticationNeeded:ASIHTTPAuthenticationNeeded]; + } else if ([self responseStatusCode] == 407) { + [self setAuthenticationNeeded:ASIProxyAuthenticationNeeded]; + } + + // Authentication succeeded, or no authentication was required + if (![self authenticationNeeded]) { + + // Did we get here without an authentication challenge? (which can happen when shouldPresentCredentialsBeforeChallenge is YES and basic auth was successful) + if (!requestAuthentication && [self username] && [self password] && [self useSessionPersistence]) { + + NSMutableDictionary *newCredentials = [NSMutableDictionary dictionaryWithCapacity:2]; + [newCredentials setObject:[self username] forKey:(NSString *)kCFHTTPAuthenticationUsername]; + [newCredentials setObject:[self password] forKey:(NSString *)kCFHTTPAuthenticationPassword]; + + // Store the credentials in the session + NSMutableDictionary *sessionCredentials = [NSMutableDictionary dictionary]; + [sessionCredentials setObject:newCredentials forKey:@"Credentials"]; + [sessionCredentials setObject:[self url] forKey:@"URL"]; + [sessionCredentials setObject:(NSString *)kCFHTTPAuthenticationSchemeBasic forKey:@"AuthenticationScheme"]; + [[self class] storeAuthenticationCredentialsInSessionStore:sessionCredentials]; + } + } + + // Read response textEncoding + [self parseStringEncodingFromHeaders]; + + // Handle cookies + NSArray *newCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[self responseHeaders] forURL:[self url]]; + [self setResponseCookies:newCookies]; + + if ([self useCookiePersistence]) { + + // Store cookies in global persistent store + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:newCookies forURL:[self url] mainDocumentURL:nil]; + + // We also keep any cookies in the sessionCookies array, so that we have a reference to them if we need to remove them later + NSHTTPCookie *cookie; + for (cookie in newCookies) { + [ASIHTTPRequest addSessionCookie:cookie]; + } + } + + // Do we need to redirect? + // Note that ASIHTTPRequest does not currently support 305 Use Proxy + if ([self shouldRedirect] && [responseHeaders valueForKey:@"Location"]) { + if (([self responseStatusCode] > 300 && [self responseStatusCode] < 304) || [self responseStatusCode] == 307) { + + [self performSelectorOnMainThread:@selector(requestRedirected) withObject:nil waitUntilDone:[NSThread isMainThread]]; + + // By default, we redirect 301 and 302 response codes as GET requests + // According to RFC 2616 this is wrong, but this is what most browsers do, so it's probably what you're expecting to happen + // See also: + // http://allseeing-i.lighthouseapp.com/projects/27881/tickets/27-302-redirection-issue + + if ([self responseStatusCode] != 307 && (![self shouldUseRFC2616RedirectBehaviour] || [self responseStatusCode] == 303)) { + [self setRequestMethod:@"GET"]; + [self setPostBody:nil]; + [self setPostLength:0]; + + // Perhaps there are other headers we should be preserving, but it's hard to know what we need to keep and what to throw away. + NSString *userAgentHeader = [[self requestHeaders] objectForKey:@"User-Agent"]; + NSString *acceptHeader = [[self requestHeaders] objectForKey:@"Accept"]; + [self setRequestHeaders:nil]; + if (userAgentHeader) { + [self addRequestHeader:@"User-Agent" value:userAgentHeader]; + } + if (acceptHeader) { + [self addRequestHeader:@"Accept" value:acceptHeader]; + } + [self setHaveBuiltRequestHeaders:NO]; + } else { + + // Force rebuild the cookie header incase we got some new cookies from this request + // All other request headers will remain as they are for 301 / 302 redirects + [self applyCookieHeader]; + } + + // Force the redirected request to rebuild the request headers (if not a 303, it will re-use old ones, and add any new ones) + [self setRedirectURL:[[NSURL URLWithString:[responseHeaders valueForKey:@"Location"] relativeToURL:[self url]] absoluteURL]]; + [self setNeedsRedirect:YES]; + + // Clear the request cookies + // This means manually added cookies will not be added to the redirect request - only those stored in the global persistent store + // But, this is probably the safest option - we might be redirecting to a different domain + [self setRequestCookies:[NSMutableArray array]]; + + #if DEBUG_REQUEST_STATUS + NSLog(@"Request will redirect (code: %i): %@",[self responseStatusCode],self); + #endif + + } + } + + if (![self needsRedirect]) { + // See if we got a Content-length header + NSString *cLength = [responseHeaders valueForKey:@"Content-Length"]; + ASIHTTPRequest *theRequest = self; + if ([self mainRequest]) { + theRequest = [self mainRequest]; + } + + if (cLength) { + unsigned long long length = strtoull([cLength UTF8String], NULL, 0); + + // Workaround for Apache HEAD requests for dynamically generated content returning the wrong Content-Length when using gzip + if ([self mainRequest] && [self allowCompressedResponse] && length == 20 && [self showAccurateProgress] && [self shouldResetDownloadProgress]) { + [[self mainRequest] setShowAccurateProgress:NO]; + [[self mainRequest] incrementDownloadSizeBy:1]; + + } else { + [theRequest setContentLength:length]; + if ([self showAccurateProgress] && [self shouldResetDownloadProgress]) { + [theRequest incrementDownloadSizeBy:[theRequest contentLength]+[theRequest partialDownloadSize]]; + } + } + + } else if ([self showAccurateProgress] && [self shouldResetDownloadProgress]) { + [theRequest setShowAccurateProgress:NO]; + [theRequest incrementDownloadSizeBy:1]; + } + } + + // Handle connection persistence + if ([self shouldAttemptPersistentConnection]) { + + NSString *connectionHeader = [[[self responseHeaders] objectForKey:@"Connection"] lowercaseString]; + NSString *httpVersion = NSMakeCollectable([(NSString *)CFHTTPMessageCopyVersion(message) autorelease]); + + // Don't re-use the connection if the server is HTTP 1.0 and didn't send Connection: Keep-Alive + if (![httpVersion isEqualToString:(NSString *)kCFHTTPVersion1_0] || [connectionHeader isEqualToString:@"keep-alive"]) { + + // See if server explicitly told us to close the connection + if (![connectionHeader isEqualToString:@"close"]) { + + NSString *keepAliveHeader = [[self responseHeaders] objectForKey:@"Keep-Alive"]; + + // If we got a keep alive header, we'll reuse the connection for as long as the server tells us + if (keepAliveHeader) { + int timeout = 0; + int max = 0; + NSScanner *scanner = [NSScanner scannerWithString:keepAliveHeader]; + [scanner scanString:@"timeout=" intoString:NULL]; + [scanner scanInt:&timeout]; + [scanner scanUpToString:@"max=" intoString:NULL]; + [scanner scanString:@"max=" intoString:NULL]; + [scanner scanInt:&max]; + if (max > 5) { + [self setConnectionCanBeReused:YES]; + [self setPersistentConnectionTimeoutSeconds:timeout]; + #if DEBUG_PERSISTENT_CONNECTIONS + NSLog(@"Got a keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]); + #endif + } + + // Otherwise, we'll assume we can keep this connection open + } else { + [self setConnectionCanBeReused:YES]; + #if DEBUG_PERSISTENT_CONNECTIONS + NSLog(@"Got no keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]); + #endif + } + } + } + } + + CFRelease(message); + [self performSelectorOnMainThread:@selector(requestReceivedResponseHeaders:) withObject:[[[self responseHeaders] copy] autorelease] waitUntilDone:[NSThread isMainThread]]; +} + +- (void)parseStringEncodingFromHeaders +{ + // Handle response text encoding + NSStringEncoding charset = 0; + NSString *mimeType = nil; + [[self class] parseMimeType:&mimeType andResponseEncoding:&charset fromContentType:[[self responseHeaders] valueForKey:@"Content-Type"]]; + if (charset != 0) { + [self setResponseEncoding:charset]; + } else { + [self setResponseEncoding:[self defaultResponseEncoding]]; + } +} + +#pragma mark http authentication + +- (void)saveProxyCredentialsToKeychain:(NSDictionary *)newCredentials +{ + NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent]; + if (authenticationCredentials) { + [ASIHTTPRequest saveCredentials:authenticationCredentials forProxy:[self proxyHost] port:[self proxyPort] realm:[self proxyAuthenticationRealm]]; + } +} + + +- (void)saveCredentialsToKeychain:(NSDictionary *)newCredentials +{ + NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent]; + + if (authenticationCredentials) { + [ASIHTTPRequest saveCredentials:authenticationCredentials forHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]]; + } +} + +- (BOOL)applyProxyCredentials:(NSDictionary *)newCredentials +{ + [self setProxyAuthenticationRetryCount:[self proxyAuthenticationRetryCount]+1]; + + if (newCredentials && proxyAuthentication && request) { + + // Apply whatever credentials we've built up to the old request + if (CFHTTPMessageApplyCredentialDictionary(request, proxyAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) { + + //If we have credentials and they're ok, let's save them to the keychain + if (useKeychainPersistence) { + [self saveProxyCredentialsToKeychain:newCredentials]; + } + if (useSessionPersistence) { + NSMutableDictionary *sessionProxyCredentials = [NSMutableDictionary dictionary]; + [sessionProxyCredentials setObject:(id)proxyAuthentication forKey:@"Authentication"]; + [sessionProxyCredentials setObject:newCredentials forKey:@"Credentials"]; + [sessionProxyCredentials setObject:[self proxyHost] forKey:@"Host"]; + [sessionProxyCredentials setObject:[NSNumber numberWithInt:[self proxyPort]] forKey:@"Port"]; + [sessionProxyCredentials setObject:[self proxyAuthenticationScheme] forKey:@"AuthenticationScheme"]; + [[self class] storeProxyAuthenticationCredentialsInSessionStore:sessionProxyCredentials]; + } + [self setProxyCredentials:newCredentials]; + return YES; + } else { + [[self class] removeProxyAuthenticationCredentialsFromSessionStore:newCredentials]; + } + } + return NO; +} + +- (BOOL)applyCredentials:(NSDictionary *)newCredentials +{ + [self setAuthenticationRetryCount:[self authenticationRetryCount]+1]; + + if (newCredentials && requestAuthentication && request) { + // Apply whatever credentials we've built up to the old request + if (CFHTTPMessageApplyCredentialDictionary(request, requestAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) { + + //If we have credentials and they're ok, let's save them to the keychain + if (useKeychainPersistence) { + [self saveCredentialsToKeychain:newCredentials]; + } + if (useSessionPersistence) { + + NSMutableDictionary *sessionCredentials = [NSMutableDictionary dictionary]; + [sessionCredentials setObject:(id)requestAuthentication forKey:@"Authentication"]; + [sessionCredentials setObject:newCredentials forKey:@"Credentials"]; + [sessionCredentials setObject:[self url] forKey:@"URL"]; + [sessionCredentials setObject:[self authenticationScheme] forKey:@"AuthenticationScheme"]; + if ([self authenticationRealm]) { + [sessionCredentials setObject:[self authenticationRealm] forKey:@"AuthenticationRealm"]; + } + [[self class] storeAuthenticationCredentialsInSessionStore:sessionCredentials]; + + } + [self setRequestCredentials:newCredentials]; + return YES; + } else { + [[self class] removeAuthenticationCredentialsFromSessionStore:newCredentials]; + } + } + return NO; +} + +- (NSMutableDictionary *)findProxyCredentials +{ + NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease]; + + NSString *user = nil; + NSString *pass = nil; + + + // If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request + if ([self mainRequest] && [[self mainRequest] proxyUsername] && [[self mainRequest] proxyPassword]) { + user = [[self mainRequest] proxyUsername]; + pass = [[self mainRequest] proxyPassword]; + + // Let's try to use the ones set in this object + } else if ([self proxyUsername] && [self proxyPassword]) { + user = [self proxyUsername]; + pass = [self proxyPassword]; + } + + + // Ok, that didn't work, let's try the keychain + // For authenticating proxies, we'll look in the keychain regardless of the value of useKeychainPersistence + if ((!user || !pass)) { + NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForProxy:[self proxyHost] port:[self proxyPort] protocol:[[self url] scheme] realm:[self proxyAuthenticationRealm]]; + if (authenticationCredentials) { + user = [authenticationCredentials user]; + pass = [authenticationCredentials password]; + } + + } + + // Handle NTLM, which requires a domain to be set too + if (CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) { + + NSString *ntlmDomain = [self proxyDomain]; + + // If we have no domain yet, let's try to extract it from the username + if (!ntlmDomain || [ntlmDomain length] == 0) { + ntlmDomain = @""; + NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"]; + if ([ntlmComponents count] == 2) { + ntlmDomain = [ntlmComponents objectAtIndex:0]; + user = [ntlmComponents objectAtIndex:1]; + } + } + [newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain]; + } + + + // If we have a username and password, let's apply them to the request and continue + if (user && pass) { + [newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername]; + [newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword]; + return newCredentials; + } + return nil; +} + + +- (NSMutableDictionary *)findCredentials +{ + NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease]; + + + // First, let's look at the url to see if the username and password were included + NSString *user = [[self url] user]; + NSString *pass = [[self url] password]; + + // If the username and password weren't in the url + if (!user || !pass) { + + // If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request + if ([self mainRequest] && [[self mainRequest] username] && [[self mainRequest] password]) { + user = [[self mainRequest] username]; + pass = [[self mainRequest] password]; + + // Let's try to use the ones set in this object + } else if ([self username] && [self password]) { + user = [self username]; + pass = [self password]; + } + + } + + // Ok, that didn't work, let's try the keychain + if ((!user || !pass) && useKeychainPersistence) { + NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]]; + if (authenticationCredentials) { + user = [authenticationCredentials user]; + pass = [authenticationCredentials password]; + } + + } + + // Handle NTLM, which requires a domain to be set too + if (CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) { + + NSString *ntlmDomain = [self domain]; + + // If we have no domain yet, let's try to extract it from the username + if (!ntlmDomain || [ntlmDomain length] == 0) { + ntlmDomain = @""; + NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"]; + if ([ntlmComponents count] == 2) { + ntlmDomain = [ntlmComponents objectAtIndex:0]; + user = [ntlmComponents objectAtIndex:1]; + } + } + [newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain]; + } + + // If we have a username and password, let's apply them to the request and continue + if (user && pass) { + [newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername]; + [newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword]; + return newCredentials; + } + return nil; +} + +// Called by delegate or authentication dialog to resume loading once authentication info has been populated +- (void)retryUsingSuppliedCredentials +{ + //If the url was changed by the delegate, our CFHTTPMessageRef will be NULL and we'll go back to the start + if (!request) { + [self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; + return; + } + [self performSelector:@selector(attemptToApplyCredentialsAndResume) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; +} + +// Called by delegate or authentication dialog to cancel authentication +- (void)cancelAuthentication +{ + [self performSelector:@selector(failAuthentication) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; +} + +- (void)failAuthentication +{ + [self failWithError:ASIAuthenticationError]; +} + +- (BOOL)showProxyAuthenticationDialog +{ +// Mac authentication dialog coming soon! +#if TARGET_OS_IPHONE + if ([self shouldPresentProxyAuthenticationDialog]) { + [ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]]; + return YES; + } + return NO; +#else + return NO; +#endif +} + + +- (BOOL)willAskDelegateForProxyCredentials +{ + // If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:. + // Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate + id authenticationDelegate = [self delegate]; + if (!authenticationDelegate) { + authenticationDelegate = [self queue]; + } + + BOOL delegateOrBlockWillHandleAuthentication = NO; + + if ([authenticationDelegate respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) { + delegateOrBlockWillHandleAuthentication = YES; + } + + #if NS_BLOCKS_AVAILABLE + if(proxyAuthenticationNeededBlock){ + delegateOrBlockWillHandleAuthentication = YES; + } + #endif + + if (delegateOrBlockWillHandleAuthentication) { + [self performSelectorOnMainThread:@selector(askDelegateForProxyCredentials) withObject:nil waitUntilDone:NO]; + } + + return delegateOrBlockWillHandleAuthentication; +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)askDelegateForProxyCredentials +{ + id authenticationDelegate = [self delegate]; + if (!authenticationDelegate) { + authenticationDelegate = [self queue]; + } + if ([authenticationDelegate respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) { + [authenticationDelegate performSelector:@selector(proxyAuthenticationNeededForRequest:) withObject:self]; + return; + } + #if NS_BLOCKS_AVAILABLE + if(proxyAuthenticationNeededBlock){ + proxyAuthenticationNeededBlock(); + } + #endif +} + + +- (BOOL)willAskDelegateForCredentials +{ + // If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:. + // Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate + id authenticationDelegate = [self delegate]; + if (!authenticationDelegate) { + authenticationDelegate = [self queue]; + } + + BOOL delegateOrBlockWillHandleAuthentication = NO; + + if ([authenticationDelegate respondsToSelector:@selector(authenticationNeededForRequest:)]) { + delegateOrBlockWillHandleAuthentication = YES; + } + + #if NS_BLOCKS_AVAILABLE + if (authenticationNeededBlock) { + delegateOrBlockWillHandleAuthentication = YES; + } + #endif + + if (delegateOrBlockWillHandleAuthentication) { + [self performSelectorOnMainThread:@selector(askDelegateForCredentials) withObject:nil waitUntilDone:NO]; + } + return delegateOrBlockWillHandleAuthentication; +} + +/* ALWAYS CALLED ON MAIN THREAD! */ +- (void)askDelegateForCredentials +{ + id authenticationDelegate = [self delegate]; + if (!authenticationDelegate) { + authenticationDelegate = [self queue]; + } + + if ([authenticationDelegate respondsToSelector:@selector(authenticationNeededForRequest:)]) { + [authenticationDelegate performSelector:@selector(authenticationNeededForRequest:) withObject:self]; + return; + } + + #if NS_BLOCKS_AVAILABLE + if (authenticationNeededBlock) { + authenticationNeededBlock(); + } + #endif +} + +- (void)attemptToApplyProxyCredentialsAndResume +{ + + if ([self error] || [self isCancelled]) { + return; + } + + // Read authentication data + if (!proxyAuthentication) { + CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty((CFReadStreamRef)[self readStream],kCFStreamPropertyHTTPResponseHeader); + proxyAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader); + CFRelease(responseHeader); + [self setProxyAuthenticationScheme:[(NSString *)CFHTTPAuthenticationCopyMethod(proxyAuthentication) autorelease]]; + } + + // If we haven't got a CFHTTPAuthenticationRef by now, something is badly wrong, so we'll have to give up + if (!proxyAuthentication) { + [self cancelLoad]; + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]]; + return; + } + + // Get the authentication realm + [self setProxyAuthenticationRealm:nil]; + if (!CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) { + [self setProxyAuthenticationRealm:[(NSString *)CFHTTPAuthenticationCopyRealm(proxyAuthentication) autorelease]]; + } + + // See if authentication is valid + CFStreamError err; + if (!CFHTTPAuthenticationIsValid(proxyAuthentication, &err)) { + + CFRelease(proxyAuthentication); + proxyAuthentication = NULL; + + // check for bad credentials, so we can give the delegate a chance to replace them + if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) { + + // Prevent more than one request from asking for credentials at once + [delegateAuthenticationLock lock]; + + // We know the credentials we just presented are bad, we should remove them from the session store too + [[self class] removeProxyAuthenticationCredentialsFromSessionStore:proxyCredentials]; + [self setProxyCredentials:nil]; + + + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us + if ([self error] || [self isCancelled]) { + [delegateAuthenticationLock unlock]; + return; + } + + + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request + if ([self useSessionPersistence]) { + NSDictionary *credentials = [self findSessionProxyAuthenticationCredentials]; + if (credentials && [self applyProxyCredentials:[credentials objectForKey:@"Credentials"]]) { + [delegateAuthenticationLock unlock]; + [self startRequest]; + return; + } + } + + [self setLastActivityTime:nil]; + + if ([self willAskDelegateForProxyCredentials]) { + [self attemptToApplyProxyCredentialsAndResume]; + [delegateAuthenticationLock unlock]; + return; + } + if ([self showProxyAuthenticationDialog]) { + [self attemptToApplyProxyCredentialsAndResume]; + [delegateAuthenticationLock unlock]; + return; + } + [delegateAuthenticationLock unlock]; + } + [self cancelLoad]; + [self failWithError:ASIAuthenticationError]; + return; + } + + [self cancelLoad]; + + if (proxyCredentials) { + + // We use startRequest rather than starting all over again in load request because NTLM requires we reuse the request + if ((([self proxyAuthenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || [self proxyAuthenticationRetryCount] < 2) && [self applyProxyCredentials:proxyCredentials]) { + [self startRequest]; + + // We've failed NTLM authentication twice, we should assume our credentials are wrong + } else if ([self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self proxyAuthenticationRetryCount] == 2) { + [self failWithError:ASIAuthenticationError]; + + // Something went wrong, we'll have to give up + } else { + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply proxy credentials to request",NSLocalizedDescriptionKey,nil]]]; + } + + // Are a user name & password needed? + } else if (CFHTTPAuthenticationRequiresUserNameAndPassword(proxyAuthentication)) { + + // Prevent more than one request from asking for credentials at once + [delegateAuthenticationLock lock]; + + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us + if ([self error] || [self isCancelled]) { + [delegateAuthenticationLock unlock]; + return; + } + + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request + if ([self useSessionPersistence]) { + NSDictionary *credentials = [self findSessionProxyAuthenticationCredentials]; + if (credentials && [self applyProxyCredentials:[credentials objectForKey:@"Credentials"]]) { + [delegateAuthenticationLock unlock]; + [self startRequest]; + return; + } + } + + NSMutableDictionary *newCredentials = [self findProxyCredentials]; + + //If we have some credentials to use let's apply them to the request and continue + if (newCredentials) { + + if ([self applyProxyCredentials:newCredentials]) { + [delegateAuthenticationLock unlock]; + [self startRequest]; + } else { + [delegateAuthenticationLock unlock]; + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply proxy credentials to request",NSLocalizedDescriptionKey,nil]]]; + } + + return; + } + + if ([self willAskDelegateForProxyCredentials]) { + [delegateAuthenticationLock unlock]; + return; + } + + if ([self showProxyAuthenticationDialog]) { + [delegateAuthenticationLock unlock]; + return; + } + [delegateAuthenticationLock unlock]; + + // The delegate isn't interested and we aren't showing the authentication dialog, we'll have to give up + [self failWithError:ASIAuthenticationError]; + return; + } + +} + +- (BOOL)showAuthenticationDialog +{ +// Mac authentication dialog coming soon! +#if TARGET_OS_IPHONE + if ([self shouldPresentAuthenticationDialog]) { + [ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]]; + return YES; + } + return NO; +#else + return NO; +#endif +} + + + +- (void)attemptToApplyCredentialsAndResume +{ + if ([self error] || [self isCancelled]) { + return; + } + + if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) { + [self attemptToApplyProxyCredentialsAndResume]; + return; + } + + // Read authentication data + if (!requestAuthentication) { + CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty((CFReadStreamRef)[self readStream],kCFStreamPropertyHTTPResponseHeader); + requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader); + CFRelease(responseHeader); + [self setAuthenticationScheme:[(NSString *)CFHTTPAuthenticationCopyMethod(requestAuthentication) autorelease]]; + } + + if (!requestAuthentication) { + [self cancelLoad]; + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]]; + return; + } + + // Get the authentication realm + [self setAuthenticationRealm:nil]; + if (!CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) { + [self setAuthenticationRealm:[(NSString *)CFHTTPAuthenticationCopyRealm(requestAuthentication) autorelease]]; + } + + // See if authentication is valid + CFStreamError err; + if (!CFHTTPAuthenticationIsValid(requestAuthentication, &err)) { + + CFRelease(requestAuthentication); + requestAuthentication = NULL; + + // check for bad credentials, so we can give the delegate a chance to replace them + if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) { + + // Prevent more than one request from asking for credentials at once + [delegateAuthenticationLock lock]; + + // We know the credentials we just presented are bad, we should remove them from the session store too + [[self class] removeAuthenticationCredentialsFromSessionStore:requestCredentials]; + [self setRequestCredentials:nil]; + + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us + if ([self error] || [self isCancelled]) { + [delegateAuthenticationLock unlock]; + return; + } + + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request + if ([self useSessionPersistence]) { + NSDictionary *credentials = [self findSessionAuthenticationCredentials]; + if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) { + [delegateAuthenticationLock unlock]; + [self startRequest]; + return; + } + } + + + + [self setLastActivityTime:nil]; + + if ([self willAskDelegateForCredentials]) { + [delegateAuthenticationLock unlock]; + return; + } + if ([self showAuthenticationDialog]) { + [delegateAuthenticationLock unlock]; + return; + } + [delegateAuthenticationLock unlock]; + } + [self cancelLoad]; + [self failWithError:ASIAuthenticationError]; + return; + } + + [self cancelLoad]; + + if (requestCredentials) { + + if ((([self authenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || [self authenticationRetryCount] < 2) && [self applyCredentials:requestCredentials]) { + [self startRequest]; + + // We've failed NTLM authentication twice, we should assume our credentials are wrong + } else if ([self authenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self authenticationRetryCount ] == 2) { + [self failWithError:ASIAuthenticationError]; + + } else { + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]]; + } + + // Are a user name & password needed? + } else if (CFHTTPAuthenticationRequiresUserNameAndPassword(requestAuthentication)) { + + // Prevent more than one request from asking for credentials at once + [delegateAuthenticationLock lock]; + + // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us + if ([self error] || [self isCancelled]) { + [delegateAuthenticationLock unlock]; + return; + } + + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request + if ([self useSessionPersistence]) { + NSDictionary *credentials = [self findSessionAuthenticationCredentials]; + if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) { + [delegateAuthenticationLock unlock]; + [self startRequest]; + return; + } + } + + + NSMutableDictionary *newCredentials = [self findCredentials]; + + //If we have some credentials to use let's apply them to the request and continue + if (newCredentials) { + + if ([self applyCredentials:newCredentials]) { + [delegateAuthenticationLock unlock]; + [self startRequest]; + } else { + [delegateAuthenticationLock unlock]; + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]]; + } + return; + } + if ([self willAskDelegateForCredentials]) { + [delegateAuthenticationLock unlock]; + return; + } + + if ([self showAuthenticationDialog]) { + [delegateAuthenticationLock unlock]; + return; + } + [delegateAuthenticationLock unlock]; + + [self failWithError:ASIAuthenticationError]; + + return; + } + +} + +- (void)addBasicAuthenticationHeaderWithUsername:(NSString *)theUsername andPassword:(NSString *)thePassword +{ + [self addRequestHeader:@"Authorization" value:[NSString stringWithFormat:@"Basic %@",[ASIHTTPRequest base64forData:[[NSString stringWithFormat:@"%@:%@",theUsername,thePassword] dataUsingEncoding:NSUTF8StringEncoding]]]]; +} + + +#pragma mark stream status handlers + +- (void)handleNetworkEvent:(CFStreamEventType)type +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [[self cancelledLock] lock]; + + if ([self complete] || [self isCancelled]) { + [[self cancelledLock] unlock]; + [pool release]; + return; + } + + CFRetain(self); + + // Dispatch the stream events. + switch (type) { + case kCFStreamEventHasBytesAvailable: + [self handleBytesAvailable]; + break; + + case kCFStreamEventEndEncountered: + [self handleStreamComplete]; + break; + + case kCFStreamEventErrorOccurred: + [self handleStreamError]; + break; + + default: + break; + } + + [self performThrottling]; + + [[self cancelledLock] unlock]; + + if ([self downloadComplete] && [self needsRedirect]) { + + // We must lock again to ensure delegate / queue aren't changed while we check them + [[self cancelledLock] lock]; + // Here we perform an initial check to see if either the delegate or the queue wants to be asked about the redirect, because if not we should redirect straight away + // We will check again on the main thread later + BOOL needToAskDelegateAboutRedirect = (([self delegate] && [[self delegate] respondsToSelector:[self willRedirectSelector]]) || ([self queue] && [[self queue] respondsToSelector:@selector(request:willRedirectToURL:)])); + [[self cancelledLock] unlock]; + + // Either the delegate or the queue's delegate is interested in being told when we are about to redirect + if (needToAskDelegateAboutRedirect) { + NSURL *newURL = [[[self redirectURL] copy] autorelease]; + [self setRedirectURL:nil]; + [self performSelectorOnMainThread:@selector(requestWillRedirectToURL:) withObject:newURL waitUntilDone:[NSThread isMainThread]]; + + // If neither the delegate nor the queue's delegate implement request:willRedirectToURL:, we will redirect automatically + } else { + [self performRedirect]; + } + } else if ([self downloadComplete] && [self authenticationNeeded]) { + [self attemptToApplyCredentialsAndResume]; + } + + CFRelease(self); + [pool release]; +} + +- (void)handleBytesAvailable +{ + if (![self responseHeaders]) { + [self readResponseHeaders]; + } + + // If we've cancelled the load part way through (for example, after deciding to use a cached version) + if ([self complete]) { + return; + } + + // In certain (presumably very rare) circumstances, handleBytesAvailable seems to be called when there isn't actually any data available + // We'll check that there is actually data available to prevent blocking on CFReadStreamRead() + // So far, I've only seen this in the stress tests, so it might never happen in real-world situations. + if (!CFReadStreamHasBytesAvailable((CFReadStreamRef)[self readStream])) { + return; + } + + long long bufferSize = 16384; + if (contentLength > 262144) { + bufferSize = 262144; + } else if (contentLength > 65536) { + bufferSize = 65536; + } + + // Reduce the buffer size if we're receiving data too quickly when bandwidth throttling is active + // This just augments the throttling done in measureBandwidthUsage to reduce the amount we go over the limit + + if ([[self class] isBandwidthThrottled]) { + [bandwidthThrottlingLock lock]; + if (maxBandwidthPerSecond > 0) { + long long maxiumumSize = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond; + if (maxiumumSize < 0) { + // We aren't supposed to read any more data right now, but we'll read a single byte anyway so the CFNetwork's buffer isn't full + bufferSize = 1; + } else if (maxiumumSize/4 < bufferSize) { + // We were going to fetch more data that we should be allowed, so we'll reduce the size of our read + bufferSize = maxiumumSize/4; + } + } + if (bufferSize < 1) { + bufferSize = 1; + } + [bandwidthThrottlingLock unlock]; + } + + + UInt8 buffer[bufferSize]; + NSInteger bytesRead = [[self readStream] read:buffer maxLength:sizeof(buffer)]; + + // Less than zero is an error + if (bytesRead < 0) { + [self handleStreamError]; + + // If zero bytes were read, wait for the EOF to come. + } else if (bytesRead) { + + // If we are inflating the response on the fly + NSData *inflatedData = nil; + if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) { + if (![self dataDecompressor]) { + [self setDataDecompressor:[ASIDataDecompressor decompressor]]; + } + NSError *err = nil; + inflatedData = [[self dataDecompressor] uncompressBytes:buffer length:bytesRead error:&err]; + if (err) { + [self failWithError:err]; + return; + } + } + + [self setTotalBytesRead:[self totalBytesRead]+bytesRead]; + [self setLastActivityTime:[NSDate date]]; + + // For bandwidth measurement / throttling + [ASIHTTPRequest incrementBandwidthUsedInLastSecond:bytesRead]; + + // If we need to redirect, and have automatic redirect on, and might be resuming a download, let's do nothing with the content + if ([self needsRedirect] && [self shouldRedirect] && [self allowResumeForFileDownloads]) { + return; + } + + BOOL dataWillBeHandledExternally = NO; + if ([[self delegate] respondsToSelector:[self didReceiveDataSelector]]) { + dataWillBeHandledExternally = YES; + } + #if NS_BLOCKS_AVAILABLE + if (dataReceivedBlock) { + dataWillBeHandledExternally = YES; + } + #endif + // Does the delegate want to handle the data manually? + if (dataWillBeHandledExternally) { + + NSData *data = nil; + if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) { + data = inflatedData; + } else { + data = [NSData dataWithBytes:buffer length:bytesRead]; + } + [self performSelectorOnMainThread:@selector(passOnReceivedData:) withObject:data waitUntilDone:[NSThread isMainThread]]; + + // Are we downloading to a file? + } else if ([self downloadDestinationPath]) { + BOOL append = NO; + if (![self fileDownloadOutputStream]) { + if (![self temporaryFileDownloadPath]) { + [self setTemporaryFileDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; + } else if ([self allowResumeForFileDownloads] && [[self requestHeaders] objectForKey:@"Range"]) { + if ([[self responseHeaders] objectForKey:@"Content-Range"]) { + append = YES; + } else { + [self incrementDownloadSizeBy:-[self partialDownloadSize]]; + [self setPartialDownloadSize:0]; + } + } + + [self setFileDownloadOutputStream:[[[NSOutputStream alloc] initToFileAtPath:[self temporaryFileDownloadPath] append:append] autorelease]]; + [[self fileDownloadOutputStream] open]; + + } + [[self fileDownloadOutputStream] write:buffer maxLength:bytesRead]; + + if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) { + + if (![self inflatedFileDownloadOutputStream]) { + if (![self temporaryUncompressedDataDownloadPath]) { + [self setTemporaryUncompressedDataDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; + } + + [self setInflatedFileDownloadOutputStream:[[[NSOutputStream alloc] initToFileAtPath:[self temporaryUncompressedDataDownloadPath] append:append] autorelease]]; + [[self inflatedFileDownloadOutputStream] open]; + } + + [[self inflatedFileDownloadOutputStream] write:[inflatedData bytes] maxLength:[inflatedData length]]; + } + + + //Otherwise, let's add the data to our in-memory store + } else { + if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) { + [rawResponseData appendData:inflatedData]; + } else { + [rawResponseData appendBytes:buffer length:bytesRead]; + } + } + } +} + +- (void)handleStreamComplete +{ + +#if DEBUG_REQUEST_STATUS + NSLog(@"Request %@ finished downloading data (%qu bytes)",self, [self totalBytesRead]); +#endif + [self setStatusTimer:nil]; + [self setDownloadComplete:YES]; + + if (![self responseHeaders]) { + [self readResponseHeaders]; + } + + [progressLock lock]; + // Find out how much data we've uploaded so far + [self setLastBytesSent:totalBytesSent]; + [self setTotalBytesSent:[NSMakeCollectable([(NSNumber *)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease]) unsignedLongLongValue]]; + [self setComplete:YES]; + if (![self contentLength]) { + [self setContentLength:[self totalBytesRead]]; + } + [self updateProgressIndicators]; + + + [[self postBodyReadStream] close]; + [self setPostBodyReadStream:nil]; + + [self setDataDecompressor:nil]; + + NSError *fileError = nil; + + // Delete up the request body temporary file, if it exists + if ([self didCreateTemporaryPostDataFile] && ![self authenticationNeeded]) { + [self removeTemporaryUploadFile]; + [self removeTemporaryCompressedUploadFile]; + } + + // Close the output stream as we're done writing to the file + if ([self temporaryFileDownloadPath]) { + + [[self fileDownloadOutputStream] close]; + [self setFileDownloadOutputStream:nil]; + + [[self inflatedFileDownloadOutputStream] close]; + [self setInflatedFileDownloadOutputStream:nil]; + + // If we are going to redirect and we are resuming, let's ignore this download + if ([self shouldRedirect] && [self needsRedirect] && [self allowResumeForFileDownloads]) { + + } else if ([self isResponseCompressed]) { + + // Decompress the file directly to the destination path + if ([self shouldWaitToInflateCompressedResponses]) { + [ASIDataDecompressor uncompressDataFromFile:[self temporaryFileDownloadPath] toFile:[self downloadDestinationPath] error:&fileError]; + + // Response should already have been inflated, move the temporary file to the destination path + } else { + NSError *moveError = nil; + [[[[NSFileManager alloc] init] autorelease] moveItemAtPath:[self temporaryUncompressedDataDownloadPath] toPath:[self downloadDestinationPath] error:&moveError]; + if (moveError) { + fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to move file from '%@' to '%@'",[self temporaryFileDownloadPath],[self downloadDestinationPath]],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]]; + } + [self setTemporaryUncompressedDataDownloadPath:nil]; + + } + [self removeTemporaryDownloadFile]; + + } else { + + //Remove any file at the destination path + NSError *moveError = nil; + if (![[self class] removeFileAtPath:[self downloadDestinationPath] error:&moveError]) { + fileError = moveError; + + } + + //Move the temporary file to the destination path + if (!fileError) { + [[[[NSFileManager alloc] init] autorelease] moveItemAtPath:[self temporaryFileDownloadPath] toPath:[self downloadDestinationPath] error:&moveError]; + if (moveError) { + fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to move file from '%@' to '%@'",[self temporaryFileDownloadPath],[self downloadDestinationPath]],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]]; + } + [self setTemporaryFileDownloadPath:nil]; + } + + } + } + + // Save to the cache + if ([self downloadCache] && ![self didUseCachedResponse]) { + [[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]]; + } + + [progressLock unlock]; + + + [connectionsLock lock]; + if (![self connectionCanBeReused]) { + [self unscheduleReadStream]; + } + #if DEBUG_PERSISTENT_CONNECTIONS + NSLog(@"Request #%@ finished using connection #%@",[self requestID], [[self connectionInfo] objectForKey:@"id"]); + #endif + [[self connectionInfo] removeObjectForKey:@"request"]; + [[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:[self persistentConnectionTimeoutSeconds]] forKey:@"expires"]; + [connectionsLock unlock]; + + if (![self authenticationNeeded]) { + [self destroyReadStream]; + } + + + if (![self needsRedirect] && ![self authenticationNeeded] && ![self didUseCachedResponse]) { + + if (fileError) { + [self failWithError:fileError]; + } else { + [self requestFinished]; + } + + [self markAsFinished]; + + // If request has asked delegate or ASIAuthenticationDialog for credentials + } else if ([self authenticationNeeded]) { + CFRunLoopStop(CFRunLoopGetCurrent()); + } + +} + +- (void)markAsFinished +{ + // Autoreleased requests may well be dealloced here otherwise + CFRetain(self); + + // dealloc won't be called when running with GC, so we'll clean these up now + if (request) { + CFMakeCollectable(request); + } + if (requestAuthentication) { + CFMakeCollectable(requestAuthentication); + } + if (proxyAuthentication) { + CFMakeCollectable(proxyAuthentication); + } + + BOOL wasInProgress = inProgress; + BOOL wasFinished = finished; + + if (!wasFinished) + [self willChangeValueForKey:@"isFinished"]; + if (wasInProgress) + [self willChangeValueForKey:@"isExecuting"]; + + [self setInProgress:NO]; + finished = YES; + + if (wasInProgress) + [self didChangeValueForKey:@"isExecuting"]; + if (!wasFinished) + [self didChangeValueForKey:@"isFinished"]; + + CFRunLoopStop(CFRunLoopGetCurrent()); + + #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 + if ([ASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (backgroundTask != UIBackgroundTaskInvalid) { + [[UIApplication sharedApplication] endBackgroundTask:backgroundTask]; + backgroundTask = UIBackgroundTaskInvalid; + } + }); + } + #endif + CFRelease(self); +} + +- (void)useDataFromCache +{ + NSDictionary *headers = [[self downloadCache] cachedResponseHeadersForURL:[self url]]; + NSString *dataPath = [[self downloadCache] pathToCachedResponseDataForURL:[self url]]; + + ASIHTTPRequest *theRequest = self; + if ([self mainRequest]) { + theRequest = [self mainRequest]; + } + + if (headers && dataPath) { + + // only 200 responses are stored in the cache, so let the client know + // this was a successful response + [self setResponseStatusCode:200]; + + [self setDidUseCachedResponse:YES]; + + [theRequest setResponseHeaders:headers]; + if ([theRequest downloadDestinationPath]) { + [theRequest setDownloadDestinationPath:dataPath]; + } else { + [theRequest setRawResponseData:[NSMutableData dataWithData:[[self downloadCache] cachedResponseDataForURL:[self url]]]]; + } + [theRequest setContentLength:[[[self responseHeaders] objectForKey:@"Content-Length"] longLongValue]]; + [theRequest setTotalBytesRead:[self contentLength]]; + + [theRequest parseStringEncodingFromHeaders]; + + [theRequest setResponseCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:[self url]]]; + } + [theRequest setComplete:YES]; + [theRequest setDownloadComplete:YES]; + + // If we're pulling data from the cache without contacting the server at all, we won't have set originalURL yet + if ([self redirectCount] == 0) { + [theRequest setOriginalURL:[theRequest url]]; + } + + [theRequest updateProgressIndicators]; + [theRequest requestFinished]; + [theRequest markAsFinished]; + if ([self mainRequest]) { + [self markAsFinished]; + } +} + +- (BOOL)retryUsingNewConnection +{ + if ([self retryCount] == 0) { + #if DEBUG_PERSISTENT_CONNECTIONS + NSLog(@"Request attempted to use connection #%@, but it has been closed - will retry with a new connection", [[self connectionInfo] objectForKey:@"id"]); + #endif + [connectionsLock lock]; + [[self connectionInfo] removeObjectForKey:@"request"]; + [persistentConnectionsPool removeObject:[self connectionInfo]]; + [self setConnectionInfo:nil]; + [connectionsLock unlock]; + [self setRetryCount:[self retryCount]+1]; + [self startRequest]; + return YES; + } + #if DEBUG_PERSISTENT_CONNECTIONS + NSLog(@"Request attempted to use connection #%@, but it has been closed - we have already retried with a new connection, so we must give up", [[self connectionInfo] objectForKey:@"id"]); + #endif + return NO; +} + +- (void)handleStreamError + +{ + NSError *underlyingError = NSMakeCollectable([(NSError *)CFReadStreamCopyError((CFReadStreamRef)[self readStream]) autorelease]); + + [self cancelLoad]; + + if (![self error]) { // We may already have handled this error + + // First, check for a 'socket not connected', 'broken pipe' or 'connection lost' error + // This may occur when we've attempted to reuse a connection that should have been closed + // If we get this, we need to retry the request + // We'll only do this once - if it happens again on retry, we'll give up + // -1005 = kCFURLErrorNetworkConnectionLost - this doesn't seem to be declared on Mac OS 10.5 + if (([[underlyingError domain] isEqualToString:NSPOSIXErrorDomain] && ([underlyingError code] == ENOTCONN || [underlyingError code] == EPIPE)) + || ([[underlyingError domain] isEqualToString:(NSString *)kCFErrorDomainCFNetwork] && [underlyingError code] == -1005)) { + if ([self retryUsingNewConnection]) { + return; + } + } + + NSString *reason = @"A connection failure occurred"; + + // We'll use a custom error message for SSL errors, but you should always check underlying error if you want more details + // For some reason SecureTransport.h doesn't seem to be available on iphone, so error codes hard-coded + // Also, iPhone seems to handle errors differently from Mac OS X - a self-signed certificate returns a different error code on each platform, so we'll just provide a general error + if ([[underlyingError domain] isEqualToString:NSOSStatusErrorDomain]) { + if ([underlyingError code] <= -9800 && [underlyingError code] >= -9818) { + reason = [NSString stringWithFormat:@"%@: SSL problem (possibly a bad/expired/self-signed certificate)",reason]; + } + } + + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIConnectionFailureErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:reason,NSLocalizedDescriptionKey,underlyingError,NSUnderlyingErrorKey,nil]]]; + } + [self checkRequestStatus]; +} + +#pragma mark managing the read stream + +- (void)destroyReadStream +{ + if ([self readStream]) { + [self unscheduleReadStream]; + if (![self connectionCanBeReused]) { + [[self readStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]]; + [[self readStream] close]; + } + [self setReadStream:nil]; + } +} + +- (void)scheduleReadStream +{ + if ([self readStream] && ![self readStreamIsScheduled]) { + + [connectionsLock lock]; + runningRequestCount++; + if (shouldUpdateNetworkActivityIndicator) { + [[self class] showNetworkActivityIndicator]; + } + [connectionsLock unlock]; + + // Reset the timeout + [self setLastActivityTime:[NSDate date]]; + CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL}; + CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt); + [[self readStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]]; + [self setReadStreamIsScheduled:YES]; + } +} + + +- (void)unscheduleReadStream +{ + if ([self readStream] && [self readStreamIsScheduled]) { + + [connectionsLock lock]; + runningRequestCount--; + if (shouldUpdateNetworkActivityIndicator && runningRequestCount == 0) { + // This call will wait half a second before turning off the indicator + // This can prevent flicker when you have a single request finish and then immediately start another request + // We run this on the main thread because we have no guarantee this thread will have a runloop in 0.5 seconds time + // We don't bother the cancel this call if we start a new request, because we'll check if requests are running before we hide it + [[self class] performSelectorOnMainThread:@selector(hideNetworkActivityIndicatorAfterDelay) withObject:nil waitUntilDone:[NSThread isMainThread]]; + } + [connectionsLock unlock]; + + CFReadStreamSetClient((CFReadStreamRef)[self readStream], kCFStreamEventNone, NULL, NULL); + [[self readStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]]; + [self setReadStreamIsScheduled:NO]; + } +} + +#pragma mark cleanup + +- (BOOL)removeTemporaryDownloadFile +{ + NSError *err = nil; + if ([self temporaryFileDownloadPath]) { + if (![[self class] removeFileAtPath:[self temporaryFileDownloadPath] error:&err]) { + [self failWithError:err]; + } + [self setTemporaryFileDownloadPath:nil]; + } + return (!err); +} + +- (BOOL)removeTemporaryUncompressedDownloadFile +{ + NSError *err = nil; + if ([self temporaryUncompressedDataDownloadPath]) { + if (![[self class] removeFileAtPath:[self temporaryUncompressedDataDownloadPath] error:&err]) { + [self failWithError:err]; + } + [self setTemporaryUncompressedDataDownloadPath:nil]; + } + return (!err); +} + +- (BOOL)removeTemporaryUploadFile +{ + NSError *err = nil; + if ([self postBodyFilePath]) { + if (![[self class] removeFileAtPath:[self postBodyFilePath] error:&err]) { + [self failWithError:err]; + } + [self setPostBodyFilePath:nil]; + } + return (!err); +} + +- (BOOL)removeTemporaryCompressedUploadFile +{ + NSError *err = nil; + if ([self compressedPostBodyFilePath]) { + if (![[self class] removeFileAtPath:[self compressedPostBodyFilePath] error:&err]) { + [self failWithError:err]; + } + [self setCompressedPostBodyFilePath:nil]; + } + return (!err); +} + ++ (BOOL)removeFileAtPath:(NSString *)path error:(NSError **)err +{ + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + + if ([fileManager fileExistsAtPath:path]) { + NSError *removeError = nil; + [fileManager removeItemAtPath:path error:&removeError]; + if (removeError) { + if (err) { + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at path '%@'",path],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]]; + } + return NO; + } + } + return YES; +} + +#pragma mark Proxies + +- (BOOL)configureProxies +{ + // Have details of the proxy been set on this request + if (![self isPACFileRequest] && (![self proxyHost] && ![self proxyPort])) { + + // If not, we need to figure out what they'll be + NSArray *proxies = nil; + + // Have we been given a proxy auto config file? + if ([self PACurl]) { + + // If yes, we'll need to fetch the PAC file asynchronously, so we stop this request to wait until we have the proxy details. + [self fetchPACFile]; + return NO; + + // Detect proxy settings and apply them + } else { + +#if TARGET_OS_IPHONE + NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)CFNetworkCopySystemProxySettings() autorelease]); +#else + NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)SCDynamicStoreCopyProxies(NULL) autorelease]); +#endif + + proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForURL((CFURLRef)[self url], (CFDictionaryRef)proxySettings) autorelease]); + + // Now check to see if the proxy settings contained a PAC url, we need to run the script to get the real list of proxies if so + NSDictionary *settings = [proxies objectAtIndex:0]; + if ([settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]) { + [self setPACurl:[settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]]; + [self fetchPACFile]; + return NO; + } + } + + if (!proxies) { + [self setReadStream:nil]; + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to obtain information on proxy servers needed for request",NSLocalizedDescriptionKey,nil]]]; + return NO; + } + // I don't really understand why the dictionary returned by CFNetworkCopyProxiesForURL uses different key names from CFNetworkCopySystemProxySettings/SCDynamicStoreCopyProxies + // and why its key names are documented while those we actually need to use don't seem to be (passing the kCF* keys doesn't seem to work) + if ([proxies count] > 0) { + NSDictionary *settings = [proxies objectAtIndex:0]; + [self setProxyHost:[settings objectForKey:(NSString *)kCFProxyHostNameKey]]; + [self setProxyPort:[[settings objectForKey:(NSString *)kCFProxyPortNumberKey] intValue]]; + [self setProxyType:[settings objectForKey:(NSString *)kCFProxyTypeKey]]; + } + } + return YES; +} + + + +// Attempts to download a PAC (Proxy Auto-Configuration) file +// PAC files at file://, http:// and https:// addresses are supported +- (void)fetchPACFile +{ + // For file:// urls, we'll use an async NSInputStream (ASIHTTPRequest does not support file:// urls) + if ([[self PACurl] isFileURL]) { + NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:[[self PACurl] path]] autorelease]; + [self setPACFileReadStream:stream]; + [stream setDelegate:(id)self]; + [stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]]; + [stream open]; + // If it takes more than timeOutSeconds to read the PAC, we'll just give up and assume no proxies + // We won't bother to handle cases where the first part of the PAC is read within timeOutSeconds, but the whole thing takes longer + // Either our PAC file is in easy reach, or it's going to slow things down to the point that it's probably better requests fail + [self performSelector:@selector(timeOutPACRead) withObject:nil afterDelay:[self timeOutSeconds]]; + return; + } + + NSString *scheme = [[[self PACurl] scheme] lowercaseString]; + if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) { + // Don't know how to read data from this URL, we'll have to give up + // We'll simply assume no proxies, and start the request as normal + [self startRequest]; + return; + } + + // Create an ASIHTTPRequest to fetch the PAC file + ASIHTTPRequest *PACRequest = [ASIHTTPRequest requestWithURL:[self PACurl]]; + + // Will prevent this request attempting to configure proxy settings for itself + [PACRequest setIsPACFileRequest:YES]; + + [PACRequest setTimeOutSeconds:[self timeOutSeconds]]; + + // If we're a synchronous request, we'll download the PAC file synchronously + if ([self isSynchronous]) { + [PACRequest startSynchronous]; + if (![PACRequest error] && [PACRequest responseString]) { + [self runPACScript:[PACRequest responseString]]; + } + [self startRequest]; + return; + } + + [self setPACFileRequest:PACRequest]; + + // Force this request to run before others in the shared queue + [PACRequest setQueuePriority:NSOperationQueuePriorityHigh]; + + // We'll treat failure to download the PAC file the same as success - if we were unable to fetch a PAC file, we proceed as if we have no proxy server and let this request fail itself if necessary + [PACRequest setDelegate:self]; + [PACRequest setDidFinishSelector:@selector(finishedDownloadingPACFile:)]; + [PACRequest setDidFailSelector:@selector(finishedDownloadingPACFile:)]; + [PACRequest startAsynchronous]; + + // Temporarily increase the number of operations in the shared queue to give our request a chance to run + [connectionsLock lock]; + [sharedQueue setMaxConcurrentOperationCount:[sharedQueue maxConcurrentOperationCount]+1]; + [connectionsLock unlock]; +} + +// Called as we read the PAC file from a file:// url +- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode +{ + if (![self PACFileReadStream]) { + return; + } + if (eventCode == NSStreamEventHasBytesAvailable) { + + if (![self PACFileData]) { + [self setPACFileData:[NSMutableData data]]; + } + // If your PAC file is larger than 16KB, you're just being cruel. + uint8_t buf[16384]; + NSInteger len = [(NSInputStream *)stream read:buf maxLength:16384]; + if (len) { + [[self PACFileData] appendBytes:(const void *)buf length:len]; + } + + } else if (eventCode == NSStreamEventErrorOccurred || eventCode == NSStreamEventEndEncountered) { + + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeOutPACRead) object:nil]; + + [stream close]; + [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]]; + [self setPACFileReadStream:nil]; + + if (eventCode == NSStreamEventEndEncountered) { + // It sounds as though we have no idea what encoding a PAC file will use + static NSStringEncoding encodingsToTry[2] = {NSUTF8StringEncoding,NSISOLatin1StringEncoding}; + NSUInteger i; + for (i=0; i<2; i++) { + NSString *pacScript = [[[NSString alloc] initWithBytes:[[self PACFileData] bytes] length:[[self PACFileData] length] encoding:encodingsToTry[i]] autorelease]; + if (pacScript) { + [self runPACScript:pacScript]; + break; + } + } + } + [self setPACFileData:nil]; + [self startRequest]; + } +} + +// Called if it takes longer than timeOutSeconds to read the whole PAC file (when reading from a file:// url) +- (void)timeOutPACRead +{ + [self stream:[self PACFileReadStream] handleEvent:NSStreamEventErrorOccurred]; +} + +// Runs the downloaded PAC script +- (void)runPACScript:(NSString *)script +{ + if (script) { + // From: http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html + // Work around . This dummy call to + // CFNetworkCopyProxiesForURL initialise some state within CFNetwork + // that is required by CFNetworkCopyProxiesForAutoConfigurationScript. + CFRelease(CFNetworkCopyProxiesForURL((CFURLRef)[self url], NULL)); + + // Obtain the list of proxies by running the autoconfiguration script + CFErrorRef err = NULL; + NSArray *proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForAutoConfigurationScript((CFStringRef)script,(CFURLRef)[self url], &err) autorelease]); + if (!err && [proxies count] > 0) { + NSDictionary *settings = [proxies objectAtIndex:0]; + [self setProxyHost:[settings objectForKey:(NSString *)kCFProxyHostNameKey]]; + [self setProxyPort:[[settings objectForKey:(NSString *)kCFProxyPortNumberKey] intValue]]; + [self setProxyType:[settings objectForKey:(NSString *)kCFProxyTypeKey]]; + } + } +} + +// Called if we successfully downloaded a PAC file from a webserver +- (void)finishedDownloadingPACFile:(ASIHTTPRequest *)theRequest +{ + if (![theRequest error] && [theRequest responseString]) { + [self runPACScript:[theRequest responseString]]; + } + + // Set the shared queue's maxConcurrentOperationCount back to normal + [connectionsLock lock]; + [sharedQueue setMaxConcurrentOperationCount:[sharedQueue maxConcurrentOperationCount]-1]; + [connectionsLock unlock]; + + // We no longer need our PAC file request + [self setPACFileRequest:nil]; + + // Start the request + [self startRequest]; +} + + +#pragma mark persistent connections + +- (NSNumber *)connectionID +{ + return [[self connectionInfo] objectForKey:@"id"]; +} + ++ (void)expirePersistentConnections +{ + [connectionsLock lock]; + NSUInteger i; + for (i=0; i<[persistentConnectionsPool count]; i++) { + NSDictionary *existingConnection = [persistentConnectionsPool objectAtIndex:i]; + if (![existingConnection objectForKey:@"request"] && [[existingConnection objectForKey:@"expires"] timeIntervalSinceNow] <= 0) { +#if DEBUG_PERSISTENT_CONNECTIONS + NSLog(@"Closing connection #%i because it has expired",[[existingConnection objectForKey:@"id"] intValue]); +#endif + NSInputStream *stream = [existingConnection objectForKey:@"stream"]; + if (stream) { + [stream close]; + } + [persistentConnectionsPool removeObject:existingConnection]; + i--; + } + } + [connectionsLock unlock]; +} + +#pragma mark NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + // Don't forget - this will return a retained copy! + ASIHTTPRequest *newRequest = [[[self class] alloc] initWithURL:[self url]]; + [newRequest setDelegate:[self delegate]]; + [newRequest setRequestMethod:[self requestMethod]]; + [newRequest setPostBody:[self postBody]]; + [newRequest setShouldStreamPostDataFromDisk:[self shouldStreamPostDataFromDisk]]; + [newRequest setPostBodyFilePath:[self postBodyFilePath]]; + [newRequest setRequestHeaders:[[[self requestHeaders] mutableCopyWithZone:zone] autorelease]]; + [newRequest setRequestCookies:[[[self requestCookies] mutableCopyWithZone:zone] autorelease]]; + [newRequest setUseCookiePersistence:[self useCookiePersistence]]; + [newRequest setUseKeychainPersistence:[self useKeychainPersistence]]; + [newRequest setUseSessionPersistence:[self useSessionPersistence]]; + [newRequest setAllowCompressedResponse:[self allowCompressedResponse]]; + [newRequest setDownloadDestinationPath:[self downloadDestinationPath]]; + [newRequest setTemporaryFileDownloadPath:[self temporaryFileDownloadPath]]; + [newRequest setUsername:[self username]]; + [newRequest setPassword:[self password]]; + [newRequest setDomain:[self domain]]; + [newRequest setProxyUsername:[self proxyUsername]]; + [newRequest setProxyPassword:[self proxyPassword]]; + [newRequest setProxyDomain:[self proxyDomain]]; + [newRequest setProxyHost:[self proxyHost]]; + [newRequest setProxyPort:[self proxyPort]]; + [newRequest setProxyType:[self proxyType]]; + [newRequest setUploadProgressDelegate:[self uploadProgressDelegate]]; + [newRequest setDownloadProgressDelegate:[self downloadProgressDelegate]]; + [newRequest setShouldPresentAuthenticationDialog:[self shouldPresentAuthenticationDialog]]; + [newRequest setShouldPresentProxyAuthenticationDialog:[self shouldPresentProxyAuthenticationDialog]]; + [newRequest setPostLength:[self postLength]]; + [newRequest setHaveBuiltPostBody:[self haveBuiltPostBody]]; + [newRequest setDidStartSelector:[self didStartSelector]]; + [newRequest setDidFinishSelector:[self didFinishSelector]]; + [newRequest setDidFailSelector:[self didFailSelector]]; + [newRequest setTimeOutSeconds:[self timeOutSeconds]]; + [newRequest setShouldResetDownloadProgress:[self shouldResetDownloadProgress]]; + [newRequest setShouldResetUploadProgress:[self shouldResetUploadProgress]]; + [newRequest setShowAccurateProgress:[self showAccurateProgress]]; + [newRequest setDefaultResponseEncoding:[self defaultResponseEncoding]]; + [newRequest setAllowResumeForFileDownloads:[self allowResumeForFileDownloads]]; + [newRequest setUserInfo:[[[self userInfo] copyWithZone:zone] autorelease]]; + [newRequest setUseHTTPVersionOne:[self useHTTPVersionOne]]; + [newRequest setShouldRedirect:[self shouldRedirect]]; + [newRequest setValidatesSecureCertificate:[self validatesSecureCertificate]]; + [newRequest setClientCertificateIdentity:clientCertificateIdentity]; + [newRequest setClientCertificates:[[clientCertificates copy] autorelease]]; + [newRequest setPACurl:[self PACurl]]; + [newRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]]; + [newRequest setNumberOfTimesToRetryOnTimeout:[self numberOfTimesToRetryOnTimeout]]; + [newRequest setShouldUseRFC2616RedirectBehaviour:[self shouldUseRFC2616RedirectBehaviour]]; + [newRequest setShouldAttemptPersistentConnection:[self shouldAttemptPersistentConnection]]; + [newRequest setPersistentConnectionTimeoutSeconds:[self persistentConnectionTimeoutSeconds]]; + return newRequest; +} + +#pragma mark default time out + ++ (NSTimeInterval)defaultTimeOutSeconds +{ + return defaultTimeOutSeconds; +} + ++ (void)setDefaultTimeOutSeconds:(NSTimeInterval)newTimeOutSeconds +{ + defaultTimeOutSeconds = newTimeOutSeconds; +} + + +#pragma mark client certificate + +- (void)setClientCertificateIdentity:(SecIdentityRef)anIdentity { + if(clientCertificateIdentity) { + CFRelease(clientCertificateIdentity); + } + + clientCertificateIdentity = anIdentity; + + if (clientCertificateIdentity) { + CFRetain(clientCertificateIdentity); + } +} + + +#pragma mark session credentials + ++ (NSMutableArray *)sessionProxyCredentialsStore +{ + [sessionCredentialsLock lock]; + if (!sessionProxyCredentialsStore) { + sessionProxyCredentialsStore = [[NSMutableArray alloc] init]; + } + [sessionCredentialsLock unlock]; + return sessionProxyCredentialsStore; +} + ++ (NSMutableArray *)sessionCredentialsStore +{ + [sessionCredentialsLock lock]; + if (!sessionCredentialsStore) { + sessionCredentialsStore = [[NSMutableArray alloc] init]; + } + [sessionCredentialsLock unlock]; + return sessionCredentialsStore; +} + ++ (void)storeProxyAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials +{ + [sessionCredentialsLock lock]; + [self removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; + [[[self class] sessionProxyCredentialsStore] addObject:credentials]; + [sessionCredentialsLock unlock]; +} + ++ (void)storeAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials +{ + [sessionCredentialsLock lock]; + [self removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; + [[[self class] sessionCredentialsStore] addObject:credentials]; + [sessionCredentialsLock unlock]; +} + ++ (void)removeProxyAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials +{ + [sessionCredentialsLock lock]; + NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore]; + NSUInteger i; + for (i=0; i<[sessionCredentialsList count]; i++) { + NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i]; + if ([theCredentials objectForKey:@"Credentials"] == credentials) { + [sessionCredentialsList removeObjectAtIndex:i]; + [sessionCredentialsLock unlock]; + return; + } + } + [sessionCredentialsLock unlock]; +} + ++ (void)removeAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials +{ + [sessionCredentialsLock lock]; + NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore]; + NSUInteger i; + for (i=0; i<[sessionCredentialsList count]; i++) { + NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i]; + if ([theCredentials objectForKey:@"Credentials"] == credentials) { + [sessionCredentialsList removeObjectAtIndex:i]; + [sessionCredentialsLock unlock]; + return; + } + } + [sessionCredentialsLock unlock]; +} + +- (NSDictionary *)findSessionProxyAuthenticationCredentials +{ + [sessionCredentialsLock lock]; + NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore]; + for (NSDictionary *theCredentials in sessionCredentialsList) { + if ([[theCredentials objectForKey:@"Host"] isEqualToString:[self proxyHost]] && [[theCredentials objectForKey:@"Port"] intValue] == [self proxyPort]) { + [sessionCredentialsLock unlock]; + return theCredentials; + } + } + [sessionCredentialsLock unlock]; + return nil; +} + + +- (NSDictionary *)findSessionAuthenticationCredentials +{ + [sessionCredentialsLock lock]; + NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore]; + // Find an exact match (same url) + for (NSDictionary *theCredentials in sessionCredentialsList) { + if ([(NSURL*)[theCredentials objectForKey:@"URL"] isEqual:[self url]]) { + // /Just a sanity check to ensure we never choose credentials from a different realm. Can't really do more than that, as either this request or the stored credentials may not have a realm when the other does + if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) { + [sessionCredentialsLock unlock]; + return theCredentials; + } + } + } + // Find a rough match (same host, port, scheme) + NSURL *requestURL = [self url]; + for (NSDictionary *theCredentials in sessionCredentialsList) { + NSURL *theURL = [theCredentials objectForKey:@"URL"]; + + // Port can be nil! + if ([[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]]) { + if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) { + [sessionCredentialsLock unlock]; + return theCredentials; + } + } + } + [sessionCredentialsLock unlock]; + return nil; +} + +#pragma mark keychain storage + ++ (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace]; +} + ++ (void)saveCredentials:(NSURLCredential *)credentials forProxy:(NSString *)host port:(int)port realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace]; +} + ++ (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace]; +} + ++ (NSURLCredential *)savedCredentialsForProxy:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace]; +} + ++ (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace]; + if (credential) { + [[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:protectionSpace]; + } +} + ++ (void)removeCredentialsForProxy:(NSString *)host port:(int)port realm:(NSString *)realm +{ + NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; + NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace]; + if (credential) { + [[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:protectionSpace]; + } +} + + ++ (NSMutableArray *)sessionCookies +{ + if (!sessionCookies) { + [ASIHTTPRequest setSessionCookies:[[[NSMutableArray alloc] init] autorelease]]; + } + return sessionCookies; +} + ++ (void)setSessionCookies:(NSMutableArray *)newSessionCookies +{ + [sessionCookiesLock lock]; + // Remove existing cookies from the persistent store + for (NSHTTPCookie *cookie in sessionCookies) { + [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie]; + } + [sessionCookies release]; + sessionCookies = [newSessionCookies retain]; + [sessionCookiesLock unlock]; +} + ++ (void)addSessionCookie:(NSHTTPCookie *)newCookie +{ + [sessionCookiesLock lock]; + NSHTTPCookie *cookie; + NSUInteger i; + NSUInteger max = [[ASIHTTPRequest sessionCookies] count]; + for (i=0; i 0) { + if ([self readStreamIsScheduled]) { + [self unscheduleReadStream]; + #if DEBUG_THROTTLING + NSLog(@"Sleeping request %@ until after %@",self,throttleWakeUpTime); + #endif + } + } else { + if (![self readStreamIsScheduled]) { + [self scheduleReadStream]; + #if DEBUG_THROTTLING + NSLog(@"Waking up request %@",self); + #endif + } + } + } + [bandwidthThrottlingLock unlock]; + + // Bandwidth throttling must have been turned off since we last looked, let's re-schedule the stream + } else if (![self readStreamIsScheduled]) { + [self scheduleReadStream]; + } +} + ++ (BOOL)isBandwidthThrottled +{ +#if TARGET_OS_IPHONE + [bandwidthThrottlingLock lock]; + + BOOL throttle = isBandwidthThrottled || (!shouldThrottleBandwithForWWANOnly && (maxBandwidthPerSecond)); + [bandwidthThrottlingLock unlock]; + return throttle; +#else + [bandwidthThrottlingLock lock]; + BOOL throttle = (maxBandwidthPerSecond); + [bandwidthThrottlingLock unlock]; + return throttle; +#endif +} + ++ (unsigned long)maxBandwidthPerSecond +{ + [bandwidthThrottlingLock lock]; + unsigned long amount = maxBandwidthPerSecond; + [bandwidthThrottlingLock unlock]; + return amount; +} + ++ (void)setMaxBandwidthPerSecond:(unsigned long)bytes +{ + [bandwidthThrottlingLock lock]; + maxBandwidthPerSecond = bytes; + [bandwidthThrottlingLock unlock]; +} + ++ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes +{ + [bandwidthThrottlingLock lock]; + bandwidthUsedInLastSecond += bytes; + [bandwidthThrottlingLock unlock]; +} + ++ (void)recordBandwidthUsage +{ + if (bandwidthUsedInLastSecond == 0) { + [bandwidthUsageTracker removeAllObjects]; + } else { + NSTimeInterval interval = [bandwidthMeasurementDate timeIntervalSinceNow]; + while ((interval < 0 || [bandwidthUsageTracker count] > 5) && [bandwidthUsageTracker count] > 0) { + [bandwidthUsageTracker removeObjectAtIndex:0]; + interval++; + } + } + #if DEBUG_THROTTLING + NSLog(@"===Used: %u bytes of bandwidth in last measurement period===",bandwidthUsedInLastSecond); + #endif + [bandwidthUsageTracker addObject:[NSNumber numberWithUnsignedLong:bandwidthUsedInLastSecond]]; + [bandwidthMeasurementDate release]; + bandwidthMeasurementDate = [[NSDate dateWithTimeIntervalSinceNow:1] retain]; + bandwidthUsedInLastSecond = 0; + + NSUInteger measurements = [bandwidthUsageTracker count]; + unsigned long totalBytes = 0; + for (NSNumber *bytes in bandwidthUsageTracker) { + totalBytes += [bytes unsignedLongValue]; + } + averageBandwidthUsedPerSecond = totalBytes/measurements; +} + ++ (unsigned long)averageBandwidthUsedPerSecond +{ + [bandwidthThrottlingLock lock]; + unsigned long amount = averageBandwidthUsedPerSecond; + [bandwidthThrottlingLock unlock]; + return amount; +} + ++ (void)measureBandwidthUsage +{ + // Other requests may have to wait for this lock if we're sleeping, but this is fine, since in that case we already know they shouldn't be sending or receiving data + [bandwidthThrottlingLock lock]; + + if (!bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) { + [ASIHTTPRequest recordBandwidthUsage]; + } + + // Are we performing bandwidth throttling? + if ( + #if TARGET_OS_IPHONE + isBandwidthThrottled || (!shouldThrottleBandwithForWWANOnly && (maxBandwidthPerSecond)) + #else + maxBandwidthPerSecond + #endif + ) { + // How much data can we still send or receive this second? + long long bytesRemaining = (long long)maxBandwidthPerSecond - (long long)bandwidthUsedInLastSecond; + + // Have we used up our allowance? + if (bytesRemaining < 0) { + + // Yes, put this request to sleep until a second is up, with extra added punishment sleeping time for being very naughty (we have used more bandwidth than we were allowed) + double extraSleepyTime = (-bytesRemaining/(maxBandwidthPerSecond*1.0)); + [throttleWakeUpTime release]; + throttleWakeUpTime = [[NSDate alloc] initWithTimeInterval:extraSleepyTime sinceDate:bandwidthMeasurementDate]; + } + } + [bandwidthThrottlingLock unlock]; +} + ++ (unsigned long)maxUploadReadLength +{ + + [bandwidthThrottlingLock lock]; + + // We'll split our bandwidth allowance into 4 (which is the default for an ASINetworkQueue's max concurrent operations count) to give all running requests a fighting chance of reading data this cycle + long long toRead = maxBandwidthPerSecond/4; + if (maxBandwidthPerSecond > 0 && (bandwidthUsedInLastSecond + toRead > maxBandwidthPerSecond)) { + toRead = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond; + if (toRead < 0) { + toRead = 0; + } + } + + if (toRead == 0 || !bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) { + [throttleWakeUpTime release]; + throttleWakeUpTime = [bandwidthMeasurementDate retain]; + } + [bandwidthThrottlingLock unlock]; + return (unsigned long)toRead; +} + + +#if TARGET_OS_IPHONE ++ (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle +{ + if (throttle) { + [ASIHTTPRequest throttleBandwidthForWWANUsingLimit:ASIWWANBandwidthThrottleAmount]; + } else { + [ASIHTTPRequest unsubscribeFromNetworkReachabilityNotifications]; + [ASIHTTPRequest setMaxBandwidthPerSecond:0]; + [bandwidthThrottlingLock lock]; + isBandwidthThrottled = NO; + shouldThrottleBandwithForWWANOnly = NO; + [bandwidthThrottlingLock unlock]; + } +} + ++ (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit +{ + [bandwidthThrottlingLock lock]; + shouldThrottleBandwithForWWANOnly = YES; + maxBandwidthPerSecond = limit; + [ASIHTTPRequest registerForNetworkReachabilityNotifications]; + [bandwidthThrottlingLock unlock]; + [ASIHTTPRequest reachabilityChanged:nil]; +} + +#pragma mark reachability + ++ (void)registerForNetworkReachabilityNotifications +{ + [[Reachability reachabilityForInternetConnection] startNotifier]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil]; +} + + ++ (void)unsubscribeFromNetworkReachabilityNotifications +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil]; +} + ++ (BOOL)isNetworkReachableViaWWAN +{ + return ([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] == ReachableViaWWAN); +} + ++ (void)reachabilityChanged:(NSNotification *)note +{ + [bandwidthThrottlingLock lock]; + isBandwidthThrottled = [ASIHTTPRequest isNetworkReachableViaWWAN]; + [bandwidthThrottlingLock unlock]; +} +#endif + +#pragma mark queue + +// Returns the shared queue ++ (NSOperationQueue *)sharedQueue +{ + return [[sharedQueue retain] autorelease]; +} + +#pragma mark cache + ++ (void)setDefaultCache:(id )cache +{ + [defaultCache release]; + defaultCache = [cache retain]; +} + ++ (id )defaultCache +{ + return defaultCache; +} + + +#pragma mark network activity + ++ (BOOL)isNetworkInUse +{ + [connectionsLock lock]; + BOOL inUse = (runningRequestCount > 0); + [connectionsLock unlock]; + return inUse; +} + ++ (void)setShouldUpdateNetworkActivityIndicator:(BOOL)shouldUpdate +{ + [connectionsLock lock]; + shouldUpdateNetworkActivityIndicator = shouldUpdate; + [connectionsLock unlock]; +} + ++ (void)showNetworkActivityIndicator +{ +#if TARGET_OS_IPHONE + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; +#endif +} + ++ (void)hideNetworkActivityIndicator +{ +#if TARGET_OS_IPHONE + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; +#endif +} + + +/* Always called on main thread */ ++ (void)hideNetworkActivityIndicatorAfterDelay +{ + [self performSelector:@selector(hideNetworkActivityIndicatorIfNeeeded) withObject:nil afterDelay:0.5]; +} + ++ (void)hideNetworkActivityIndicatorIfNeeeded +{ + [connectionsLock lock]; + if (runningRequestCount == 0) { + [self hideNetworkActivityIndicator]; + } + [connectionsLock unlock]; +} + + +#pragma mark threading behaviour + +// In the default implementation, all requests run in a single background thread +// Advanced users only: Override this method in a subclass for a different threading behaviour +// Eg: return [NSThread mainThread] to run all requests in the main thread +// Alternatively, you can create a thread on demand, or manage a pool of threads +// Threads returned by this method will need to run the runloop in default mode (eg CFRunLoopRun()) +// Requests will stop the runloop when they complete +// If you have multiple requests sharing the thread or you want to re-use the thread, you'll need to restart the runloop ++ (NSThread *)threadForRequest:(ASIHTTPRequest *)request +{ + if (!networkThread) { + networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequests) object:nil]; + [networkThread start]; + } + return networkThread; +} + ++ (void)runRequests +{ + // Should keep the runloop from exiting + CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; + CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + + BOOL runAlways = YES; // Introduced to cheat Static Analyzer + while (runAlways) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + CFRunLoopRun(); + [pool release]; + } + + // Should never be called, but anyway + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + CFRelease(source); +} + +#pragma mark miscellany + +#if TARGET_OS_IPHONE ++ (BOOL)isMultitaskingSupported +{ + BOOL multiTaskingSupported = NO; + if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]) { + multiTaskingSupported = [(id)[UIDevice currentDevice] isMultitaskingSupported]; + } + return multiTaskingSupported; +} +#endif + +// From: http://www.cocoadev.com/index.pl?BaseSixtyFour + ++ (NSString*)base64forData:(NSData*)theData { + + const uint8_t* input = (const uint8_t*)[theData bytes]; + NSInteger length = [theData length]; + + static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; + uint8_t* output = (uint8_t*)data.mutableBytes; + + NSInteger i; + for (i=0; i < length; i += 3) { + NSInteger value = 0; + NSInteger j; + for (j = i; j < (i + 3); j++) { + value <<= 8; + + if (j < length) { + value |= (0xFF & input[j]); + } + } + + NSInteger theIndex = (i / 3) * 4; + output[theIndex + 0] = table[(value >> 18) & 0x3F]; + output[theIndex + 1] = table[(value >> 12) & 0x3F]; + output[theIndex + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '='; + output[theIndex + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '='; + } + + return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease]; +} + +// Based on hints from http://stackoverflow.com/questions/1850824/parsing-a-rfc-822-date-with-nsdateformatter ++ (NSDate *)dateFromRFC1123String:(NSString *)string +{ + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]]; + // Does the string include a week day? + NSString *day = @""; + if ([string rangeOfString:@","].location != NSNotFound) { + day = @"EEE, "; + } + // Does the string include seconds? + NSString *seconds = @""; + if ([[string componentsSeparatedByString:@":"] count] == 3) { + seconds = @":ss"; + } + [formatter setDateFormat:[NSString stringWithFormat:@"%@dd MMM yyyy HH:mm%@ z",day,seconds]]; + return [formatter dateFromString:string]; +} + ++ (void)parseMimeType:(NSString **)mimeType andResponseEncoding:(NSStringEncoding *)stringEncoding fromContentType:(NSString *)contentType +{ + if (!contentType) { + return; + } + NSScanner *charsetScanner = [NSScanner scannerWithString: contentType]; + if (![charsetScanner scanUpToString:@";" intoString:mimeType] || [charsetScanner scanLocation] == [contentType length]) { + *mimeType = [contentType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + return; + } + *mimeType = [*mimeType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSString *charsetSeparator = @"charset="; + NSString *IANAEncoding = nil; + + if ([charsetScanner scanUpToString: charsetSeparator intoString: NULL] && [charsetScanner scanLocation] < [contentType length]) { + [charsetScanner setScanLocation: [charsetScanner scanLocation] + [charsetSeparator length]]; + [charsetScanner scanUpToString: @";" intoString: &IANAEncoding]; + } + + if (IANAEncoding) { + CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)IANAEncoding); + if (cfEncoding != kCFStringEncodingInvalidId) { + *stringEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); + } + } +} + +#pragma mark - +#pragma mark blocks +#if NS_BLOCKS_AVAILABLE +- (void)setStartedBlock:(ASIBasicBlock)aStartedBlock +{ + [startedBlock release]; + startedBlock = [aStartedBlock copy]; +} + +- (void)setHeadersReceivedBlock:(ASIHeadersBlock)aReceivedBlock +{ + [headersReceivedBlock release]; + headersReceivedBlock = [aReceivedBlock copy]; +} + +- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock +{ + [completionBlock release]; + completionBlock = [aCompletionBlock copy]; +} + +- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock +{ + [failureBlock release]; + failureBlock = [aFailedBlock copy]; +} + +- (void)setBytesReceivedBlock:(ASIProgressBlock)aBytesReceivedBlock +{ + [bytesReceivedBlock release]; + bytesReceivedBlock = [aBytesReceivedBlock copy]; +} + +- (void)setBytesSentBlock:(ASIProgressBlock)aBytesSentBlock +{ + [bytesSentBlock release]; + bytesSentBlock = [aBytesSentBlock copy]; +} + +- (void)setDownloadSizeIncrementedBlock:(ASISizeBlock)aDownloadSizeIncrementedBlock{ + [downloadSizeIncrementedBlock release]; + downloadSizeIncrementedBlock = [aDownloadSizeIncrementedBlock copy]; +} + +- (void)setUploadSizeIncrementedBlock:(ASISizeBlock)anUploadSizeIncrementedBlock +{ + [uploadSizeIncrementedBlock release]; + uploadSizeIncrementedBlock = [anUploadSizeIncrementedBlock copy]; +} + +- (void)setDataReceivedBlock:(ASIDataBlock)aReceivedBlock +{ + [dataReceivedBlock release]; + dataReceivedBlock = [aReceivedBlock copy]; +} + +- (void)setAuthenticationNeededBlock:(ASIBasicBlock)anAuthenticationBlock +{ + [authenticationNeededBlock release]; + authenticationNeededBlock = [anAuthenticationBlock copy]; +} +- (void)setProxyAuthenticationNeededBlock:(ASIBasicBlock)aProxyAuthenticationBlock +{ + [proxyAuthenticationNeededBlock release]; + proxyAuthenticationNeededBlock = [aProxyAuthenticationBlock copy]; +} +- (void)setRequestRedirectedBlock:(ASIBasicBlock)aRedirectBlock +{ + [requestRedirectedBlock release]; + requestRedirectedBlock = [aRedirectBlock copy]; +} +#endif + +#pragma mark === + +@synthesize username; +@synthesize password; +@synthesize domain; +@synthesize proxyUsername; +@synthesize proxyPassword; +@synthesize proxyDomain; +@synthesize url; +@synthesize originalURL; +@synthesize delegate; +@synthesize queue; +@synthesize uploadProgressDelegate; +@synthesize downloadProgressDelegate; +@synthesize useKeychainPersistence; +@synthesize useSessionPersistence; +@synthesize useCookiePersistence; +@synthesize downloadDestinationPath; +@synthesize temporaryFileDownloadPath; +@synthesize temporaryUncompressedDataDownloadPath; +@synthesize didStartSelector; +@synthesize didReceiveResponseHeadersSelector; +@synthesize willRedirectSelector; +@synthesize didFinishSelector; +@synthesize didFailSelector; +@synthesize didReceiveDataSelector; +@synthesize authenticationRealm; +@synthesize proxyAuthenticationRealm; +@synthesize error; +@synthesize complete; +@synthesize requestHeaders; +@synthesize responseHeaders; +@synthesize responseCookies; +@synthesize requestCookies; +@synthesize requestCredentials; +@synthesize responseStatusCode; +@synthesize rawResponseData; +@synthesize lastActivityTime; +@synthesize timeOutSeconds; +@synthesize requestMethod; +@synthesize postBody; +@synthesize compressedPostBody; +@synthesize contentLength; +@synthesize partialDownloadSize; +@synthesize postLength; +@synthesize shouldResetDownloadProgress; +@synthesize shouldResetUploadProgress; +@synthesize mainRequest; +@synthesize totalBytesRead; +@synthesize totalBytesSent; +@synthesize showAccurateProgress; +@synthesize uploadBufferSize; +@synthesize defaultResponseEncoding; +@synthesize responseEncoding; +@synthesize allowCompressedResponse; +@synthesize allowResumeForFileDownloads; +@synthesize userInfo; +@synthesize postBodyFilePath; +@synthesize compressedPostBodyFilePath; +@synthesize postBodyWriteStream; +@synthesize postBodyReadStream; +@synthesize shouldStreamPostDataFromDisk; +@synthesize didCreateTemporaryPostDataFile; +@synthesize useHTTPVersionOne; +@synthesize lastBytesRead; +@synthesize lastBytesSent; +@synthesize cancelledLock; +@synthesize haveBuiltPostBody; +@synthesize fileDownloadOutputStream; +@synthesize inflatedFileDownloadOutputStream; +@synthesize authenticationRetryCount; +@synthesize proxyAuthenticationRetryCount; +@synthesize updatedProgress; +@synthesize shouldRedirect; +@synthesize validatesSecureCertificate; +@synthesize needsRedirect; +@synthesize redirectCount; +@synthesize shouldCompressRequestBody; +@synthesize proxyCredentials; +@synthesize proxyHost; +@synthesize proxyPort; +@synthesize proxyType; +@synthesize PACurl; +@synthesize authenticationScheme; +@synthesize proxyAuthenticationScheme; +@synthesize shouldPresentAuthenticationDialog; +@synthesize shouldPresentProxyAuthenticationDialog; +@synthesize authenticationNeeded; +@synthesize responseStatusMessage; +@synthesize shouldPresentCredentialsBeforeChallenge; +@synthesize haveBuiltRequestHeaders; +@synthesize inProgress; +@synthesize numberOfTimesToRetryOnTimeout; +@synthesize retryCount; +@synthesize shouldAttemptPersistentConnection; +@synthesize persistentConnectionTimeoutSeconds; +@synthesize connectionCanBeReused; +@synthesize connectionInfo; +@synthesize readStream; +@synthesize readStreamIsScheduled; +@synthesize shouldUseRFC2616RedirectBehaviour; +@synthesize downloadComplete; +@synthesize requestID; +@synthesize runLoopMode; +@synthesize statusTimer; +@synthesize downloadCache; +@synthesize cachePolicy; +@synthesize cacheStoragePolicy; +@synthesize didUseCachedResponse; +@synthesize secondsToCache; +@synthesize clientCertificates; +@synthesize redirectURL; +#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 +@synthesize shouldContinueWhenAppEntersBackground; +#endif +@synthesize dataDecompressor; +@synthesize shouldWaitToInflateCompressedResponses; + +@synthesize isPACFileRequest; +@synthesize PACFileRequest; +@synthesize PACFileReadStream; +@synthesize PACFileData; + +@synthesize isSynchronous; +@end diff --git a/Vendor/ASIHTTPRequest/ASIHTTPRequestConfig.h b/Vendor/ASIHTTPRequest/ASIHTTPRequestConfig.h new file mode 100644 index 0000000..52cbfcb --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIHTTPRequestConfig.h @@ -0,0 +1,32 @@ +// +// ASIHTTPRequestConfig.h +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 14/12/2009. +// Copyright 2009 All-Seeing Interactive. All rights reserved. +// + + +// ====== +// Debug output configuration options +// ====== + +// When set to 1 ASIHTTPRequests will print information about what a request is doing +#ifndef DEBUG_REQUEST_STATUS + #define DEBUG_REQUEST_STATUS 0 +#endif + +// When set to 1, ASIFormDataRequests will print information about the request body to the console +#ifndef DEBUG_FORM_DATA_REQUEST + #define DEBUG_FORM_DATA_REQUEST 0 +#endif + +// When set to 1, ASIHTTPRequests will print information about bandwidth throttling to the console +#ifndef DEBUG_THROTTLING + #define DEBUG_THROTTLING 0 +#endif + +// When set to 1, ASIHTTPRequests will print information about persistent connections to the console +#ifndef DEBUG_PERSISTENT_CONNECTIONS + #define DEBUG_PERSISTENT_CONNECTIONS 0 +#endif diff --git a/Vendor/ASIHTTPRequest/ASIHTTPRequestDelegate.h b/Vendor/ASIHTTPRequest/ASIHTTPRequestDelegate.h new file mode 100644 index 0000000..c495a27 --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIHTTPRequestDelegate.h @@ -0,0 +1,35 @@ +// +// ASIHTTPRequestDelegate.h +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 13/04/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// + +@class ASIHTTPRequest; + +@protocol ASIHTTPRequestDelegate + +@optional + +// These are the default delegate methods for request status +// You can use different ones by setting didStartSelector / didFinishSelector / didFailSelector +- (void)requestStarted:(ASIHTTPRequest *)request; +- (void)request:(ASIHTTPRequest *)request didReceiveResponseHeaders:(NSDictionary *)responseHeaders; +- (void)request:(ASIHTTPRequest *)request willRedirectToURL:(NSURL *)newURL; +- (void)requestFinished:(ASIHTTPRequest *)request; +- (void)requestFailed:(ASIHTTPRequest *)request; +- (void)requestRedirected:(ASIHTTPRequest *)request; + +// When a delegate implements this method, it is expected to process all incoming data itself +// This means that responseData / responseString / downloadDestinationPath etc are ignored +// You can have the request call a different method by setting didReceiveDataSelector +- (void)request:(ASIHTTPRequest *)request didReceiveData:(NSData *)data; + +// If a delegate implements one of these, it will be asked to supply credentials when none are available +// The delegate can then either restart the request ([request retryUsingSuppliedCredentials]) once credentials have been set +// or cancel it ([request cancelAuthentication]) +- (void)authenticationNeededForRequest:(ASIHTTPRequest *)request; +- (void)proxyAuthenticationNeededForRequest:(ASIHTTPRequest *)request; + +@end diff --git a/Vendor/ASIHTTPRequest/ASIInputStream.h b/Vendor/ASIHTTPRequest/ASIInputStream.h new file mode 100644 index 0000000..7b9f93e --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIInputStream.h @@ -0,0 +1,26 @@ +// +// ASIInputStream.h +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 10/08/2009. +// Copyright 2009 All-Seeing Interactive. All rights reserved. +// + +#import + +@class ASIHTTPRequest; + +// This is a wrapper for NSInputStream that pretends to be an NSInputStream itself +// Subclassing NSInputStream seems to be tricky, and may involve overriding undocumented methods, so we'll cheat instead. +// It is used by ASIHTTPRequest whenever we have a request body, and handles measuring and throttling the bandwidth used for uploading + +@interface ASIInputStream : NSObject { + NSInputStream *stream; + ASIHTTPRequest *request; +} ++ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)request; ++ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)request; + +@property (retain, nonatomic) NSInputStream *stream; +@property (assign, nonatomic) ASIHTTPRequest *request; +@end diff --git a/Vendor/ASIHTTPRequest/ASIInputStream.m b/Vendor/ASIHTTPRequest/ASIInputStream.m new file mode 100644 index 0000000..5753510 --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIInputStream.m @@ -0,0 +1,136 @@ +// +// ASIInputStream.m +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 10/08/2009. +// Copyright 2009 All-Seeing Interactive. All rights reserved. +// + +#import "ASIInputStream.h" +#import "ASIHTTPRequest.h" + +// Used to ensure only one request can read data at once +static NSLock *readLock = nil; + +@implementation ASIInputStream + ++ (void)initialize +{ + if (self == [ASIInputStream class]) { + readLock = [[NSLock alloc] init]; + } +} + ++ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)theRequest +{ + ASIInputStream *theStream = [[[self alloc] init] autorelease]; + [theStream setRequest:theRequest]; + [theStream setStream:[NSInputStream inputStreamWithFileAtPath:path]]; + return theStream; +} + ++ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)theRequest +{ + ASIInputStream *theStream = [[[self alloc] init] autorelease]; + [theStream setRequest:theRequest]; + [theStream setStream:[NSInputStream inputStreamWithData:data]]; + return theStream; +} + +- (void)dealloc +{ + [stream release]; + [super dealloc]; +} + +// Called when CFNetwork wants to read more of our request body +// When throttling is on, we ask ASIHTTPRequest for the maximum amount of data we can read +- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len +{ + [readLock lock]; + unsigned long toRead = len; + if ([ASIHTTPRequest isBandwidthThrottled]) { + toRead = [ASIHTTPRequest maxUploadReadLength]; + if (toRead > len) { + toRead = len; + } else if (toRead == 0) { + toRead = 1; + } + [request performThrottling]; + } + [ASIHTTPRequest incrementBandwidthUsedInLastSecond:toRead]; + [readLock unlock]; + return [stream read:buffer maxLength:toRead]; +} + +/* + * Implement NSInputStream mandatory methods to make sure they are implemented + * (necessary for MacRuby for example) and avoid the overhead of method + * forwarding for these common methods. + */ +- (void)open +{ + [stream open]; +} + +- (void)close +{ + [stream close]; +} + +- (id)delegate +{ + return [stream delegate]; +} + +- (void)setDelegate:(id)delegate +{ + [stream setDelegate:delegate]; +} + +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode +{ + [stream scheduleInRunLoop:aRunLoop forMode:mode]; +} + +- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode +{ + [stream removeFromRunLoop:aRunLoop forMode:mode]; +} + +- (id)propertyForKey:(NSString *)key +{ + return [stream propertyForKey:key]; +} + +- (BOOL)setProperty:(id)property forKey:(NSString *)key +{ + return [stream setProperty:property forKey:key]; +} + +- (NSStreamStatus)streamStatus +{ + return [stream streamStatus]; +} + +- (NSError *)streamError +{ + return [stream streamError]; +} + +// If we get asked to perform a method we don't have (probably internal ones), +// we'll just forward the message to our stream + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + return [stream methodSignatureForSelector:aSelector]; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation +{ + [anInvocation invokeWithTarget:stream]; +} + +@synthesize stream; +@synthesize request; +@end diff --git a/Vendor/ASIHTTPRequest/ASINetworkQueue.h b/Vendor/ASIHTTPRequest/ASINetworkQueue.h new file mode 100644 index 0000000..787f391 --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASINetworkQueue.h @@ -0,0 +1,108 @@ +// +// ASINetworkQueue.h +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 07/11/2008. +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved. +// + +#import +#import "ASIHTTPRequestDelegate.h" +#import "ASIProgressDelegate.h" + +@interface ASINetworkQueue : NSOperationQueue { + + // Delegate will get didFail + didFinish messages (if set) + id delegate; + + // Will be called when a request starts with the request as the argument + SEL requestDidStartSelector; + + // Will be called when a request receives response headers + // Should take the form request:didRecieveResponseHeaders:, where the first argument is the request, and the second the headers dictionary + SEL requestDidReceiveResponseHeadersSelector; + + // Will be called when a request is about to redirect + // Should take the form request:willRedirectToURL:, where the first argument is the request, and the second the new url + SEL requestWillRedirectSelector; + + // Will be called when a request completes with the request as the argument + SEL requestDidFinishSelector; + + // Will be called when a request fails with the request as the argument + SEL requestDidFailSelector; + + // Will be called when the queue finishes with the queue as the argument + SEL queueDidFinishSelector; + + // Upload progress indicator, probably an NSProgressIndicator or UIProgressView + id uploadProgressDelegate; + + // Total amount uploaded so far for all requests in this queue + unsigned long long bytesUploadedSoFar; + + // Total amount to be uploaded for all requests in this queue - requests add to this figure as they work out how much data they have to transmit + unsigned long long totalBytesToUpload; + + // Download progress indicator, probably an NSProgressIndicator or UIProgressView + id downloadProgressDelegate; + + // Total amount downloaded so far for all requests in this queue + unsigned long long bytesDownloadedSoFar; + + // Total amount to be downloaded for all requests in this queue - requests add to this figure as they receive Content-Length headers + unsigned long long totalBytesToDownload; + + // When YES, the queue will cancel all requests when a request fails. Default is YES + BOOL shouldCancelAllRequestsOnFailure; + + //Number of real requests (excludes HEAD requests created to manage showAccurateProgress) + int requestsCount; + + // When NO, this request will only update the progress indicator when it completes + // When YES, this request will update the progress indicator according to how much data it has received so far + // When YES, the queue will first perform HEAD requests for all GET requests in the queue, so it can calculate the total download size before it starts + // NO means better performance, because it skips this step for GET requests, and it won't waste time updating the progress indicator until a request completes + // Set to YES if the size of a requests in the queue varies greatly for much more accurate results + // Default for requests in the queue is NO + BOOL showAccurateProgress; + + // Storage container for additional queue information. + NSDictionary *userInfo; + +} + +// Convenience constructor ++ (id)queue; + +// Call this to reset a queue - it will cancel all operations, clear delegates, and suspend operation +- (void)reset; + +// Used internally to manage HEAD requests when showAccurateProgress is YES, do not use! +- (void)addHEADOperation:(NSOperation *)operation; + +// All ASINetworkQueues are paused when created so that total size can be calculated before the queue starts +// This method will start the queue +- (void)go; + +@property (assign, nonatomic, setter=setUploadProgressDelegate:) id uploadProgressDelegate; +@property (assign, nonatomic, setter=setDownloadProgressDelegate:) id downloadProgressDelegate; + +@property (assign) SEL requestDidStartSelector; +@property (assign) SEL requestDidReceiveResponseHeadersSelector; +@property (assign) SEL requestWillRedirectSelector; +@property (assign) SEL requestDidFinishSelector; +@property (assign) SEL requestDidFailSelector; +@property (assign) SEL queueDidFinishSelector; +@property (assign) BOOL shouldCancelAllRequestsOnFailure; +@property (assign) id delegate; +@property (assign) BOOL showAccurateProgress; +@property (assign, readonly) int requestsCount; +@property (retain) NSDictionary *userInfo; + +@property (assign) unsigned long long bytesUploadedSoFar; +@property (assign) unsigned long long totalBytesToUpload; +@property (assign) unsigned long long bytesDownloadedSoFar; +@property (assign) unsigned long long totalBytesToDownload; + +@end diff --git a/Vendor/ASIHTTPRequest/ASINetworkQueue.m b/Vendor/ASIHTTPRequest/ASINetworkQueue.m new file mode 100644 index 0000000..b24076d --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASINetworkQueue.m @@ -0,0 +1,343 @@ +// +// ASINetworkQueue.m +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 07/11/2008. +// Copyright 2008-2009 All-Seeing Interactive. All rights reserved. +// + +#import "ASINetworkQueue.h" +#import "ASIHTTPRequest.h" + +// Private stuff +@interface ASINetworkQueue () + - (void)resetProgressDelegate:(id *)progressDelegate; + @property (assign) int requestsCount; +@end + +@implementation ASINetworkQueue + +- (id)init +{ + self = [super init]; + [self setShouldCancelAllRequestsOnFailure:YES]; + [self setMaxConcurrentOperationCount:4]; + [self setSuspended:YES]; + + return self; +} + ++ (id)queue +{ + return [[[self alloc] init] autorelease]; +} + +- (void)dealloc +{ + //We need to clear the queue on any requests that haven't got around to cleaning up yet, as otherwise they'll try to let us know if something goes wrong, and we'll be long gone by then + for (ASIHTTPRequest *request in [self operations]) { + [request setQueue:nil]; + } + [userInfo release]; + [super dealloc]; +} + +- (void)setSuspended:(BOOL)suspend +{ + [super setSuspended:suspend]; +} + +- (void)reset +{ + [self cancelAllOperations]; + [self setDelegate:nil]; + [self setDownloadProgressDelegate:nil]; + [self setUploadProgressDelegate:nil]; + [self setRequestDidStartSelector:NULL]; + [self setRequestDidReceiveResponseHeadersSelector:NULL]; + [self setRequestDidFailSelector:NULL]; + [self setRequestDidFinishSelector:NULL]; + [self setQueueDidFinishSelector:NULL]; + [self setSuspended:YES]; +} + + +- (void)go +{ + [self setSuspended:NO]; +} + +- (void)cancelAllOperations +{ + [self setBytesUploadedSoFar:0]; + [self setTotalBytesToUpload:0]; + [self setBytesDownloadedSoFar:0]; + [self setTotalBytesToDownload:0]; + [super cancelAllOperations]; +} + +- (void)setUploadProgressDelegate:(id)newDelegate +{ + uploadProgressDelegate = newDelegate; + [self resetProgressDelegate:&uploadProgressDelegate]; + +} + +- (void)setDownloadProgressDelegate:(id)newDelegate +{ + downloadProgressDelegate = newDelegate; + [self resetProgressDelegate:&downloadProgressDelegate]; +} + +- (void)resetProgressDelegate:(id *)progressDelegate +{ +#if !TARGET_OS_IPHONE + // If the uploadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can treat it similarly to UIProgressViews + SEL selector = @selector(setMaxValue:); + if ([*progressDelegate respondsToSelector:selector]) { + double max = 1.0; + [ASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&max callerToRetain:nil]; + } + selector = @selector(setDoubleValue:); + if ([*progressDelegate respondsToSelector:selector]) { + double value = 0.0; + [ASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&value callerToRetain:nil]; + } +#else + SEL selector = @selector(setProgress:); + if ([*progressDelegate respondsToSelector:selector]) { + float value = 0.0f; + [ASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&value callerToRetain:nil]; + } +#endif +} + +- (void)addHEADOperation:(NSOperation *)operation +{ + if ([operation isKindOfClass:[ASIHTTPRequest class]]) { + + ASIHTTPRequest *request = (ASIHTTPRequest *)operation; + [request setRequestMethod:@"HEAD"]; + [request setQueuePriority:10]; + [request setShowAccurateProgress:YES]; + [request setQueue:self]; + + // Important - we are calling NSOperation's add method - we don't want to add this as a normal request! + [super addOperation:request]; + } +} + +// Only add ASIHTTPRequests to this queue!! +- (void)addOperation:(NSOperation *)operation +{ + if (![operation isKindOfClass:[ASIHTTPRequest class]]) { + [NSException raise:@"AttemptToAddInvalidRequest" format:@"Attempted to add an object that was not an ASIHTTPRequest to an ASINetworkQueue"]; + } + + [self setRequestsCount:[self requestsCount]+1]; + + ASIHTTPRequest *request = (ASIHTTPRequest *)operation; + + if ([self showAccurateProgress]) { + + // Force the request to build its body (this may change requestMethod) + [request buildPostBody]; + + // If this is a GET request and we want accurate progress, perform a HEAD request first to get the content-length + // We'll only do this before the queue is started + // If requests are added after the queue is started they will probably move the overall progress backwards anyway, so there's no value performing the HEAD requests first + // Instead, they'll update the total progress if and when they receive a content-length header + if ([[request requestMethod] isEqualToString:@"GET"]) { + if ([self isSuspended]) { + ASIHTTPRequest *HEADRequest = [request HEADRequest]; + [self addHEADOperation:HEADRequest]; + [request addDependency:HEADRequest]; + if ([request shouldResetDownloadProgress]) { + [self resetProgressDelegate:&downloadProgressDelegate]; + [request setShouldResetDownloadProgress:NO]; + } + } + } + [request buildPostBody]; + [self request:nil incrementUploadSizeBy:[request postLength]]; + + + } else { + [self request:nil incrementDownloadSizeBy:1]; + [self request:nil incrementUploadSizeBy:1]; + } + // Tell the request not to increment the upload size when it starts, as we've already added its length + if ([request shouldResetUploadProgress]) { + [self resetProgressDelegate:&uploadProgressDelegate]; + [request setShouldResetUploadProgress:NO]; + } + + [request setShowAccurateProgress:[self showAccurateProgress]]; + + [request setQueue:self]; + [super addOperation:request]; + +} + +- (void)requestStarted:(ASIHTTPRequest *)request +{ + if ([self requestDidStartSelector]) { + [[self delegate] performSelector:[self requestDidStartSelector] withObject:request]; + } +} + +- (void)request:(ASIHTTPRequest *)request didReceiveResponseHeaders:(NSDictionary *)responseHeaders +{ + if ([self requestDidReceiveResponseHeadersSelector]) { + [[self delegate] performSelector:[self requestDidReceiveResponseHeadersSelector] withObject:request withObject:responseHeaders]; + } +} + +- (void)request:(ASIHTTPRequest *)request willRedirectToURL:(NSURL *)newURL +{ + if ([self requestWillRedirectSelector]) { + [[self delegate] performSelector:[self requestWillRedirectSelector] withObject:request withObject:newURL]; + } +} + +- (void)requestFinished:(ASIHTTPRequest *)request +{ + [self setRequestsCount:[self requestsCount]-1]; + if ([self requestDidFinishSelector]) { + [[self delegate] performSelector:[self requestDidFinishSelector] withObject:request]; + } + if ([self requestsCount] == 0) { + if ([self queueDidFinishSelector]) { + [[self delegate] performSelector:[self queueDidFinishSelector] withObject:self]; + } + } +} + +- (void)requestFailed:(ASIHTTPRequest *)request +{ + [self setRequestsCount:[self requestsCount]-1]; + if ([self requestDidFailSelector]) { + [[self delegate] performSelector:[self requestDidFailSelector] withObject:request]; + } + if ([self requestsCount] == 0) { + if ([self queueDidFinishSelector]) { + [[self delegate] performSelector:[self queueDidFinishSelector] withObject:self]; + } + } + if ([self shouldCancelAllRequestsOnFailure] && [self requestsCount] > 0) { + [self cancelAllOperations]; + } + +} + + +- (void)request:(ASIHTTPRequest *)request didReceiveBytes:(long long)bytes +{ + [self setBytesDownloadedSoFar:[self bytesDownloadedSoFar]+bytes]; + if ([self downloadProgressDelegate]) { + [ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:[self bytesDownloadedSoFar] ofTotal:[self totalBytesToDownload]]; + } +} + +- (void)request:(ASIHTTPRequest *)request didSendBytes:(long long)bytes +{ + [self setBytesUploadedSoFar:[self bytesUploadedSoFar]+bytes]; + if ([self uploadProgressDelegate]) { + [ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:[self bytesUploadedSoFar] ofTotal:[self totalBytesToUpload]]; + } +} + +- (void)request:(ASIHTTPRequest *)request incrementDownloadSizeBy:(long long)newLength +{ + [self setTotalBytesToDownload:[self totalBytesToDownload]+newLength]; +} + +- (void)request:(ASIHTTPRequest *)request incrementUploadSizeBy:(long long)newLength +{ + [self setTotalBytesToUpload:[self totalBytesToUpload]+newLength]; +} + + +// Since this queue takes over as the delegate for all requests it contains, it should forward authorisation requests to its own delegate +- (void)authenticationNeededForRequest:(ASIHTTPRequest *)request +{ + if ([[self delegate] respondsToSelector:@selector(authenticationNeededForRequest:)]) { + [[self delegate] performSelector:@selector(authenticationNeededForRequest:) withObject:request]; + } +} + +- (void)proxyAuthenticationNeededForRequest:(ASIHTTPRequest *)request +{ + if ([[self delegate] respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) { + [[self delegate] performSelector:@selector(proxyAuthenticationNeededForRequest:) withObject:request]; + } +} + + +- (BOOL)respondsToSelector:(SEL)selector +{ + // We handle certain methods differently because whether our delegate implements them or not can affect how the request should behave + + // If the delegate implements this, the request will stop to wait for credentials + if (selector == @selector(authenticationNeededForRequest:)) { + if ([[self delegate] respondsToSelector:@selector(authenticationNeededForRequest:)]) { + return YES; + } + return NO; + + // If the delegate implements this, the request will to wait for credentials + } else if (selector == @selector(proxyAuthenticationNeededForRequest:)) { + if ([[self delegate] respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) { + return YES; + } + return NO; + + // If the delegate implements requestWillRedirectSelector, the request will stop to allow the delegate to change the url + } else if (selector == @selector(request:willRedirectToURL:)) { + if ([self requestWillRedirectSelector] && [[self delegate] respondsToSelector:[self requestWillRedirectSelector]]) { + return YES; + } + return NO; + } + return [super respondsToSelector:selector]; +} + +#pragma mark NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + ASINetworkQueue *newQueue = [[[self class] alloc] init]; + [newQueue setDelegate:[self delegate]]; + [newQueue setRequestDidStartSelector:[self requestDidStartSelector]]; + [newQueue setRequestWillRedirectSelector:[self requestWillRedirectSelector]]; + [newQueue setRequestDidReceiveResponseHeadersSelector:[self requestDidReceiveResponseHeadersSelector]]; + [newQueue setRequestDidFinishSelector:[self requestDidFinishSelector]]; + [newQueue setRequestDidFailSelector:[self requestDidFailSelector]]; + [newQueue setQueueDidFinishSelector:[self queueDidFinishSelector]]; + [newQueue setUploadProgressDelegate:[self uploadProgressDelegate]]; + [newQueue setDownloadProgressDelegate:[self downloadProgressDelegate]]; + [newQueue setShouldCancelAllRequestsOnFailure:[self shouldCancelAllRequestsOnFailure]]; + [newQueue setShowAccurateProgress:[self showAccurateProgress]]; + [newQueue setUserInfo:[[[self userInfo] copyWithZone:zone] autorelease]]; + return newQueue; +} + + +@synthesize requestsCount; +@synthesize bytesUploadedSoFar; +@synthesize totalBytesToUpload; +@synthesize bytesDownloadedSoFar; +@synthesize totalBytesToDownload; +@synthesize shouldCancelAllRequestsOnFailure; +@synthesize uploadProgressDelegate; +@synthesize downloadProgressDelegate; +@synthesize requestDidStartSelector; +@synthesize requestDidReceiveResponseHeadersSelector; +@synthesize requestWillRedirectSelector; +@synthesize requestDidFinishSelector; +@synthesize requestDidFailSelector; +@synthesize queueDidFinishSelector; +@synthesize delegate; +@synthesize showAccurateProgress; +@synthesize userInfo; +@end diff --git a/Vendor/ASIHTTPRequest/ASIProgressDelegate.h b/Vendor/ASIHTTPRequest/ASIProgressDelegate.h new file mode 100644 index 0000000..e2bb0cf --- /dev/null +++ b/Vendor/ASIHTTPRequest/ASIProgressDelegate.h @@ -0,0 +1,38 @@ +// +// ASIProgressDelegate.h +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest +// +// Created by Ben Copsey on 13/04/2010. +// Copyright 2010 All-Seeing Interactive. All rights reserved. +// + +@class ASIHTTPRequest; + +@protocol ASIProgressDelegate + +@optional + +// These methods are used to update UIProgressViews (iPhone OS) or NSProgressIndicators (Mac OS X) +// If you are using a custom progress delegate, you may find it easier to implement didReceiveBytes / didSendBytes instead +#if TARGET_OS_IPHONE +- (void)setProgress:(float)newProgress; +#else +- (void)setDoubleValue:(double)newProgress; +- (void)setMaxValue:(double)newMax; +#endif + +// Called when the request receives some data - bytes is the length of that data +- (void)request:(ASIHTTPRequest *)request didReceiveBytes:(long long)bytes; + +// Called when the request sends some data +// The first 32KB (128KB on older platforms) of data sent is not included in this amount because of limitations with the CFNetwork API +// bytes may be less than zero if a request needs to remove upload progress (probably because the request needs to run again) +- (void)request:(ASIHTTPRequest *)request didSendBytes:(long long)bytes; + +// Called when a request needs to change the length of the content to download +- (void)request:(ASIHTTPRequest *)request incrementDownloadSizeBy:(long long)newLength; + +// Called when a request needs to change the length of the content to upload +// newLength may be less than zero when a request needs to remove the size of the internal buffer from progress tracking +- (void)request:(ASIHTTPRequest *)request incrementUploadSizeBy:(long long)newLength; +@end diff --git a/Vendor/ASIHTTPRequest/Reachability.h b/Vendor/ASIHTTPRequest/Reachability.h new file mode 100644 index 0000000..b49b797 --- /dev/null +++ b/Vendor/ASIHTTPRequest/Reachability.h @@ -0,0 +1,193 @@ +/* + + File: Reachability.h + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + + Version: 2.0.4ddg + */ + +/* + Significant additions made by Andrew W. Donoho, August 11, 2009. + This is a derived work of Apple's Reachability v2.0 class. + + The below license is the new BSD license with the OSI recommended personalizations. + + + Extensions Copyright (C) 2009 Donoho Design Group, LLC. All Rights Reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Andrew W. Donoho nor Donoho Design Group, L.L.C. + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY DONOHO DESIGN GROUP, L.L.C. "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + */ + + +/* + + Apple's Original License on Reachability v2.0 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2009 Apple Inc. All Rights Reserved. + + */ + + +/* + DDG extensions include: + Each reachability object now has a copy of the key used to store it in a + dictionary. This allows each observer to quickly determine if the event is + important to them. + + -currentReachabilityStatus also has a significantly different decision criteria than + Apple's code. + + A multiple convenience test methods have been added. + */ + +#import +#import + +#define USE_DDG_EXTENSIONS 1 // Use DDG's Extensions to test network criteria. +// Since NSAssert and NSCAssert are used in this code, +// I recommend you set NS_BLOCK_ASSERTIONS=1 in the release versions of your projects. + +enum { + + // DDG NetworkStatus Constant Names. + kNotReachable = 0, // Apple's code depends upon 'NotReachable' being the same value as 'NO'. + kReachableViaWWAN, // Switched order from Apple's enum. WWAN is active before WiFi. + kReachableViaWiFi + +}; +typedef uint32_t NetworkStatus; + +enum { + + // Apple NetworkStatus Constant Names. + NotReachable = kNotReachable, + ReachableViaWiFi = kReachableViaWiFi, + ReachableViaWWAN = kReachableViaWWAN + +}; + + +extern NSString *const kInternetConnection; +extern NSString *const kLocalWiFiConnection; +extern NSString *const kReachabilityChangedNotification; + +@interface Reachability: NSObject { + +@private + NSString *key_; + SCNetworkReachabilityRef reachabilityRef; + +} + +@property (copy) NSString *key; // Atomic because network operations are asynchronous. + +// Designated Initializer. +- (Reachability *) initWithReachabilityRef: (SCNetworkReachabilityRef) ref; + +// Use to check the reachability of a particular host name. ++ (Reachability *) reachabilityWithHostName: (NSString*) hostName; + +// Use to check the reachability of a particular IP address. ++ (Reachability *) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress; + +// Use to check whether the default route is available. +// Should be used to, at minimum, establish network connectivity. ++ (Reachability *) reachabilityForInternetConnection; + +// Use to check whether a local wifi connection is available. ++ (Reachability *) reachabilityForLocalWiFi; + +//Start listening for reachability notifications on the current run loop. +- (BOOL) startNotifier; +- (void) stopNotifier; + +// Comparison routines to enable choosing actions in a notification. +- (BOOL) isEqual: (Reachability *) r; + +// These are the status tests. +- (NetworkStatus) currentReachabilityStatus; + +// The main direct test of reachability. +- (BOOL) isReachable; + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +- (BOOL) isConnectionRequired; // Identical DDG variant. +- (BOOL) connectionRequired; // Apple's routine. + +// Dynamic, on demand connection? +- (BOOL) isConnectionOnDemand; + +// Is user intervention required? +- (BOOL) isInterventionRequired; + +// Routines for specific connection testing by your app. +- (BOOL) isReachableViaWWAN; +- (BOOL) isReachableViaWiFi; + +- (SCNetworkReachabilityFlags) reachabilityFlags; + +@end diff --git a/Vendor/ASIHTTPRequest/Reachability.m b/Vendor/ASIHTTPRequest/Reachability.m new file mode 100644 index 0000000..efe99b3 --- /dev/null +++ b/Vendor/ASIHTTPRequest/Reachability.m @@ -0,0 +1,814 @@ +/* + + File: Reachability.m + Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + + Version: 2.0.4ddg + */ + +/* + Significant additions made by Andrew W. Donoho, August 11, 2009. + This is a derived work of Apple's Reachability v2.0 class. + + The below license is the new BSD license with the OSI recommended personalizations. + + + Extensions Copyright (C) 2009 Donoho Design Group, LLC. All Rights Reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Andrew W. Donoho nor Donoho Design Group, L.L.C. + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY DONOHO DESIGN GROUP, L.L.C. "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + */ + + +/* + + Apple's Original License on Reachability v2.0 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under + Apple's copyrights in this original Apple software (the "Apple Software"), to + use, reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions + of the Apple Software. + + Neither the name, trademarks, service marks or logos of Apple Inc. may be used + to endorse or promote products derived from the Apple Software without specific + prior written permission from Apple. Except as expressly stated in this notice, + no other rights or licenses, express or implied, are granted by Apple herein, + including but not limited to any patent rights that may be infringed by your + derivative works or by other works in which the Apple Software may be + incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR + DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF + CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF + APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2009 Apple Inc. All Rights Reserved. + +*/ + +/* + Each reachability object now has a copy of the key used to store it in a dictionary. + This allows each observer to quickly determine if the event is important to them. +*/ + +#import +#import +#import +#import +#import +#import + +#import + +#import "Reachability.h" + +NSString *const kInternetConnection = @"InternetConnection"; +NSString *const kLocalWiFiConnection = @"LocalWiFiConnection"; +NSString *const kReachabilityChangedNotification = @"NetworkReachabilityChangedNotification"; + +#define CLASS_DEBUG 1 // Turn on logReachabilityFlags. Must also have a project wide defined DEBUG. + +#if (defined DEBUG && defined CLASS_DEBUG) +#define logReachabilityFlags(flags) (logReachabilityFlags_(__PRETTY_FUNCTION__, __LINE__, flags)) + +static NSString *reachabilityFlags_(SCNetworkReachabilityFlags flags) { + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 30000) // Apple advises you to use the magic number instead of a symbol. + return [NSString stringWithFormat:@"Reachability Flags: %c%c %c%c%c%c%c%c%c", + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; +#else + // Compile out the v3.0 features for v2.2.1 deployment. + return [NSString stringWithFormat:@"Reachability Flags: %c%c %c%c%c%c%c%c", + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + // v3 kSCNetworkReachabilityFlagsConnectionOnTraffic == v2 kSCNetworkReachabilityFlagsConnectionAutomatic + (flags & kSCNetworkReachabilityFlagsConnectionAutomatic) ? 'C' : '-', + // (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', // No v2 equivalent. + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; +#endif + +} // reachabilityFlags_() + +static void logReachabilityFlags_(const char *name, int line, SCNetworkReachabilityFlags flags) { + + NSLog(@"%s (%d) \n\t%@", name, line, reachabilityFlags_(flags)); + +} // logReachabilityFlags_() + +#define logNetworkStatus(status) (logNetworkStatus_(__PRETTY_FUNCTION__, __LINE__, status)) + +static void logNetworkStatus_(const char *name, int line, NetworkStatus status) { + + NSString *statusString = nil; + + switch (status) { + case kNotReachable: + statusString = [NSString stringWithString: @"Not Reachable"]; + break; + case kReachableViaWWAN: + statusString = [NSString stringWithString: @"Reachable via WWAN"]; + break; + case kReachableViaWiFi: + statusString = [NSString stringWithString: @"Reachable via WiFi"]; + break; + } + + NSLog(@"%s (%d) \n\tNetwork Status: %@", name, line, statusString); + +} // logNetworkStatus_() + +#else +#define logReachabilityFlags(flags) +#define logNetworkStatus(status) +#endif + +@interface Reachability (private) + +- (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags; + +@end + +@implementation Reachability + +@synthesize key = key_; + +// Preclude direct access to ivars. ++ (BOOL) accessInstanceVariablesDirectly { + + return NO; + +} // accessInstanceVariablesDirectly + + +- (void) dealloc { + + [self stopNotifier]; + if(reachabilityRef) { + + CFRelease(reachabilityRef); reachabilityRef = NULL; + + } + + self.key = nil; + + [super dealloc]; + +} // dealloc + + +- (Reachability *) initWithReachabilityRef: (SCNetworkReachabilityRef) ref +{ + self = [super init]; + if (self != nil) + { + reachabilityRef = ref; + } + + return self; + +} // initWithReachabilityRef: + + +#if (defined DEBUG && defined CLASS_DEBUG) +- (NSString *) description { + + NSAssert(reachabilityRef, @"-description called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + + SCNetworkReachabilityGetFlags(reachabilityRef, &flags); + + return [NSString stringWithFormat: @"%@\n\t%@", self.key, reachabilityFlags_(flags)]; + +} // description +#endif + + +#pragma mark - +#pragma mark Notification Management Methods + + +//Start listening for reachability notifications on the current run loop +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) { + + #pragma unused (target, flags) + NSCAssert(info, @"info was NULL in ReachabilityCallback"); + NSCAssert([(NSObject*) info isKindOfClass: [Reachability class]], @"info was the wrong class in ReachabilityCallback"); + + //We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively + // in case someone uses the Reachablity object in a different thread. + NSAutoreleasePool* pool = [NSAutoreleasePool new]; + + // Post a notification to notify the client that the network reachability changed. + [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification + object: (Reachability *) info]; + + [pool release]; + +} // ReachabilityCallback() + + +- (BOOL) startNotifier { + + SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL}; + + if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) { + + if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) { + + return YES; + + } + + } + + return NO; + +} // startNotifier + + +- (void) stopNotifier { + + if(reachabilityRef) { + + SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + + } + +} // stopNotifier + + +- (BOOL) isEqual: (Reachability *) r { + + return [r.key isEqualToString: self.key]; + +} // isEqual: + + +#pragma mark - +#pragma mark Reachability Allocation Methods + + ++ (Reachability *) reachabilityWithHostName: (NSString *) hostName { + + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); + + if (ref) { + + Reachability *r = [[[self alloc] initWithReachabilityRef: ref] autorelease]; + + r.key = hostName; + + return r; + + } + + return nil; + +} // reachabilityWithHostName + + ++ (NSString *) makeAddressKey: (in_addr_t) addr { + // addr is assumed to be in network byte order. + + static const int highShift = 24; + static const int highMidShift = 16; + static const int lowMidShift = 8; + static const in_addr_t mask = 0x000000ff; + + addr = ntohl(addr); + + return [NSString stringWithFormat: @"%d.%d.%d.%d", + (addr >> highShift) & mask, + (addr >> highMidShift) & mask, + (addr >> lowMidShift) & mask, + addr & mask]; + +} // makeAddressKey: + + ++ (Reachability *) reachabilityWithAddress: (const struct sockaddr_in *) hostAddress { + + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + + if (ref) { + + Reachability *r = [[[self alloc] initWithReachabilityRef: ref] autorelease]; + + r.key = [self makeAddressKey: hostAddress->sin_addr.s_addr]; + + return r; + + } + + return nil; + +} // reachabilityWithAddress + + ++ (Reachability *) reachabilityForInternetConnection { + + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + Reachability *r = [self reachabilityWithAddress: &zeroAddress]; + + r.key = kInternetConnection; + + return r; + +} // reachabilityForInternetConnection + + ++ (Reachability *) reachabilityForLocalWiFi { + + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + + Reachability *r = [self reachabilityWithAddress: &localWifiAddress]; + + r.key = kLocalWiFiConnection; + + return r; + +} // reachabilityForLocalWiFi + + +#pragma mark - +#pragma mark Network Flag Handling Methods + + +#if USE_DDG_EXTENSIONS +// +// iPhone condition codes as reported by a 3GS running iPhone OS v3.0. +// Airplane Mode turned on: Reachability Flag Status: -- ------- +// WWAN Active: Reachability Flag Status: WR -t----- +// WWAN Connection required: Reachability Flag Status: WR ct----- +// WiFi turned on: Reachability Flag Status: -R ------- Reachable. +// Local WiFi turned on: Reachability Flag Status: -R xxxxxxd Reachable. +// WiFi turned on: Reachability Flag Status: -R ct----- Connection down. (Non-intuitive, empirically determined answer.) +const SCNetworkReachabilityFlags kConnectionDown = kSCNetworkReachabilityFlagsConnectionRequired | + kSCNetworkReachabilityFlagsTransientConnection; +// WiFi turned on: Reachability Flag Status: -R ct-i--- Reachable but it will require user intervention (e.g. enter a WiFi password). +// WiFi turned on: Reachability Flag Status: -R -t----- Reachable via VPN. +// +// In the below method, an 'x' in the flag status means I don't care about its value. +// +// This method differs from Apple's by testing explicitly for empirically observed values. +// This gives me more confidence in it's correct behavior. Apple's code covers more cases +// than mine. My code covers the cases that occur. +// +- (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags { + + if (flags & kSCNetworkReachabilityFlagsReachable) { + + // Local WiFi -- Test derived from Apple's code: -localWiFiStatusForFlags:. + if (self.key == kLocalWiFiConnection) { + + // Reachability Flag Status: xR xxxxxxd Reachable. + return (flags & kSCNetworkReachabilityFlagsIsDirect) ? kReachableViaWiFi : kNotReachable; + + } + + // Observed WWAN Values: + // WWAN Active: Reachability Flag Status: WR -t----- + // WWAN Connection required: Reachability Flag Status: WR ct----- + // + // Test Value: Reachability Flag Status: WR xxxxxxx + if (flags & kSCNetworkReachabilityFlagsIsWWAN) { return kReachableViaWWAN; } + + // Clear moot bits. + flags &= ~kSCNetworkReachabilityFlagsReachable; + flags &= ~kSCNetworkReachabilityFlagsIsDirect; + flags &= ~kSCNetworkReachabilityFlagsIsLocalAddress; // kInternetConnection is local. + + // Reachability Flag Status: -R ct---xx Connection down. + if (flags == kConnectionDown) { return kNotReachable; } + + // Reachability Flag Status: -R -t---xx Reachable. WiFi + VPN(is up) (Thank you Ling Wang) + if (flags & kSCNetworkReachabilityFlagsTransientConnection) { return kReachableViaWiFi; } + + // Reachability Flag Status: -R -----xx Reachable. + if (flags == 0) { return kReachableViaWiFi; } + + // Apple's code tests for dynamic connection types here. I don't. + // If a connection is required, regardless of whether it is on demand or not, it is a WiFi connection. + // If you care whether a connection needs to be brought up, use -isConnectionRequired. + // If you care about whether user intervention is necessary, use -isInterventionRequired. + // If you care about dynamically establishing the connection, use -isConnectionIsOnDemand. + + // Reachability Flag Status: -R cxxxxxx Reachable. + if (flags & kSCNetworkReachabilityFlagsConnectionRequired) { return kReachableViaWiFi; } + + // Required by the compiler. Should never get here. Default to not connected. +#if (defined DEBUG && defined CLASS_DEBUG) + NSAssert1(NO, @"Uncaught reachability test. Flags: %@", reachabilityFlags_(flags)); +#endif + return kNotReachable; + + } + + // Reachability Flag Status: x- xxxxxxx + return kNotReachable; + +} // networkStatusForFlags: + + +- (NetworkStatus) currentReachabilityStatus { + + NSAssert(reachabilityRef, @"currentReachabilityStatus called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + NetworkStatus status = kNotReachable; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + +// logReachabilityFlags(flags); + + status = [self networkStatusForFlags: flags]; + + return status; + + } + + return kNotReachable; + +} // currentReachabilityStatus + + +- (BOOL) isReachable { + + NSAssert(reachabilityRef, @"isReachable called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + NetworkStatus status = kNotReachable; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + +// logReachabilityFlags(flags); + + status = [self networkStatusForFlags: flags]; + +// logNetworkStatus(status); + + return (kNotReachable != status); + + } + + return NO; + +} // isReachable + + +- (BOOL) isConnectionRequired { + + NSAssert(reachabilityRef, @"isConnectionRequired called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + + } + + return NO; + +} // isConnectionRequired + + +- (BOOL) connectionRequired { + + return [self isConnectionRequired]; + +} // connectionRequired +#endif + + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 30000) +static const SCNetworkReachabilityFlags kOnDemandConnection = kSCNetworkReachabilityFlagsConnectionOnTraffic | + kSCNetworkReachabilityFlagsConnectionOnDemand; +#else +static const SCNetworkReachabilityFlags kOnDemandConnection = kSCNetworkReachabilityFlagsConnectionAutomatic; +#endif + +- (BOOL) isConnectionOnDemand { + + NSAssert(reachabilityRef, @"isConnectionIsOnDemand called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & kOnDemandConnection)); + + } + + return NO; + +} // isConnectionOnDemand + + +- (BOOL) isInterventionRequired { + + NSAssert(reachabilityRef, @"isInterventionRequired called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & kSCNetworkReachabilityFlagsInterventionRequired)); + + } + + return NO; + +} // isInterventionRequired + + +- (BOOL) isReachableViaWWAN { + + NSAssert(reachabilityRef, @"isReachableViaWWAN called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + NetworkStatus status = kNotReachable; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + status = [self networkStatusForFlags: flags]; + + return (kReachableViaWWAN == status); + + } + + return NO; + +} // isReachableViaWWAN + + +- (BOOL) isReachableViaWiFi { + + NSAssert(reachabilityRef, @"isReachableViaWiFi called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + NetworkStatus status = kNotReachable; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + status = [self networkStatusForFlags: flags]; + + return (kReachableViaWiFi == status); + + } + + return NO; + +} // isReachableViaWiFi + + +- (SCNetworkReachabilityFlags) reachabilityFlags { + + NSAssert(reachabilityRef, @"reachabilityFlags called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + return flags; + + } + + return 0; + +} // reachabilityFlags + + +#pragma mark - +#pragma mark Apple's Network Flag Handling Methods + + +#if !USE_DDG_EXTENSIONS +/* + * + * Apple's Network Status testing code. + * The only changes that have been made are to use the new logReachabilityFlags macro and + * test for local WiFi via the key instead of Apple's boolean. Also, Apple's code was for v3.0 only + * iPhone OS. v2.2.1 and earlier conditional compiling is turned on. Hence, to mirror Apple's behavior, + * set your Base SDK to v3.0 or higher. + * + */ + +- (NetworkStatus) localWiFiStatusForFlags: (SCNetworkReachabilityFlags) flags +{ + logReachabilityFlags(flags); + + BOOL retVal = NotReachable; + if((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect)) + { + retVal = ReachableViaWiFi; + } + return retVal; +} + + +- (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags +{ + logReachabilityFlags(flags); + if (!(flags & kSCNetworkReachabilityFlagsReachable)) + { + // if target host is not reachable + return NotReachable; + } + + BOOL retVal = NotReachable; + + if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) + { + // if target host is reachable and no connection is required + // then we'll assume (for now) that your on Wi-Fi + retVal = ReachableViaWiFi; + } + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 30000) // Apple advises you to use the magic number instead of a symbol. + if ((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) || + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic)) +#else + if (flags & kSCNetworkReachabilityFlagsConnectionAutomatic) +#endif + { + // ... and the connection is on-demand (or on-traffic) if the + // calling application is using the CFSocketStream or higher APIs + + if (!(flags & kSCNetworkReachabilityFlagsInterventionRequired)) + { + // ... and no [user] intervention is needed + retVal = ReachableViaWiFi; + } + } + + if (flags & kSCNetworkReachabilityFlagsIsWWAN) + { + // ... but WWAN connections are OK if the calling application + // is using the CFNetwork (CFSocketStream?) APIs. + retVal = ReachableViaWWAN; + } + return retVal; +} + + +- (NetworkStatus) currentReachabilityStatus +{ + NSAssert(reachabilityRef, @"currentReachabilityStatus called with NULL reachabilityRef"); + + NetworkStatus retVal = NotReachable; + SCNetworkReachabilityFlags flags; + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + if(self.key == kLocalWiFiConnection) + { + retVal = [self localWiFiStatusForFlags: flags]; + } + else + { + retVal = [self networkStatusForFlags: flags]; + } + } + return retVal; +} + + +- (BOOL) isReachable { + + NSAssert(reachabilityRef, @"isReachable called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags = 0; + NetworkStatus status = kNotReachable; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + if(self.key == kLocalWiFiConnection) { + + status = [self localWiFiStatusForFlags: flags]; + + } else { + + status = [self networkStatusForFlags: flags]; + + } + + return (kNotReachable != status); + + } + + return NO; + +} // isReachable + + +- (BOOL) isConnectionRequired { + + return [self connectionRequired]; + +} // isConnectionRequired + + +- (BOOL) connectionRequired { + + NSAssert(reachabilityRef, @"connectionRequired called with NULL reachabilityRef"); + + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { + + logReachabilityFlags(flags); + + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + + } + + return NO; + +} // connectionRequired +#endif + +@end diff --git a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj index 73dbd04..3487664 100755 --- a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj +++ b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj @@ -17,19 +17,19 @@ 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; }; 28C286E10D94DF7D0034E888 /* RootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C286E00D94DF7D0034E888 /* RootViewController.m */; }; 28F335F11007B36200424DE2 /* RootViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28F335F01007B36200424DE2 /* RootViewController.xib */; }; - 4A97CB751332094B009028BE /* ASIAuthenticationDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB621332094B009028BE /* ASIAuthenticationDialog.m */; }; - 4A97CB761332094B009028BE /* ASIDataCompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB651332094B009028BE /* ASIDataCompressor.m */; }; - 4A97CB771332094B009028BE /* ASIDataDecompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB671332094B009028BE /* ASIDataDecompressor.m */; }; - 4A97CB781332094B009028BE /* ASIDownloadCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB691332094B009028BE /* ASIDownloadCache.m */; }; - 4A97CB791332094B009028BE /* ASIFormDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB6B1332094B009028BE /* ASIFormDataRequest.m */; }; - 4A97CB7A1332094B009028BE /* ASIHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB6D1332094B009028BE /* ASIHTTPRequest.m */; }; - 4A97CB7B1332094B009028BE /* ASIInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB711332094B009028BE /* ASIInputStream.m */; }; - 4A97CB7C1332094B009028BE /* ASINetworkQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB731332094B009028BE /* ASINetworkQueue.m */; }; - 4A97CB7F1332096A009028BE /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A97CB7E1332096A009028BE /* Reachability.m */; }; 4A97CBDB1332314C009028BE /* libz.1.2.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A97CBDA1332314C009028BE /* libz.1.2.3.dylib */; }; 4A97CBDF13323191009028BE /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A97CBDE13323191009028BE /* MobileCoreServices.framework */; }; 4AD0F4E3133C82F600F442D9 /* BoshTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD0F4E2133C82F600F442D9 /* BoshTransport.m */; }; 4AD0F6511340F88800F442D9 /* BoshWindowManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD0F6501340F88800F442D9 /* BoshWindowManager.m */; }; + 4ADAD416134222CE00F6E4B3 /* ASIAuthenticationDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD401134222CE00F6E4B3 /* ASIAuthenticationDialog.m */; }; + 4ADAD417134222CE00F6E4B3 /* ASIDataCompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD404134222CE00F6E4B3 /* ASIDataCompressor.m */; }; + 4ADAD418134222CE00F6E4B3 /* ASIDataDecompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD406134222CE00F6E4B3 /* ASIDataDecompressor.m */; }; + 4ADAD419134222CE00F6E4B3 /* ASIDownloadCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD408134222CE00F6E4B3 /* ASIDownloadCache.m */; }; + 4ADAD41A134222CE00F6E4B3 /* ASIFormDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD40A134222CE00F6E4B3 /* ASIFormDataRequest.m */; }; + 4ADAD41B134222CE00F6E4B3 /* ASIHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD40C134222CE00F6E4B3 /* ASIHTTPRequest.m */; }; + 4ADAD41C134222CE00F6E4B3 /* ASIInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD410134222CE00F6E4B3 /* ASIInputStream.m */; }; + 4ADAD41D134222CE00F6E4B3 /* ASINetworkQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD412134222CE00F6E4B3 /* ASINetworkQueue.m */; }; + 4ADAD41E134222CE00F6E4B3 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD415134222CE00F6E4B3 /* Reachability.m */; }; 54FDBEC41330DA030099D4DB /* XMPPSocketTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FDBEC31330DA030099D4DB /* XMPPSocketTransport.m */; }; DC1F97E21152CA2D00138A8F /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1F97E11152CA2D00138A8F /* libxml2.dylib */; }; DC1F97E81152CA4E00138A8F /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1F97E71152CA4E00138A8F /* CFNetwork.framework */; }; @@ -80,34 +80,34 @@ 28C286E00D94DF7D0034E888 /* RootViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RootViewController.m; sourceTree = ""; }; 28F335F01007B36200424DE2 /* RootViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RootViewController.xib; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 4A97CB611332094B009028BE /* ASIAuthenticationDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIAuthenticationDialog.h; path = "../../../asi-http-request/Classes/ASIAuthenticationDialog.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB621332094B009028BE /* ASIAuthenticationDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIAuthenticationDialog.m; path = "../../../asi-http-request/Classes/ASIAuthenticationDialog.m"; sourceTree = SOURCE_ROOT; }; - 4A97CB631332094B009028BE /* ASICacheDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASICacheDelegate.h; path = "../../../asi-http-request/Classes/ASICacheDelegate.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB641332094B009028BE /* ASIDataCompressor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIDataCompressor.h; path = "../../../asi-http-request/Classes/ASIDataCompressor.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB651332094B009028BE /* ASIDataCompressor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIDataCompressor.m; path = "../../../asi-http-request/Classes/ASIDataCompressor.m"; sourceTree = SOURCE_ROOT; }; - 4A97CB661332094B009028BE /* ASIDataDecompressor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIDataDecompressor.h; path = "../../../asi-http-request/Classes/ASIDataDecompressor.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB671332094B009028BE /* ASIDataDecompressor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIDataDecompressor.m; path = "../../../asi-http-request/Classes/ASIDataDecompressor.m"; sourceTree = SOURCE_ROOT; }; - 4A97CB681332094B009028BE /* ASIDownloadCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIDownloadCache.h; path = "../../../asi-http-request/Classes/ASIDownloadCache.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB691332094B009028BE /* ASIDownloadCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIDownloadCache.m; path = "../../../asi-http-request/Classes/ASIDownloadCache.m"; sourceTree = SOURCE_ROOT; }; - 4A97CB6A1332094B009028BE /* ASIFormDataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIFormDataRequest.h; path = "../../../asi-http-request/Classes/ASIFormDataRequest.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB6B1332094B009028BE /* ASIFormDataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIFormDataRequest.m; path = "../../../asi-http-request/Classes/ASIFormDataRequest.m"; sourceTree = SOURCE_ROOT; }; - 4A97CB6C1332094B009028BE /* ASIHTTPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIHTTPRequest.h; path = "../../../asi-http-request/Classes/ASIHTTPRequest.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB6D1332094B009028BE /* ASIHTTPRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIHTTPRequest.m; path = "../../../asi-http-request/Classes/ASIHTTPRequest.m"; sourceTree = SOURCE_ROOT; }; - 4A97CB6E1332094B009028BE /* ASIHTTPRequestConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIHTTPRequestConfig.h; path = "../../../asi-http-request/Classes/ASIHTTPRequestConfig.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB6F1332094B009028BE /* ASIHTTPRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIHTTPRequestDelegate.h; path = "../../../asi-http-request/Classes/ASIHTTPRequestDelegate.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB701332094B009028BE /* ASIInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIInputStream.h; path = "../../../asi-http-request/Classes/ASIInputStream.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB711332094B009028BE /* ASIInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIInputStream.m; path = "../../../asi-http-request/Classes/ASIInputStream.m"; sourceTree = SOURCE_ROOT; }; - 4A97CB721332094B009028BE /* ASINetworkQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASINetworkQueue.h; path = "../../../asi-http-request/Classes/ASINetworkQueue.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB731332094B009028BE /* ASINetworkQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASINetworkQueue.m; path = "../../../asi-http-request/Classes/ASINetworkQueue.m"; sourceTree = SOURCE_ROOT; }; - 4A97CB741332094B009028BE /* ASIProgressDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIProgressDelegate.h; path = "../../../asi-http-request/Classes/ASIProgressDelegate.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB7D1332096A009028BE /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Reachability.h; path = "../../../asi-http-request/External/Reachability/Reachability.h"; sourceTree = SOURCE_ROOT; }; - 4A97CB7E1332096A009028BE /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Reachability.m; path = "../../../asi-http-request/External/Reachability/Reachability.m"; sourceTree = SOURCE_ROOT; }; 4A97CBDA1332314C009028BE /* libz.1.2.3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.1.2.3.dylib; path = usr/lib/libz.1.2.3.dylib; sourceTree = SDKROOT; }; 4A97CBDE13323191009028BE /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 4AD0F4E1133C82F600F442D9 /* BoshTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BoshTransport.h; path = ../../Core/Transports/BoshTransport.h; sourceTree = SOURCE_ROOT; }; 4AD0F4E2133C82F600F442D9 /* BoshTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BoshTransport.m; path = ../../Core/Transports/BoshTransport.m; sourceTree = SOURCE_ROOT; }; 4AD0F64F1340F88800F442D9 /* BoshWindowManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BoshWindowManager.h; path = ../../Core/Transports/BoshWindowManager.h; sourceTree = SOURCE_ROOT; }; 4AD0F6501340F88800F442D9 /* BoshWindowManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BoshWindowManager.m; path = ../../Core/Transports/BoshWindowManager.m; sourceTree = SOURCE_ROOT; }; + 4ADAD400134222CE00F6E4B3 /* ASIAuthenticationDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIAuthenticationDialog.h; path = ../../Vendor/ASIHTTPRequest/ASIAuthenticationDialog.h; sourceTree = ""; }; + 4ADAD401134222CE00F6E4B3 /* ASIAuthenticationDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIAuthenticationDialog.m; path = ../../Vendor/ASIHTTPRequest/ASIAuthenticationDialog.m; sourceTree = ""; }; + 4ADAD402134222CE00F6E4B3 /* ASICacheDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASICacheDelegate.h; path = ../../Vendor/ASIHTTPRequest/ASICacheDelegate.h; sourceTree = ""; }; + 4ADAD403134222CE00F6E4B3 /* ASIDataCompressor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIDataCompressor.h; path = ../../Vendor/ASIHTTPRequest/ASIDataCompressor.h; sourceTree = ""; }; + 4ADAD404134222CE00F6E4B3 /* ASIDataCompressor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIDataCompressor.m; path = ../../Vendor/ASIHTTPRequest/ASIDataCompressor.m; sourceTree = ""; }; + 4ADAD405134222CE00F6E4B3 /* ASIDataDecompressor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIDataDecompressor.h; path = ../../Vendor/ASIHTTPRequest/ASIDataDecompressor.h; sourceTree = ""; }; + 4ADAD406134222CE00F6E4B3 /* ASIDataDecompressor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIDataDecompressor.m; path = ../../Vendor/ASIHTTPRequest/ASIDataDecompressor.m; sourceTree = ""; }; + 4ADAD407134222CE00F6E4B3 /* ASIDownloadCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIDownloadCache.h; path = ../../Vendor/ASIHTTPRequest/ASIDownloadCache.h; sourceTree = ""; }; + 4ADAD408134222CE00F6E4B3 /* ASIDownloadCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIDownloadCache.m; path = ../../Vendor/ASIHTTPRequest/ASIDownloadCache.m; sourceTree = ""; }; + 4ADAD409134222CE00F6E4B3 /* ASIFormDataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIFormDataRequest.h; path = ../../Vendor/ASIHTTPRequest/ASIFormDataRequest.h; sourceTree = ""; }; + 4ADAD40A134222CE00F6E4B3 /* ASIFormDataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIFormDataRequest.m; path = ../../Vendor/ASIHTTPRequest/ASIFormDataRequest.m; sourceTree = ""; }; + 4ADAD40B134222CE00F6E4B3 /* ASIHTTPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIHTTPRequest.h; path = ../../Vendor/ASIHTTPRequest/ASIHTTPRequest.h; sourceTree = ""; }; + 4ADAD40C134222CE00F6E4B3 /* ASIHTTPRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIHTTPRequest.m; path = ../../Vendor/ASIHTTPRequest/ASIHTTPRequest.m; sourceTree = ""; }; + 4ADAD40D134222CE00F6E4B3 /* ASIHTTPRequestConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIHTTPRequestConfig.h; path = ../../Vendor/ASIHTTPRequest/ASIHTTPRequestConfig.h; sourceTree = ""; }; + 4ADAD40E134222CE00F6E4B3 /* ASIHTTPRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIHTTPRequestDelegate.h; path = ../../Vendor/ASIHTTPRequest/ASIHTTPRequestDelegate.h; sourceTree = ""; }; + 4ADAD40F134222CE00F6E4B3 /* ASIInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIInputStream.h; path = ../../Vendor/ASIHTTPRequest/ASIInputStream.h; sourceTree = ""; }; + 4ADAD410134222CE00F6E4B3 /* ASIInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIInputStream.m; path = ../../Vendor/ASIHTTPRequest/ASIInputStream.m; sourceTree = ""; }; + 4ADAD411134222CE00F6E4B3 /* ASINetworkQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASINetworkQueue.h; path = ../../Vendor/ASIHTTPRequest/ASINetworkQueue.h; sourceTree = ""; }; + 4ADAD412134222CE00F6E4B3 /* ASINetworkQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASINetworkQueue.m; path = ../../Vendor/ASIHTTPRequest/ASINetworkQueue.m; sourceTree = ""; }; + 4ADAD413134222CE00F6E4B3 /* ASIProgressDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIProgressDelegate.h; path = ../../Vendor/ASIHTTPRequest/ASIProgressDelegate.h; sourceTree = ""; }; + 4ADAD414134222CE00F6E4B3 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Reachability.h; path = ../../Vendor/ASIHTTPRequest/Reachability.h; sourceTree = ""; }; + 4ADAD415134222CE00F6E4B3 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Reachability.m; path = ../../Vendor/ASIHTTPRequest/Reachability.m; sourceTree = ""; }; 54FDBEC01330D8460099D4DB /* XMPPTransportProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPTransportProtocol.h; path = ../../Core/XMPPTransportProtocol.h; sourceTree = ""; }; 54FDBEC21330DA030099D4DB /* XMPPSocketTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XMPPSocketTransport.h; path = ../../Core/Transports/XMPPSocketTransport.h; sourceTree = ""; }; 54FDBEC31330DA030099D4DB /* XMPPSocketTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPSocketTransport.m; path = ../../Core/Transports/XMPPSocketTransport.m; sourceTree = ""; }; @@ -267,33 +267,33 @@ name = Frameworks; sourceTree = ""; }; - 4A97CB431331FC49009028BE /* HTTP */ = { + 4ADAD3FF1342229600F6E4B3 /* ASIHTTP */ = { isa = PBXGroup; children = ( - 4A97CB7D1332096A009028BE /* Reachability.h */, - 4A97CB7E1332096A009028BE /* Reachability.m */, - 4A97CB611332094B009028BE /* ASIAuthenticationDialog.h */, - 4A97CB621332094B009028BE /* ASIAuthenticationDialog.m */, - 4A97CB631332094B009028BE /* ASICacheDelegate.h */, - 4A97CB641332094B009028BE /* ASIDataCompressor.h */, - 4A97CB651332094B009028BE /* ASIDataCompressor.m */, - 4A97CB661332094B009028BE /* ASIDataDecompressor.h */, - 4A97CB671332094B009028BE /* ASIDataDecompressor.m */, - 4A97CB681332094B009028BE /* ASIDownloadCache.h */, - 4A97CB691332094B009028BE /* ASIDownloadCache.m */, - 4A97CB6A1332094B009028BE /* ASIFormDataRequest.h */, - 4A97CB6B1332094B009028BE /* ASIFormDataRequest.m */, - 4A97CB6C1332094B009028BE /* ASIHTTPRequest.h */, - 4A97CB6D1332094B009028BE /* ASIHTTPRequest.m */, - 4A97CB6E1332094B009028BE /* ASIHTTPRequestConfig.h */, - 4A97CB6F1332094B009028BE /* ASIHTTPRequestDelegate.h */, - 4A97CB701332094B009028BE /* ASIInputStream.h */, - 4A97CB711332094B009028BE /* ASIInputStream.m */, - 4A97CB721332094B009028BE /* ASINetworkQueue.h */, - 4A97CB731332094B009028BE /* ASINetworkQueue.m */, - 4A97CB741332094B009028BE /* ASIProgressDelegate.h */, + 4ADAD400134222CE00F6E4B3 /* ASIAuthenticationDialog.h */, + 4ADAD401134222CE00F6E4B3 /* ASIAuthenticationDialog.m */, + 4ADAD402134222CE00F6E4B3 /* ASICacheDelegate.h */, + 4ADAD403134222CE00F6E4B3 /* ASIDataCompressor.h */, + 4ADAD404134222CE00F6E4B3 /* ASIDataCompressor.m */, + 4ADAD405134222CE00F6E4B3 /* ASIDataDecompressor.h */, + 4ADAD406134222CE00F6E4B3 /* ASIDataDecompressor.m */, + 4ADAD407134222CE00F6E4B3 /* ASIDownloadCache.h */, + 4ADAD408134222CE00F6E4B3 /* ASIDownloadCache.m */, + 4ADAD409134222CE00F6E4B3 /* ASIFormDataRequest.h */, + 4ADAD40A134222CE00F6E4B3 /* ASIFormDataRequest.m */, + 4ADAD40B134222CE00F6E4B3 /* ASIHTTPRequest.h */, + 4ADAD40C134222CE00F6E4B3 /* ASIHTTPRequest.m */, + 4ADAD40D134222CE00F6E4B3 /* ASIHTTPRequestConfig.h */, + 4ADAD40E134222CE00F6E4B3 /* ASIHTTPRequestDelegate.h */, + 4ADAD40F134222CE00F6E4B3 /* ASIInputStream.h */, + 4ADAD410134222CE00F6E4B3 /* ASIInputStream.m */, + 4ADAD411134222CE00F6E4B3 /* ASINetworkQueue.h */, + 4ADAD412134222CE00F6E4B3 /* ASINetworkQueue.m */, + 4ADAD413134222CE00F6E4B3 /* ASIProgressDelegate.h */, + 4ADAD414134222CE00F6E4B3 /* Reachability.h */, + 4ADAD415134222CE00F6E4B3 /* Reachability.m */, ); - name = HTTP; + name = ASIHTTP; sourceTree = ""; }; 54FDBEC51330DA0C0099D4DB /* Transports */ = { @@ -458,7 +458,7 @@ DC84BBA0124409CE0055A459 /* Vendor */ = { isa = PBXGroup; children = ( - 4A97CB431331FC49009028BE /* HTTP */, + 4ADAD3FF1342229600F6E4B3 /* ASIHTTP */, DC84BBA1124409D60055A459 /* IDN */, DC1F97E31152CA3500138A8F /* TCP */, DC1F97D01152C9DC00138A8F /* KissXML */, @@ -567,17 +567,17 @@ 0771B81D1315E9FB0018D5C6 /* XMPPRosterCoreDataStorage+shared.m in Sources */, 0771B81E1315E9FC0018D5C6 /* XMPPStreamCoreDataStorage.m in Sources */, 54FDBEC41330DA030099D4DB /* XMPPSocketTransport.m in Sources */, - 4A97CB751332094B009028BE /* ASIAuthenticationDialog.m in Sources */, - 4A97CB761332094B009028BE /* ASIDataCompressor.m in Sources */, - 4A97CB771332094B009028BE /* ASIDataDecompressor.m in Sources */, - 4A97CB781332094B009028BE /* ASIDownloadCache.m in Sources */, - 4A97CB791332094B009028BE /* ASIFormDataRequest.m in Sources */, - 4A97CB7A1332094B009028BE /* ASIHTTPRequest.m in Sources */, - 4A97CB7B1332094B009028BE /* ASIInputStream.m in Sources */, - 4A97CB7C1332094B009028BE /* ASINetworkQueue.m in Sources */, - 4A97CB7F1332096A009028BE /* Reachability.m in Sources */, 4AD0F4E3133C82F600F442D9 /* BoshTransport.m in Sources */, 4AD0F6511340F88800F442D9 /* BoshWindowManager.m in Sources */, + 4ADAD416134222CE00F6E4B3 /* ASIAuthenticationDialog.m in Sources */, + 4ADAD417134222CE00F6E4B3 /* ASIDataCompressor.m in Sources */, + 4ADAD418134222CE00F6E4B3 /* ASIDataDecompressor.m in Sources */, + 4ADAD419134222CE00F6E4B3 /* ASIDownloadCache.m in Sources */, + 4ADAD41A134222CE00F6E4B3 /* ASIFormDataRequest.m in Sources */, + 4ADAD41B134222CE00F6E4B3 /* ASIHTTPRequest.m in Sources */, + 4ADAD41C134222CE00F6E4B3 /* ASIInputStream.m in Sources */, + 4ADAD41D134222CE00F6E4B3 /* ASINetworkQueue.m in Sources */, + 4ADAD41E134222CE00F6E4B3 /* Reachability.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 788f138c4a1b758487ba1bcbdd5445c36cb173a4 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Wed, 30 Mar 2011 01:40:40 +0530 Subject: [PATCH 039/180] Merging boshwindowmanager with bosh transport and refactoring a little --- Core/Transports/BoshTransport.h | 58 +++++- Core/Transports/BoshTransport.m | 169 +++++++++++++----- Core/Transports/BoshWindowManager.h | 35 ---- Core/Transports/BoshWindowManager.m | 121 ------------- .../Classes/iPhoneXMPPAppDelegate.m | 2 +- 5 files changed, 182 insertions(+), 203 deletions(-) delete mode 100644 Core/Transports/BoshWindowManager.h delete mode 100644 Core/Transports/BoshWindowManager.m diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 842ae83..0985bfa 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -11,7 +11,37 @@ #import "XMPPJID.h" #import "MulticastDelegate.h" #import "ASIHTTPRequest.h" -#import "BoshWindowManager.h" + +@protocol BoshWindowProtocol +- (void)broadcastStanzas:(NSXMLNode *)node; +@end + +@interface RequestResponsePair : NSObject +@property(retain) NSXMLElement *request; +@property(retain) NSXMLElement *response; +- (id)initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response; +- (void)dealloc; +@end + +@interface BoshWindowManager : NSObject { + NSMutableDictionary *window; + + long long maxRidReceived; + long long maxRidSent; + + id delegate; +} + +@property long long windowSize; +@property(readonly) long long outstandingRequests; + +- (void)sentRequest:(NSXMLElement *)request; +- (void)recievedResponse:(NSXMLElement *)response; +- (BOOL)canSendMoreRequests; +- (BOOL)canLetServerHoldRequests:(long long)hold; +- (NSXMLElement *)getRequestForRid:(long long)rid; +- (id)initWithDelegate:(id)del rid:(long long)rid; +@end @interface BoshTransport : NSObject { NSString *boshVersion; @@ -34,7 +64,7 @@ @property(retain) NSNumber *wait; @property(retain) NSNumber *hold; @property(copy) NSString *lang; -@property(copy) NSString *host; +@property(copy) NSString *domain; @property(readonly) NSNumber *inactivity; @property(readonly) BOOL secure; @property(readonly) NSNumber *requests; @@ -43,10 +73,10 @@ @property(copy) NSString *url; /* init Methods */ -- (id)initWithUrl:(NSString *)url forHost:(NSString *)host; -- (id)initWithUrl:(NSString *)url forHost:(NSString *)host withDelegate:(id)delegate; +- (id)initWithUrl:(NSString *)url forDomain:(NSString *)host; +- (id)initWithUrl:(NSString *)url forDomain:(NSString *)host withDelegate:(id)delegate; -/* ASI http methods */ +/* ASI http methods - Delegate Methods */ - (void)requestFinished:(ASIHTTPRequest *)request; - (void)requestFailed:(ASIHTTPRequest *)request; @@ -61,5 +91,23 @@ - (float)serverXmppStreamVersionNumber; - (BOOL)sendStanza:(NSXMLElement *)stanza; - (BOOL)sendStanzaWithString:(NSString *)string; + +/* Methods used internally */ - (void)sessionResponseHandler:(ASIHTTPRequest *)request; +- (long long)generateRid; +- (NSArray *)convertToStrings:(NSArray *)array; +- (SEL)setterForProperty:(NSString *)property; +- (NSNumber *)numberFromString:(NSString *)stringNumber; +- (void)sendRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler; +- (void)broadcastStanzas:(NSXMLNode *)node; +- (void)startSessionWithRequest:(NSXMLElement *)body; +- (void)trySendingStanzas; +- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; +- (long long)getRidInRequest:(NSXMLElement *)body; +- (NSXMLElement *)newRequestWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; +- (NSArray *)createAttributeArrayFromDictionary:(NSDictionary *)attributes; +- (NSArray *)createNamespaceArrayFromDictionary:(NSDictionary *)namespacesDictionary; +- (NSXMLElement *)parseXMLData:(NSData *)xml; +- (NSXMLElement *)parseXMLString:(NSString *)xml; + @end diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 4b5a90f..1190e59 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -10,48 +10,125 @@ #import "DDXML.h" #import "NSXMLElementAdditions.h" -#pragma mark - -#pragma mark Private Accessor Methods -@interface BoshTransport() -- (void)setInactivity:(NSString *)givenInactivity; -- (void)setSecure:(NSString *)isSecure; -- (void)setRequests:(NSString *)maxRequests; -@end +#pragma - +#pragma RequestResponsePair Class +@implementation RequestResponsePair -#pragma mark Private utilities -@interface BoshTransport() -- (long long) generateRid; -- (NSArray *) convertToStrings:(NSArray *)array; -- (SEL)setterForProperty:(NSString *)property; -- (NSNumber *)numberFromString:(NSString *)stringNumber; +@synthesize request=request_; +@synthesize response=response_; + +- (id) initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response +{ + if( (self = [super init])) { + request_ = request; + response_ = response; + } + return self; +} + +- (void)dealloc +{ + [request_ release]; + [response_ release]; + [super dealloc]; +} @end -#pragma mark http -@interface BoshTransport() -- (void) sendRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler; -- (void)broadcastStanzas:(NSXMLNode *)node; -- (void)startSessionWithRequest:(NSXMLElement *)body; -- (void)trySendingStanzas; -- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; +#pragma - +#pragma BoshWindowManager Class + +@implementation BoshWindowManager + +@synthesize windowSize; +@synthesize outstandingRequests; + +- (long long)getRidInBody:(NSXMLElement *)body +{ + return [[[body attributeForName:@"rid"] stringValue] longLongValue]; +} + +- (NSString *)stringFromInt:(long long)num +{ + return [NSString stringWithFormat:@"%q",num]; +} + +- (id)initWithDelegate:(id)del rid:(long long)rid +{ + if((self = [super init])) + { + window = [[NSMutableDictionary alloc] initWithCapacity:4]; + windowSize = 0; + outstandingRequests = 0; + maxRidSent = rid; + maxRidReceived = rid; + delegate = del; + } + return self; +} + +- (void)sentRequest:(NSXMLElement *)request +{ + NSAssert( [self canSendMoreRequests], @"Sending request when should not be: Exceeding request count" ); + long long requestRid = [self getRidInBody:request]; + NSAssert ( requestRid == maxRidSent + 1, @"Sending request with rid = %qi greater than expected rid = %qi", requestRid, maxRidSent + 1); + ++maxRidSent; + [window setValue:[[RequestResponsePair alloc] initWithRequest:request response:nil] forKey:[self stringFromInt:requestRid]]; +} + +- (void)processResponses +{ + while (true) + { + RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:(maxRidReceived+1)]]; + if( requestResponsePair.response == nil ) break; + [[requestResponsePair.response retain] autorelease]; + [window removeObjectForKey:[ self stringFromInt:(maxRidReceived + 1) ]]; + ++maxRidReceived; + [delegate broadcastStanzas:requestResponsePair.response]; + } +} + +- (void)recievedResponse:(NSXMLElement *)response +{ + long long responseRid = [self getRidInBody:response]; + NSAssert(responseRid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", responseRid, maxRidReceived); + NSAssert(responseRid < maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", responseRid, maxRidReceived, windowSize); + RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:responseRid]]; + NSAssert( requestResponsePair != nil, @"Response rid not in queue"); + requestResponsePair.response = response; + [self processResponses]; +} + +- (BOOL)canSendMoreRequests +{ + return (maxRidSent - maxRidReceived) < windowSize; +} + +- (BOOL)canLetServerHoldRequests:(long long)hold +{ + return (maxRidSent - maxRidReceived) < hold; +} +- (NSXMLElement *)getRequestForRid:(long long)rid +{ + NSAssert( rid - maxRidReceived > 0 && [window count] > (rid - maxRidReceived), @"Error Access request for rid = %qi, where maxRidReceived = %qi and [requestQueue count] = %qi", rid, maxRidReceived, [window count]); + return [window valueForKey:[self stringFromInt:rid]]; +} @end -#pragma mark xml @interface BoshTransport() -- (long long)getRidInRequest:(NSXMLElement *)body; -- (NSXMLElement *) newRequestWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; -- (NSArray *) createAttributeArrayFromDictionary:(NSDictionary *)attributes; -- (NSArray *) createNamespaceArrayFromDictionary:(NSDictionary *)namespacesDictionary; -- (NSXMLElement *) parseXMLData:(NSData *)xml; -- (NSXMLElement *) parseXMLString:(NSString *)xml; +- (void)setInactivity:(NSString *)givenInactivity; +- (void)setSecure:(NSString *)isSecure; +- (void)setRequests:(NSString *)maxRequests; @end - +#pragma - +#pragma BoshTranshport Class @implementation BoshTransport @synthesize wait = wait_; @synthesize hold = hold_; @synthesize lang = lang_; -@synthesize host = host_; +@synthesize domain = domain_; @synthesize myJID = myJID_; @synthesize sid = sid_; @synthesize url = url_; @@ -86,12 +163,12 @@ - (void)setSecure:(NSString *)isSecure #pragma mark - #pragma mark init -- (id)initWithUrl:(NSString *)url forHost:(NSString *)host +- (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain { - return [self initWithUrl:url forHost:host withDelegate:nil]; + return [self initWithUrl:url forDomain:(NSString *)domain withDelegate:nil]; } -- (id)initWithUrl:(NSString *)url forHost:(NSString *)host withDelegate:(id)delegate +- (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id)delegate { self = [super init]; if(self) @@ -116,7 +193,7 @@ - (id)initWithUrl:(NSString *)url forHost:(NSString *)host withDelegate:(id window manager raises an exception. +*/ + - (void)sessionResponseHandler:(ASIHTTPRequest *)request { [multicastDelegate transportDidConnect:self]; diff --git a/Core/Transports/BoshWindowManager.h b/Core/Transports/BoshWindowManager.h deleted file mode 100644 index e12509c..0000000 --- a/Core/Transports/BoshWindowManager.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// BoshWindowManager.h -// iPhoneXMPP -// -// Created by Satyam Shekhar on 3/28/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import -#import "DDXML.h" - -@protocol BoshWindowProtocol -- (void)broadcastStanzas:(NSXMLNode *)node; -@end - -@interface BoshWindowManager : NSObject { - NSMutableDictionary *window; - - u_int32_t maxRidReceived; - u_int32_t maxRidSent; - - id delegate; -} - -@property u_int32_t windowSize; -@property(readonly) u_int32_t outstandingRequests; - -- (void)sentRequest:(NSXMLElement *)request; -- (void)recievedResponse:(NSXMLElement *)response; -- (BOOL)canSendMoreRequests; -- (BOOL)canLetServerHoldRequests:(u_int32_t)hold; -- (NSXMLElement *)getRequestForRid:(u_int32_t)rid; -- (id)initWithDelegate:(id)del rid:(u_int32_t)rid; -@end - diff --git a/Core/Transports/BoshWindowManager.m b/Core/Transports/BoshWindowManager.m deleted file mode 100644 index ebcacdd..0000000 --- a/Core/Transports/BoshWindowManager.m +++ /dev/null @@ -1,121 +0,0 @@ -// -// BoshWindowManager.m -// iPhoneXMPP -// -// Created by Satyam Shekhar on 3/28/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "BoshWindowManager.h" - -@interface RequestResponsePair : NSObject -{ -} - -@property(retain) NSXMLElement *request; -@property(retain) NSXMLElement *response; -- (id)initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response; -- (void)dealloc; -@end - -@implementation RequestResponsePair - -@synthesize request=request_; -@synthesize response=response_; - -- (id) initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response -{ - if( (self = [super init])) { - request_ = request; - response_ = response; - } - return self; -} - -- (void)dealloc -{ - [request_ release]; - [response_ release]; - [super dealloc]; -} -@end - - -@implementation BoshWindowManager - -@synthesize windowSize; -@synthesize outstandingRequests; - -- (u_int32_t)getRidInBody:(NSXMLElement *)body -{ - return [[[body attributeForName:@"rid"] stringValue] intValue]; -} - -- (NSString *)stringFromInt:(u_int32_t)num -{ - return [NSString stringWithFormat:@"%u",num]; -} - -- (id)initWithDelegate:(id)del rid:(u_int32_t)rid -{ - if((self = [super init])) - { - window = [[NSMutableDictionary alloc] initWithCapacity:4]; - windowSize = 0; - outstandingRequests = 0; - maxRidSent = rid; - maxRidReceived = rid; - delegate = del; - } - return self; -} - -- (void)sentRequest:(NSXMLElement *)request -{ - NSAssert( [self canSendMoreRequests], @"Sending request when should not be: Exceeding request count" ); - u_int32_t requestRid = [self getRidInBody:request]; - NSAssert ( requestRid == maxRidSent + 1, @"Sending request with rid = %u greater than expected rid = %u", requestRid, maxRidSent + 1); - ++maxRidSent; - [window setValue:[[RequestResponsePair alloc] initWithRequest:request response:nil] forKey:[self stringFromInt:requestRid]]; -} - -- (void)processResponses -{ - while (true) - { - RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:(maxRidReceived+1)]]; - if( requestResponsePair.response == nil ) break; - [[requestResponsePair.response retain] autorelease]; - [window removeObjectForKey:[ self stringFromInt:(maxRidReceived + 1) ]]; - ++maxRidReceived; - [delegate broadcastStanzas:requestResponsePair.response]; - } -} - -- (void)recievedResponse:(NSXMLElement *)response -{ - u_int32_t responseRid = [self getRidInBody:response]; - NSAssert(responseRid > maxRidReceived, @"Recieving response for rid = %u where maxRidReceived = %u", responseRid, maxRidReceived); - NSAssert(responseRid < maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %u, maxRidReceived = %u, windowSize = %u", responseRid, maxRidReceived, windowSize); - RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:responseRid]]; - NSAssert( requestResponsePair != nil, @"Response rid not in queue"); - requestResponsePair.response = response; - [self processResponses]; -} - -- (BOOL)canSendMoreRequests -{ - return (maxRidSent - maxRidReceived) < windowSize; -} - -- (BOOL)canLetServerHoldRequests:(u_int32_t)hold -{ - return (maxRidSent - maxRidReceived) < hold; -} -- (NSXMLElement *)getRequestForRid:(u_int32_t)rid -{ - NSAssert( rid - maxRidReceived > 0 && [window count] > (rid - maxRidReceived), @"Error Access request for rid = %u, where maxRidReceived = %u and [requestQueue count] = %u", rid, maxRidReceived, [window count]); - return [window valueForKey:[self stringFromInt:rid]]; -} - -@end diff --git a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m index e9c3681..5a83958 100644 --- a/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m +++ b/Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m @@ -19,7 +19,7 @@ @implementation iPhoneXMPPAppDelegate - (void)applicationDidFinishLaunching:(UIApplication *)application { - transport = [[BoshTransport alloc] initWithUrl:@"http://localhost:5280/http-bind" forHost:@"directi.com"]; + transport = [[BoshTransport alloc] initWithUrl:@"http://localhost:5280/http-bind" forDomain:@"directi.com"]; //[transport setMyJID:[XMPPJID jidWithString:@"satyam.s@directi.com"]]; //173.255.205.197 From 8be79686be532cbc640430dfadc404d78c3bfdbe Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Wed, 30 Mar 2011 13:10:31 +0530 Subject: [PATCH 040/180] Removing BoshWindowManager.[hm] and renaming and removing few functions --- Core/Transports/BoshTransport.h | 10 +++-- Core/Transports/BoshTransport.m | 45 +++++++------------ .../iPhoneXMPP.xcodeproj/project.pbxproj | 6 --- 3 files changed, 22 insertions(+), 39 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 0985bfa..7cd872b 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -12,6 +12,11 @@ #import "MulticastDelegate.h" #import "ASIHTTPRequest.h" +typedef enum { + ATTR_TYPE = 0, + NAMESPACE_TYPE = 1 +} XMLNodeType; + @protocol BoshWindowProtocol - (void)broadcastStanzas:(NSXMLNode *)node; @end @@ -25,10 +30,8 @@ @interface BoshWindowManager : NSObject { NSMutableDictionary *window; - long long maxRidReceived; long long maxRidSent; - id delegate; } @@ -105,8 +108,7 @@ - (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; - (long long)getRidInRequest:(NSXMLElement *)body; - (NSXMLElement *)newRequestWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; -- (NSArray *)createAttributeArrayFromDictionary:(NSDictionary *)attributes; -- (NSArray *)createNamespaceArrayFromDictionary:(NSDictionary *)namespacesDictionary; +- (NSArray *)createXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNodeType)type; - (NSXMLElement *)parseXMLData:(NSData *)xml; - (NSXMLElement *)parseXMLString:(NSString *)xml; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 1190e59..2ce92b1 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -403,12 +403,7 @@ - (void)sendRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHa } #pragma mark - -#pragma mark XML - -- (long long)getRidInRequest:(NSXMLElement *)body -{ - return [[[body attributeForName:@"rid"] stringValue] longLongValue]; -} +#pragma mark utilities - (NSXMLElement *)newRequestWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces { @@ -423,18 +418,23 @@ - (NSXMLElement *)newRequestWithPayload:(NSArray *)payload attributes:(NSMutable /* Adding the BODY_NS namespace on every outgoing request */ [namespaces setValue:BODY_NS forKey:@""]; - NSXMLElement *boshRequest = [[NSXMLElement alloc] initWithName:@"body"]; + NSXMLElement *body = [[NSXMLElement alloc] initWithName:@"body"]; - [boshRequest setNamespaces:[self createNamespaceArrayFromDictionary:namespaces]]; - [boshRequest setAttributes:[self createAttributeArrayFromDictionary:attributes]]; + [body setNamespaces:[self createXMLNodeArrayFromDictionary:namespaces ofType:NAMESPACE_TYPE]]; + [body setAttributes:[self createXMLNodeArrayFromDictionary:attributes ofType:ATTR_TYPE]]; if(payload != nil) for(NSXMLElement *child in payload) - [boshRequest addChild:child]; + [body addChild:child]; ++nextRidToSend; - return boshRequest; + return body; +} + +- (long long)getRidInRequest:(NSXMLElement *)body +{ + return [[[body attributeForName:@"rid"] stringValue] longLongValue]; } - (NSXMLElement *)returnRootElement:(NSXMLDocument *)doc @@ -456,16 +456,16 @@ - (NSXMLElement *)parseXMLData:(NSData *)xml return [self returnRootElement:doc]; } -- (NSArray *)createArrayFromDictionary:(NSDictionary *)dictionary ofType:(NSString *)type +- (NSArray *)createXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNodeType)type { NSMutableArray *array = [[NSMutableArray alloc] init]; - for (NSString *key in dictionary) { - NSString *value = [dictionary objectForKey:key]; + for (NSString *key in dict) { + NSString *value = [dict objectForKey:key]; NSXMLNode *node; - if([type isEqualToString:@"attributes"]) + if(type == ATTR_TYPE) node = [NSXMLNode attributeWithName:key stringValue:value]; - else if([type isEqualToString:@"namespaces"]) + else if(type == NAMESPACE_TYPE) node = [NSXMLNode namespaceWithName:key stringValue:value]; else NSLog(@"BOSH: Wrong Type Passed to createArrayFrom Dictionary"); @@ -475,19 +475,6 @@ - (NSArray *)createArrayFromDictionary:(NSDictionary *)dictionary ofType:(NSStri return [array autorelease]; } -- (NSArray *)createAttributeArrayFromDictionary:(NSDictionary *)attributesDictionary -{ - return [self createArrayFromDictionary:attributesDictionary ofType:@"attributes"]; -} - -- (NSArray *)createNamespaceArrayFromDictionary:(NSDictionary *)namespacesDictionary -{ - return [self createArrayFromDictionary:namespacesDictionary ofType:@"namespaces"]; -} - -#pragma mark - -#pragma mark utilities - - (long long)generateRid { return (arc4random() % 1000000000LL + 1000000001LL); diff --git a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj index 3487664..31b96f6 100755 --- a/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj +++ b/Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj @@ -20,7 +20,6 @@ 4A97CBDB1332314C009028BE /* libz.1.2.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A97CBDA1332314C009028BE /* libz.1.2.3.dylib */; }; 4A97CBDF13323191009028BE /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A97CBDE13323191009028BE /* MobileCoreServices.framework */; }; 4AD0F4E3133C82F600F442D9 /* BoshTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD0F4E2133C82F600F442D9 /* BoshTransport.m */; }; - 4AD0F6511340F88800F442D9 /* BoshWindowManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD0F6501340F88800F442D9 /* BoshWindowManager.m */; }; 4ADAD416134222CE00F6E4B3 /* ASIAuthenticationDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD401134222CE00F6E4B3 /* ASIAuthenticationDialog.m */; }; 4ADAD417134222CE00F6E4B3 /* ASIDataCompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD404134222CE00F6E4B3 /* ASIDataCompressor.m */; }; 4ADAD418134222CE00F6E4B3 /* ASIDataDecompressor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADAD406134222CE00F6E4B3 /* ASIDataDecompressor.m */; }; @@ -84,8 +83,6 @@ 4A97CBDE13323191009028BE /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 4AD0F4E1133C82F600F442D9 /* BoshTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BoshTransport.h; path = ../../Core/Transports/BoshTransport.h; sourceTree = SOURCE_ROOT; }; 4AD0F4E2133C82F600F442D9 /* BoshTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BoshTransport.m; path = ../../Core/Transports/BoshTransport.m; sourceTree = SOURCE_ROOT; }; - 4AD0F64F1340F88800F442D9 /* BoshWindowManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BoshWindowManager.h; path = ../../Core/Transports/BoshWindowManager.h; sourceTree = SOURCE_ROOT; }; - 4AD0F6501340F88800F442D9 /* BoshWindowManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BoshWindowManager.m; path = ../../Core/Transports/BoshWindowManager.m; sourceTree = SOURCE_ROOT; }; 4ADAD400134222CE00F6E4B3 /* ASIAuthenticationDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASIAuthenticationDialog.h; path = ../../Vendor/ASIHTTPRequest/ASIAuthenticationDialog.h; sourceTree = ""; }; 4ADAD401134222CE00F6E4B3 /* ASIAuthenticationDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASIAuthenticationDialog.m; path = ../../Vendor/ASIHTTPRequest/ASIAuthenticationDialog.m; sourceTree = ""; }; 4ADAD402134222CE00F6E4B3 /* ASICacheDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASICacheDelegate.h; path = ../../Vendor/ASIHTTPRequest/ASICacheDelegate.h; sourceTree = ""; }; @@ -299,8 +296,6 @@ 54FDBEC51330DA0C0099D4DB /* Transports */ = { isa = PBXGroup; children = ( - 4AD0F64F1340F88800F442D9 /* BoshWindowManager.h */, - 4AD0F6501340F88800F442D9 /* BoshWindowManager.m */, 4AD0F4E1133C82F600F442D9 /* BoshTransport.h */, 4AD0F4E2133C82F600F442D9 /* BoshTransport.m */, 54FDBEC21330DA030099D4DB /* XMPPSocketTransport.h */, @@ -568,7 +563,6 @@ 0771B81E1315E9FC0018D5C6 /* XMPPStreamCoreDataStorage.m in Sources */, 54FDBEC41330DA030099D4DB /* XMPPSocketTransport.m in Sources */, 4AD0F4E3133C82F600F442D9 /* BoshTransport.m in Sources */, - 4AD0F6511340F88800F442D9 /* BoshWindowManager.m in Sources */, 4ADAD416134222CE00F6E4B3 /* ASIAuthenticationDialog.m in Sources */, 4ADAD417134222CE00F6E4B3 /* ASIDataCompressor.m in Sources */, 4ADAD418134222CE00F6E4B3 /* ASIDataDecompressor.m in Sources */, From 96cb3bf57bdb6d0bab9b291d23c0356bb64e5743 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Thu, 31 Mar 2011 17:15:13 +0530 Subject: [PATCH 041/180] Refactoring more - and resending the failed requests. --- Core/Transports/BoshTransport.h | 9 +- Core/Transports/BoshTransport.m | 144 ++++++++++++++++---------------- 2 files changed, 77 insertions(+), 76 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 7cd872b..1c071f3 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -96,20 +96,21 @@ typedef enum { - (BOOL)sendStanzaWithString:(NSString *)string; /* Methods used internally */ +- (BOOL)canConnect; - (void)sessionResponseHandler:(ASIHTTPRequest *)request; - (long long)generateRid; - (NSArray *)convertToStrings:(NSArray *)array; - (SEL)setterForProperty:(NSString *)property; - (NSNumber *)numberFromString:(NSString *)stringNumber; -- (void)sendRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler; +- (void)sendHTTPRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler; - (void)broadcastStanzas:(NSXMLNode *)node; -- (void)startSessionWithRequest:(NSXMLElement *)body; - (void)trySendingStanzas; - (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; - (long long)getRidInRequest:(NSXMLElement *)body; -- (NSXMLElement *)newRequestWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; +- (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; - (NSArray *)createXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNodeType)type; - (NSXMLElement *)parseXMLData:(NSData *)xml; - (NSXMLElement *)parseXMLString:(NSString *)xml; - +- (void)sendRequestsToHold; +- (BOOL) startSession:(NSError **)error; @end diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 2ce92b1..61af5ce 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -19,7 +19,7 @@ @implementation RequestResponsePair - (id) initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response { - if( (self = [super init])) { + if( (self = [super init]) ) { request_ = request; response_ = response; } @@ -49,7 +49,7 @@ - (long long)getRidInBody:(NSXMLElement *)body - (NSString *)stringFromInt:(long long)num { - return [NSString stringWithFormat:@"%q",num]; + return [NSString stringWithFormat:@"%qi",num]; } - (id)initWithDelegate:(id)del rid:(long long)rid @@ -72,7 +72,7 @@ - (void)sentRequest:(NSXMLElement *)request long long requestRid = [self getRidInBody:request]; NSAssert ( requestRid == maxRidSent + 1, @"Sending request with rid = %qi greater than expected rid = %qi", requestRid, maxRidSent + 1); ++maxRidSent; - [window setValue:[[RequestResponsePair alloc] initWithRequest:request response:nil] forKey:[self stringFromInt:requestRid]]; + [window setValue:[[RequestResponsePair alloc] initWithRequest:request response:nil] forKey:[self stringFromInt:requestRid]]; } - (void)processResponses @@ -210,37 +210,13 @@ - (float)serverXmppStreamVersionNumber return 1.0; } -- (BOOL)canConnect -{ - if(!self.domain) - { - NSLog(@"BOSH: Called Connect with specifying the domain"); - return NO; - } - if(!self.myJID) - { - NSLog(@"BOSH: Called connect without setting the jid"); - return NO; - } - return YES; -} - - (BOOL)connect:(NSError **)error { NSLog(@"BOSH: Connecting to %@ with jid = %@", self.domain, [self.myJID bare]); if(![self canConnect]) return NO; - - [multicastDelegate transportWillConnect:self]; - - NSArray *keys = [NSArray arrayWithObjects:@"content", @"hold", @"to", @"ver", @"wait", @"ack", @"xml:lang", @"from", @"secure", nil]; - NSArray *objects = [NSArray arrayWithObjects:content, self.hold, self.domain, boshVersion, self.wait, ack, self.lang, [self.myJID bare], @"false", nil]; - NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjects:[self convertToStrings:objects] forKeys:keys]; - NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; - NSXMLElement *requestPayload = [self newRequestWithPayload:nil attributes:attr namespaces:ns]; - [self sendRequestWithBody:requestPayload responseHandler:@selector(sessionResponseHandler:) errorHandler:nil]; - [requestPayload release]; - return YES; + [multicastDelegate transportWillConnect:self]; + return [self startSession:error]; } - (void)restartStream @@ -256,6 +232,7 @@ - (void)disconnect NSLog(@"Bosh: Will Terminate Session"); NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: self.lang, @"xml:lang", @"terminate", @"type", nil]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; + [self sendRequest:nil attributes:attr namespaces:ns]; } @@ -285,52 +262,39 @@ - (void) removeDelegate:(id)delegate #pragma mark - #pragma mark BOSH -- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces -{ - NSXMLElement *requestPayload = [self newRequestWithPayload:bodyPayload attributes:attributes namespaces:namespaces]; - [self sendRequestWithBody:requestPayload responseHandler:nil errorHandler:nil]; - [boshWindowManager sentRequest:requestPayload]; - [requestPayload release]; -} - -- (void)trySendingStanzas -{ - if( [boshWindowManager canSendMoreRequests] && [pendingXMPPStanzas count] > 0) - { - [self sendRequest:pendingXMPPStanzas attributes:nil namespaces:nil]; - [pendingXMPPStanzas removeAllObjects]; - } -} - -- (void)sendRequestsToHold +- (BOOL)canConnect { - while( [boshWindowManager canLetServerHoldRequests:[self.hold unsignedIntValue]] ) - [self sendRequest:nil attributes:nil namespaces:nil]; + if(!self.domain) + { + NSLog(@"BOSH: Called Connect with specifying the domain"); + return NO; + } + if(!self.myJID) + { + NSLog(@"BOSH: Called connect without setting the jid"); + return NO; + } + return YES; } -/* - For each received stanza the client might send out packets. - We should ideally put all the request in the queue and call - processRequestQueue with a timeOut. -*/ -- (void)broadcastStanzas:(NSXMLNode *)node +- (BOOL) startSession:(NSError **)error { - while( (node = [node nextNode]) ) - { - if([node level] == 1) - { - NSLog(@"BOSH: Passing to delegates the stanza = %@", node); - [multicastDelegate transport:self didReceiveStanza:[(NSXMLElement *)node copy]]; - } - } + NSArray *keys = [NSArray arrayWithObjects:@"content", @"hold", @"to", @"ver", @"wait", @"ack", @"xml:lang", @"from", @"secure", nil]; + NSArray *objects = [NSArray arrayWithObjects:content, self.hold, self.domain, boshVersion, self.wait, ack, self.lang, [self.myJID bare], @"false", nil]; + NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjects:[self convertToStrings:objects] forKeys:keys]; + NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; + + NSXMLElement *requestPayload = [self newBodyElementWithPayload:nil attributes:attr namespaces:ns]; + [self sendHTTPRequestWithBody:requestPayload responseHandler:@selector(sessionResponseHandler:) errorHandler:nil]; + [requestPayload release]; + return YES; } /* The bosh server doesn't reply with requests ( and possibly other ) attrs. Cause: Connection between the bosh proxy and xmpp server is broken. Result: requests = 0. ==> window manager raises an exception. -*/ - + */ - (void)sessionResponseHandler:(ASIHTTPRequest *)request { [multicastDelegate transportDidConnect:self]; @@ -347,7 +311,7 @@ - (void)sessionResponseHandler:(ASIHTTPRequest *)request if([self respondsToSelector:setter]) [self performSelector:setter withObject:attrValue]; } - + /* Not doing anything with namespaces right now - because chirkut doesn't send it */ //NSArray *responseNamespaces = [rootElement namespaces]; @@ -357,18 +321,53 @@ - (void)sessionResponseHandler:(ASIHTTPRequest *)request if( [(NSXMLNode *)rootElement childCount] > 0 ) [self broadcastStanzas:rootElement]; - + /* should we send these requests after a delay?? */ [self sendRequestsToHold]; } -- (void)startSessionWithRequest:(NSXMLElement *)body +- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces { - + NSXMLElement *requestPayload = [self newBodyElementWithPayload:bodyPayload attributes:attributes namespaces:namespaces]; + [boshWindowManager sentRequest:requestPayload]; + [self sendHTTPRequestWithBody:requestPayload responseHandler:nil errorHandler:nil]; + [requestPayload release]; +} + +- (void)trySendingStanzas +{ + if( [boshWindowManager canSendMoreRequests] && [pendingXMPPStanzas count] > 0) + { + [self sendRequest:pendingXMPPStanzas attributes:nil namespaces:nil]; + [pendingXMPPStanzas removeAllObjects]; + } +} + +- (void)sendRequestsToHold +{ + while( [boshWindowManager canLetServerHoldRequests:[self.hold unsignedIntValue]] ) + [self sendRequest:nil attributes:nil namespaces:nil]; +} + +/* + For each received stanza the client might send out packets. + We should ideally put all the request in the queue and call + processRequestQueue with a timeOut. +*/ +- (void)broadcastStanzas:(NSXMLNode *)node +{ + while( (node = [node nextNode]) ) + { + if([node level] == 1) + { + NSLog(@"BOSH: Passing to delegates the stanza = %@", node); + [multicastDelegate transport:self didReceiveStanza:[(NSXMLElement *)node copy]]; + } + } } #pragma mark - -#pragma mark HTTP +#pragma mark HTTP Request Response /* Should call processRequestQueue after some timeOut */ - (void)requestFinished:(ASIHTTPRequest *)request @@ -382,9 +381,10 @@ - (void)requestFinished:(ASIHTTPRequest *)request - (void)requestFailed:(ASIHTTPRequest *)request { NSLog(@"BOSH: request Failed = %@", [request error]); + [request startSynchronous]; } -- (void)sendRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler +- (void)sendHTTPRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler { NSURL *url = [NSURL URLWithString:self.url]; ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; @@ -405,7 +405,7 @@ - (void)sendRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHa #pragma mark - #pragma mark utilities -- (NSXMLElement *)newRequestWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces +- (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces { attributes = attributes?attributes:[NSMutableDictionary dictionaryWithCapacity:3]; namespaces = namespaces?namespaces:[NSMutableDictionary dictionaryWithCapacity:1]; From 02982bdf786dafd65bad0d89e265b4eaedafe7ff Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Thu, 31 Mar 2011 21:13:06 +0530 Subject: [PATCH 042/180] Transport now disconnects gracefully --- Core/Transports/BoshTransport.h | 21 ++++-- Core/Transports/BoshTransport.m | 118 +++++++++++++++++++++++++------- 2 files changed, 109 insertions(+), 30 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 1c071f3..cc280cd 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -17,6 +17,13 @@ typedef enum { NAMESPACE_TYPE = 1 } XMLNodeType; +typedef enum { + CONNECTED = 0, + CONNECTING = 1, + DISCONNECTING = 2, + DISCONNECTED = 3 +} BoshTransportState; + @protocol BoshWindowProtocol - (void)broadcastStanzas:(NSXMLNode *)node; @end @@ -44,6 +51,7 @@ typedef enum { - (BOOL)canLetServerHoldRequests:(long long)hold; - (NSXMLElement *)getRequestForRid:(long long)rid; - (id)initWithDelegate:(id)del rid:(long long)rid; +- (void)dealloc; @end @interface BoshTransport : NSObject { @@ -59,7 +67,8 @@ typedef enum { NSMutableArray *pendingXMPPStanzas; BoshWindowManager *boshWindowManager; - + BoshTransportState state; + MulticastDelegate *multicastDelegate; } @@ -79,11 +88,14 @@ typedef enum { - (id)initWithUrl:(NSString *)url forDomain:(NSString *)host; - (id)initWithUrl:(NSString *)url forDomain:(NSString *)host withDelegate:(id)delegate; +- (void)dealloc; + /* ASI http methods - Delegate Methods */ - (void)requestFinished:(ASIHTTPRequest *)request; - (void)requestFailed:(ASIHTTPRequest *)request; /* Protocol Methods */ +- (BOOL)isConnected; - (void)addDelegate:(id)delegate; - (void)removeDelegate:(id)delegate; - (XMPPJID *)myJID; @@ -97,7 +109,8 @@ typedef enum { /* Methods used internally */ - (BOOL)canConnect; -- (void)sessionResponseHandler:(ASIHTTPRequest *)request; +- (void)createSessionResponseHandler:(ASIHTTPRequest *)request; +- (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request; - (long long)generateRid; - (NSArray *)convertToStrings:(NSArray *)array; - (SEL)setterForProperty:(NSString *)property; @@ -105,12 +118,12 @@ typedef enum { - (void)sendHTTPRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler; - (void)broadcastStanzas:(NSXMLNode *)node; - (void)trySendingStanzas; -- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; +- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler; - (long long)getRidInRequest:(NSXMLElement *)body; - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; - (NSArray *)createXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNodeType)type; - (NSXMLElement *)parseXMLData:(NSData *)xml; - (NSXMLElement *)parseXMLString:(NSString *)xml; - (void)sendRequestsToHold; -- (BOOL) startSession:(NSError **)error; +- (BOOL) createSession:(NSError **)error; @end diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 61af5ce..be6234d 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -113,6 +113,12 @@ - (NSXMLElement *)getRequestForRid:(long long)rid NSAssert( rid - maxRidReceived > 0 && [window count] > (rid - maxRidReceived), @"Error Access request for rid = %qi, where maxRidReceived = %qi and [requestQueue count] = %qi", rid, maxRidReceived, [window count]); return [window valueForKey:[self stringFromInt:rid]]; } + +- (void) dealloc +{ + [window release]; + [super dealloc]; +} @end @interface BoshTransport() @@ -178,7 +184,7 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< content = [NSString stringWithFormat:@"text/xml; charset=utf-8"]; wait_ = [[NSNumber alloc] initWithDouble:60.0]; hold_ = [[NSNumber alloc] initWithInt:1]; - ack = [[NSNumber alloc] initWithInt:1]; + ack = [NSNumber numberWithInt:1]; nextRidToSend = [self generateRid]; @@ -191,11 +197,12 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< sid_ = nil; inactivity = [[NSNumber alloc] initWithInt:0]; - requests = [[NSNumber alloc] initWithInt:0]; - url_ = url; - domain_ = domain; + requests = [[NSNumber alloc] initWithInt:2]; + url_ = [url copy]; + domain_ = [domain copy]; myJID_ = nil; - + state = DISCONNECTED; + /* Keeping a random capacity right now */ pendingXMPPStanzas = [[NSMutableArray alloc] initWithCapacity:25]; } @@ -213,34 +220,52 @@ - (float)serverXmppStreamVersionNumber - (BOOL)connect:(NSError **)error { NSLog(@"BOSH: Connecting to %@ with jid = %@", self.domain, [self.myJID bare]); - if(![self canConnect]) return NO; + if(![self canConnect]) return NO; + state = CONNECTING; [multicastDelegate transportWillConnect:self]; - return [self startSession:error]; + return [self createSession:error]; } - (void)restartStream { + if(![self isConnected]) + { + NSLog(@"BOSH: Need to be connected to restart the stream."); + return ; + } NSLog(@"Bosh: Will Restart Stream"); NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: self.lang, @"xml:lang", @"true", @"xmpp:restart", nil]; - NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", XMPP_NS, @"xmpp", nil]; - [self sendRequest:nil attributes:attr namespaces:ns]; + NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys:XMPP_NS, @"xmpp", nil]; + + [self sendRequest:nil attributes:attr namespaces:ns responseHandler:nil errorHandler:nil]; } - (void)disconnect { + if(![self isConnected]) + { + NSLog(@"BOSH: Need to be connected to disconnect"); + return; + } NSLog(@"Bosh: Will Terminate Session"); + [multicastDelegate transportWillDisconnect:self]; NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: self.lang, @"xml:lang", @"terminate", @"type", nil]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; - - [self sendRequest:nil attributes:attr namespaces:ns]; + state = DISCONNECTING; + [self sendRequest:nil attributes:attr namespaces:ns responseHandler:@selector(disconnectSessionResponseHandler:) errorHandler:nil]; } - (BOOL)sendStanza:(NSXMLElement *)stanza { + if (![self isConnected]) + { + NSLog(@"BOSH: Need to be connected to be able to send stanza"); + return NO; + } [pendingXMPPStanzas addObject:stanza]; [self trySendingStanzas]; - return TRUE; + return YES; } - (BOOL)sendStanzaWithString:(NSString *)string @@ -262,8 +287,18 @@ - (void) removeDelegate:(id)delegate #pragma mark - #pragma mark BOSH +- (BOOL)isConnected +{ + return state == CONNECTED; +} + - (BOOL)canConnect { + if( state != DISCONNECTED ) + { + NSLog(@"@BOSH: Either disconnecting or still connected to the server. Disconnect First."); + return NO; + } if(!self.domain) { NSLog(@"BOSH: Called Connect with specifying the domain"); @@ -277,7 +312,7 @@ - (BOOL)canConnect return YES; } -- (BOOL) startSession:(NSError **)error +- (BOOL) createSession:(NSError **)error { NSArray *keys = [NSArray arrayWithObjects:@"content", @"hold", @"to", @"ver", @"wait", @"ack", @"xml:lang", @"from", @"secure", nil]; NSArray *objects = [NSArray arrayWithObjects:content, self.hold, self.domain, boshVersion, self.wait, ack, self.lang, [self.myJID bare], @"false", nil]; @@ -285,23 +320,19 @@ - (BOOL) startSession:(NSError **)error NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; NSXMLElement *requestPayload = [self newBodyElementWithPayload:nil attributes:attr namespaces:ns]; - [self sendHTTPRequestWithBody:requestPayload responseHandler:@selector(sessionResponseHandler:) errorHandler:nil]; + [self sendHTTPRequestWithBody:requestPayload responseHandler:@selector(createSessionResponseHandler:) errorHandler:nil]; [requestPayload release]; return YES; } -/* - The bosh server doesn't reply with requests ( and possibly other ) attrs. - Cause: Connection between the bosh proxy and xmpp server is broken. - Result: requests = 0. ==> window manager raises an exception. - */ -- (void)sessionResponseHandler:(ASIHTTPRequest *)request +- (void)createSessionResponseHandler:(ASIHTTPRequest *)request { - [multicastDelegate transportDidConnect:self]; NSLog(@"BOSH: Response = %@", [request responseString]); + state = CONNECTED; NSXMLElement *rootElement = [self parseXMLData:[request responseData]]; NSArray *responseAttributes = [rootElement attributes]; + /* Setting inactivity, sid, wait, hold, ack, lang, authid, secure, requests */ for(NSXMLNode *attr in responseAttributes) { NSString *attrName = [attr name]; @@ -315,10 +346,12 @@ - (void)sessionResponseHandler:(ASIHTTPRequest *)request /* Not doing anything with namespaces right now - because chirkut doesn't send it */ //NSArray *responseNamespaces = [rootElement namespaces]; - [multicastDelegate transportDidStartNegotiation:self]; boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:[self getRidInRequest:rootElement]]; [boshWindowManager setWindowSize:[requests unsignedIntValue]]; + [multicastDelegate transportDidConnect:self]; + [multicastDelegate transportDidStartNegotiation:self]; + if( [(NSXMLNode *)rootElement childCount] > 0 ) [self broadcastStanzas:rootElement]; @@ -326,11 +359,29 @@ - (void)sessionResponseHandler:(ASIHTTPRequest *)request [self sendRequestsToHold]; } -- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces +- (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request +{ + state = DISCONNECTED; + [boshWindowManager release]; + [multicastDelegate removeAllDelegates]; + [pendingXMPPStanzas removeAllObjects]; + self.myJID = nil; + nextRidToSend = [self generateRid]; + ack = [NSNumber numberWithInt:1]; + self.hold = [NSNumber numberWithInt:1]; + self.wait = [NSNumber numberWithDouble:60.0]; + [inactivity release]; + inactivity = [NSNumber numberWithInt:0]; + self.sid = nil; + self.authid = nil; + [multicastDelegate transportDidDisconnect:self]; +} + +- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler { NSXMLElement *requestPayload = [self newBodyElementWithPayload:bodyPayload attributes:attributes namespaces:namespaces]; [boshWindowManager sentRequest:requestPayload]; - [self sendHTTPRequestWithBody:requestPayload responseHandler:nil errorHandler:nil]; + [self sendHTTPRequestWithBody:requestPayload responseHandler:responseHandler errorHandler:errorHandler]; [requestPayload release]; } @@ -338,7 +389,7 @@ - (void)trySendingStanzas { if( [boshWindowManager canSendMoreRequests] && [pendingXMPPStanzas count] > 0) { - [self sendRequest:pendingXMPPStanzas attributes:nil namespaces:nil]; + [self sendRequest:pendingXMPPStanzas attributes:nil namespaces:nil responseHandler:nil errorHandler:nil]; [pendingXMPPStanzas removeAllObjects]; } } @@ -346,7 +397,7 @@ - (void)trySendingStanzas - (void)sendRequestsToHold { while( [boshWindowManager canLetServerHoldRequests:[self.hold unsignedIntValue]] ) - [self sendRequest:nil attributes:nil namespaces:nil]; + [self sendRequest:nil attributes:nil namespaces:nil responseHandler:nil errorHandler:nil]; } /* @@ -504,4 +555,19 @@ - (NSNumber *)numberFromString:(NSString *)stringNumber [formatter release]; return number; } + +- (void)dealloc +{ + [wait_ release]; + [hold_ release]; + [multicastDelegate release]; + [inactivity release]; + [requests release]; + [url_ release]; + [domain_ release]; + [myJID_ release]; + [authid release]; + [sid_ release]; + [super dealloc]; +} @end From 57172da19179d0fd66ec4afd2a72101a31565d7a Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Thu, 31 Mar 2011 21:47:45 +0530 Subject: [PATCH 043/180] Using request rid in BoshWindowManager --- Core/Transports/BoshTransport.h | 2 +- Core/Transports/BoshTransport.m | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index cc280cd..259fa4b 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -46,7 +46,7 @@ typedef enum { @property(readonly) long long outstandingRequests; - (void)sentRequest:(NSXMLElement *)request; -- (void)recievedResponse:(NSXMLElement *)response; +- (void)recievedResponse:(NSXMLElement *)response forRid:(long long)rid; - (BOOL)canSendMoreRequests; - (BOOL)canLetServerHoldRequests:(long long)hold; - (NSXMLElement *)getRequestForRid:(long long)rid; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index be6234d..f06ddb0 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -88,12 +88,11 @@ - (void)processResponses } } -- (void)recievedResponse:(NSXMLElement *)response +- (void)recievedResponse:(NSXMLElement *)response forRid:(long long)rid { - long long responseRid = [self getRidInBody:response]; - NSAssert(responseRid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", responseRid, maxRidReceived); - NSAssert(responseRid < maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", responseRid, maxRidReceived, windowSize); - RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:responseRid]]; + NSAssert(rid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", rid, maxRidReceived); + NSAssert(rid < maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); + RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:rid]]; NSAssert( requestResponsePair != nil, @"Response rid not in queue"); requestResponsePair.response = response; [self processResponses]; @@ -425,7 +424,9 @@ - (void)requestFinished:(ASIHTTPRequest *)request { NSString *responseString = [request responseString]; NSLog(@"BOSH: response string = %@", responseString); - [boshWindowManager recievedResponse:[self parseXMLData:[request responseData]]]; + NSString *postBodyString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; + NSXMLElement *postBody = [[NSXMLElement alloc] initWithXMLString:postBodyString error:nil]; + [boshWindowManager recievedResponse:[self parseXMLData:[request responseData]] forRid:[self getRidInRequest:postBody]]; [self sendRequestsToHold]; } From 03f431b2e24659ebac607805e4315f5fb19d9d0d Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Fri, 1 Apr 2011 12:59:59 +0530 Subject: [PATCH 044/180] Disconnect now going through window manager to make sure it doesnot exceed request count --- Core/Transports/BoshTransport.m | 36 ++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index f06ddb0..f76c1b6 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -248,11 +248,9 @@ - (void)disconnect return; } NSLog(@"Bosh: Will Terminate Session"); - [multicastDelegate transportWillDisconnect:self]; - NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: self.lang, @"xml:lang", @"terminate", @"type", nil]; - NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; state = DISCONNECTING; - [self sendRequest:nil attributes:attr namespaces:ns responseHandler:@selector(disconnectSessionResponseHandler:) errorHandler:nil]; + [multicastDelegate transportWillDisconnect:self]; + } - (BOOL)sendStanza:(NSXMLElement *)stanza @@ -386,11 +384,21 @@ - (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)att - (void)trySendingStanzas { - if( [boshWindowManager canSendMoreRequests] && [pendingXMPPStanzas count] > 0) + if( [boshWindowManager canSendMoreRequests]) { - [self sendRequest:pendingXMPPStanzas attributes:nil namespaces:nil responseHandler:nil errorHandler:nil]; - [pendingXMPPStanzas removeAllObjects]; + if ([pendingXMPPStanzas count] > 0 ) + { + [self sendRequest:pendingXMPPStanzas attributes:nil namespaces:nil responseHandler:nil errorHandler:nil]; + [pendingXMPPStanzas removeAllObjects]; + } + if( state == DISCONNECTING ) + { + NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: self.lang, @"xml:lang", @"terminate", @"type", nil]; + NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; + [self sendRequest:nil attributes:attr namespaces:ns responseHandler:@selector(disconnectSessionResponseHandler:) errorHandler:nil]; + } } + } - (void)sendRequestsToHold @@ -430,10 +438,20 @@ - (void)requestFinished:(ASIHTTPRequest *)request [self sendRequestsToHold]; } +/* Not sending terminate request to the server - just disconnecting */ - (void)requestFailed:(ASIHTTPRequest *)request { - NSLog(@"BOSH: request Failed = %@", [request error]); - [request startSynchronous]; + NSError *error = [request error]; + NSLog(@"BOSH: request Failed = %@", error); + if ( [error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType ) + { + [request startSynchronous]; + } + else + { + [multicastDelegate transportWillDisconnect:self withError:error]; + [self disconnectSessionResponseHandler:nil]; + } } - (void)sendHTTPRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler From 2f50f8fd0a4571f67d297b33b324505fec1bd73a Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Fri, 1 Apr 2011 13:24:17 +0530 Subject: [PATCH 045/180] Logging now as used to be in dojo.xmpp --- Core/Transports/BoshTransport.m | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index f76c1b6..9be4c6f 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -321,10 +321,16 @@ - (BOOL) createSession:(NSError **)error [requestPayload release]; return YES; } - +- (NSString *)logRequestResponse:(NSData *)data +{ + NSXMLElement *ele = [self parseXMLData:data]; + long long rid = [self getRidInRequest:ele]; + NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return [NSString stringWithFormat:@"%qi] = %@", rid, [dataString autorelease]]; +} - (void)createSessionResponseHandler:(ASIHTTPRequest *)request { - NSLog(@"BOSH: Response = %@", [request responseString]); + NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); state = CONNECTED; NSXMLElement *rootElement = [self parseXMLData:[request responseData]]; @@ -430,8 +436,7 @@ - (void)broadcastStanzas:(NSXMLNode *)node /* Should call processRequestQueue after some timeOut */ - (void)requestFinished:(ASIHTTPRequest *)request { - NSString *responseString = [request responseString]; - NSLog(@"BOSH: response string = %@", responseString); + NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); NSString *postBodyString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; NSXMLElement *postBody = [[NSXMLElement alloc] initWithXMLString:postBodyString error:nil]; [boshWindowManager recievedResponse:[self parseXMLData:[request responseData]] forRid:[self getRidInRequest:postBody]]; @@ -442,7 +447,7 @@ - (void)requestFinished:(ASIHTTPRequest *)request - (void)requestFailed:(ASIHTTPRequest *)request { NSError *error = [request error]; - NSLog(@"BOSH: request Failed = %@", error); + NSLog(@"BOSH: Request Failed[%@", [self logRequestResponse:[request postBody]]); if ( [error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType ) { [request startSynchronous]; @@ -468,7 +473,7 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)respon [request startAsynchronous]; - NSLog(@"BOSH: Async Request Sent with data = %@", body); + NSLog(@"BOSH: SEND[%@", [self logRequestResponse:[request postBody]]); return; } From bf229c6800bc95305fbb5c217bf5a98edea52e1f Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 1 Apr 2011 13:59:42 +0530 Subject: [PATCH 046/180] Fix received rid check. --- Core/Transports/BoshTransport.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 9be4c6f..2037ff5 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -91,7 +91,7 @@ - (void)processResponses - (void)recievedResponse:(NSXMLElement *)response forRid:(long long)rid { NSAssert(rid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", rid, maxRidReceived); - NSAssert(rid < maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); + NSAssert(rid <= maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:rid]]; NSAssert( requestResponsePair != nil, @"Response rid not in queue"); requestResponsePair.response = response; From 8e4f17ebcff8a3ba5acbb52ae2c9c8cfe8713eff Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Fri, 1 Apr 2011 18:25:31 +0530 Subject: [PATCH 047/180] Copying the stanza before appending to request --- Core/Transports/BoshTransport.h | 1 + Core/Transports/BoshTransport.m | 146 ++++++++++++++++++-------------- 2 files changed, 82 insertions(+), 65 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 259fa4b..87e0de4 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -109,6 +109,7 @@ typedef enum { /* Methods used internally */ - (BOOL)canConnect; +- (NSString *)logRequestResponse:(NSData *)data; - (void)createSessionResponseHandler:(ASIHTTPRequest *)request; - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request; - (long long)generateRid; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 9be4c6f..2336b59 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -91,7 +91,7 @@ - (void)processResponses - (void)recievedResponse:(NSXMLElement *)response forRid:(long long)rid { NSAssert(rid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", rid, maxRidReceived); - NSAssert(rid < maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); + NSAssert(rid <= maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:rid]]; NSAssert( requestResponsePair != nil, @"Response rid not in queue"); requestResponsePair.response = response; @@ -321,68 +321,11 @@ - (BOOL) createSession:(NSError **)error [requestPayload release]; return YES; } -- (NSString *)logRequestResponse:(NSData *)data -{ - NSXMLElement *ele = [self parseXMLData:data]; - long long rid = [self getRidInRequest:ele]; - NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - return [NSString stringWithFormat:@"%qi] = %@", rid, [dataString autorelease]]; -} -- (void)createSessionResponseHandler:(ASIHTTPRequest *)request -{ - NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); - state = CONNECTED; - NSXMLElement *rootElement = [self parseXMLData:[request responseData]]; - - NSArray *responseAttributes = [rootElement attributes]; - /* Setting inactivity, sid, wait, hold, ack, lang, authid, secure, requests */ - for(NSXMLNode *attr in responseAttributes) - { - NSString *attrName = [attr name]; - NSString *attrValue = [attr stringValue]; - SEL setter = [self setterForProperty:attrName]; - - if([self respondsToSelector:setter]) - [self performSelector:setter withObject:attrValue]; - } - - /* Not doing anything with namespaces right now - because chirkut doesn't send it */ - //NSArray *responseNamespaces = [rootElement namespaces]; - - boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:[self getRidInRequest:rootElement]]; - [boshWindowManager setWindowSize:[requests unsignedIntValue]]; - - [multicastDelegate transportDidConnect:self]; - [multicastDelegate transportDidStartNegotiation:self]; - - if( [(NSXMLNode *)rootElement childCount] > 0 ) - [self broadcastStanzas:rootElement]; - - /* should we send these requests after a delay?? */ - [self sendRequestsToHold]; -} - -- (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request -{ - state = DISCONNECTED; - [boshWindowManager release]; - [multicastDelegate removeAllDelegates]; - [pendingXMPPStanzas removeAllObjects]; - self.myJID = nil; - nextRidToSend = [self generateRid]; - ack = [NSNumber numberWithInt:1]; - self.hold = [NSNumber numberWithInt:1]; - self.wait = [NSNumber numberWithDouble:60.0]; - [inactivity release]; - inactivity = [NSNumber numberWithInt:0]; - self.sid = nil; - self.authid = nil; - [multicastDelegate transportDidDisconnect:self]; -} - (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler { NSXMLElement *requestPayload = [self newBodyElementWithPayload:bodyPayload attributes:attributes namespaces:namespaces]; + NSLog(@"request paylaod = %@", requestPayload); [boshWindowManager sentRequest:requestPayload]; [self sendHTTPRequestWithBody:requestPayload responseHandler:responseHandler errorHandler:errorHandler]; [requestPayload release]; @@ -399,7 +342,7 @@ - (void)trySendingStanzas } if( state == DISCONNECTING ) { - NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: self.lang, @"xml:lang", @"terminate", @"type", nil]; + NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"terminate", @"type", nil]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; [self sendRequest:nil attributes:attr namespaces:ns responseHandler:@selector(disconnectSessionResponseHandler:) errorHandler:nil]; } @@ -410,7 +353,14 @@ - (void)trySendingStanzas - (void)sendRequestsToHold { while( [boshWindowManager canLetServerHoldRequests:[self.hold unsignedIntValue]] ) - [self sendRequest:nil attributes:nil namespaces:nil responseHandler:nil errorHandler:nil]; + [self sendRequest:nil attributes:nil namespaces:nil responseHandler:nil errorHandler:nil]; + + if( state == DISCONNECTING ) + { + NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"terminate", @"type", nil]; + NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; + [self sendRequest:nil attributes:attr namespaces:ns responseHandler:@selector(disconnectSessionResponseHandler:) errorHandler:nil]; + } } /* @@ -420,9 +370,11 @@ - (void)sendRequestsToHold */ - (void)broadcastStanzas:(NSXMLNode *)node { + NSUInteger level = [node level]; while( (node = [node nextNode]) ) { - if([node level] == 1) + + if([node level] == level + 1) { NSLog(@"BOSH: Passing to delegates the stanza = %@", node); [multicastDelegate transport:self didReceiveStanza:[(NSXMLElement *)node copy]]; @@ -433,6 +385,60 @@ - (void)broadcastStanzas:(NSXMLNode *)node #pragma mark - #pragma mark HTTP Request Response +- (void)createSessionResponseHandler:(ASIHTTPRequest *)request +{ + NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); + state = CONNECTED; + NSXMLElement *rootElement = [self parseXMLData:[request responseData]]; + + NSArray *responseAttributes = [rootElement attributes]; + /* Setting inactivity, sid, wait, hold, ack, lang, authid, secure, requests */ + for(NSXMLNode *attr in responseAttributes) + { + NSString *attrName = [attr name]; + NSString *attrValue = [attr stringValue]; + SEL setter = [self setterForProperty:attrName]; + + if([self respondsToSelector:setter]) + [self performSelector:setter withObject:attrValue]; + } + + /* Not doing anything with namespaces right now - because chirkut doesn't send it */ + //NSArray *responseNamespaces = [rootElement namespaces]; + + boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:[self getRidInRequest:rootElement]]; + [boshWindowManager setWindowSize:[requests unsignedIntValue]]; + + [multicastDelegate transportDidConnect:self]; + [multicastDelegate transportDidStartNegotiation:self]; + + NSLog(@"%ud", [(NSXMLNode *)rootElement childCount]); + NSLog(@"root elemetn = %@", rootElement); + if( [(NSXMLNode *)rootElement childCount] > 0 ) + [self broadcastStanzas:rootElement]; + + /* should we send these requests after a delay?? */ + [self sendRequestsToHold]; +} + +- (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request +{ + NSLog(@"disconnectSessionResponseHandler"); + NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); + state = DISCONNECTED; + [multicastDelegate transportDidDisconnect:self]; + [boshWindowManager release]; + [pendingXMPPStanzas removeAllObjects]; + nextRidToSend = [self generateRid]; + ack = [NSNumber numberWithInt:1]; + self.hold = [NSNumber numberWithInt:1]; + self.wait = [NSNumber numberWithDouble:60.0]; + [inactivity release]; + inactivity = [NSNumber numberWithInt:0]; + self.sid = nil; + self.authid = nil; +} + /* Should call processRequestQueue after some timeOut */ - (void)requestFinished:(ASIHTTPRequest *)request { @@ -446,6 +452,7 @@ - (void)requestFinished:(ASIHTTPRequest *)request /* Not sending terminate request to the server - just disconnecting */ - (void)requestFailed:(ASIHTTPRequest *)request { + if( ![self isConnected] ) return ; NSError *error = [request error]; NSLog(@"BOSH: Request Failed[%@", [self logRequestResponse:[request postBody]]); if ( [error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType ) @@ -500,7 +507,7 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMut if(payload != nil) for(NSXMLElement *child in payload) - [body addChild:child]; + [body addChild:[[child copy] autorelease]]; ++nextRidToSend; @@ -509,13 +516,14 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMut - (long long)getRidInRequest:(NSXMLElement *)body { - return [[[body attributeForName:@"rid"] stringValue] longLongValue]; + + return body && [body attributeForName:@"rid"]?[[[body attributeForName:@"rid"] stringValue] longLongValue]:-1; } - (NSXMLElement *)returnRootElement:(NSXMLDocument *)doc { NSXMLElement *rootElement = [doc rootElement]; - [doc release]; + //[doc autorelease]; return rootElement; } @@ -580,6 +588,14 @@ - (NSNumber *)numberFromString:(NSString *)stringNumber return number; } +- (NSString *)logRequestResponse:(NSData *)data +{ + NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSXMLElement *ele = [self parseXMLData:data]; + long long rid = [self getRidInRequest:ele]; + return [NSString stringWithFormat:@"%qi] = %@", rid, [dataString autorelease]]; +} + - (void)dealloc { [wait_ release]; From bc15980f6a076106abb8c5723e8b334ac3ab525a Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Fri, 1 Apr 2011 19:42:52 +0530 Subject: [PATCH 048/180] Cancelling pending http requests when disconnected --- Core/Transports/BoshTransport.m | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 2336b59..c79ff24 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -200,10 +200,11 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< url_ = [url copy]; domain_ = [domain copy]; myJID_ = nil; - state = DISCONNECTED; + state = DISCONNECTED; - /* Keeping a random capacity right now */ - pendingXMPPStanzas = [[NSMutableArray alloc] initWithCapacity:25]; + /* Keeping a random capacity right now */ + pendingXMPPStanzas = [[NSMutableArray alloc] initWithCapacity:25]; + pendingHttpRequests = [[NSMutableSet alloc] initWithCapacity:3]; } return self; } @@ -426,7 +427,6 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request NSLog(@"disconnectSessionResponseHandler"); NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); state = DISCONNECTED; - [multicastDelegate transportDidDisconnect:self]; [boshWindowManager release]; [pendingXMPPStanzas removeAllObjects]; nextRidToSend = [self generateRid]; @@ -436,7 +436,11 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request [inactivity release]; inactivity = [NSNumber numberWithInt:0]; self.sid = nil; - self.authid = nil; + self.authid = nil; + for ( ASIHTTPRequest *pendingRequest in pendingHttpRequests ) + [pendingRequest cancel]; + [pendingHttpRequests removeAllObjects]; + [multicastDelegate transportDidDisconnect:self]; } /* Should call processRequestQueue after some timeOut */ @@ -444,8 +448,9 @@ - (void)requestFinished:(ASIHTTPRequest *)request { NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); NSString *postBodyString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; + [pendingHttpRequests removeObject:request]; NSXMLElement *postBody = [[NSXMLElement alloc] initWithXMLString:postBodyString error:nil]; - [boshWindowManager recievedResponse:[self parseXMLData:[request responseData]] forRid:[self getRidInRequest:postBody]]; + [boshWindowManager recievedResponse:[self parseXMLData:[request responseData]] forRid:[self getRidInRequest:postBody]]; [self sendRequestsToHold]; } @@ -479,7 +484,7 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)respon if(errorHandler) [request setDidFailSelector:errorHandler]; [request startAsynchronous]; - + [pendingHttpRequests addObject:request]; NSLog(@"BOSH: SEND[%@", [self logRequestResponse:[request postBody]]); return; } From 87ac768de4f8d7ed7f766c6fab438a24e81728ca Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Fri, 1 Apr 2011 19:47:09 +0530 Subject: [PATCH 049/180] Forgot to commit BoshTransport.h --- Core/Transports/BoshTransport.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 87e0de4..1b622f4 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -68,7 +68,8 @@ typedef enum { NSMutableArray *pendingXMPPStanzas; BoshWindowManager *boshWindowManager; BoshTransportState state; - + + NSMutableSet *pendingHttpRequests; MulticastDelegate *multicastDelegate; } From 78292a7bd9b54cf4c9fd2aeba5a7b4d8c59ad483 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Mon, 4 Apr 2011 13:28:03 +0530 Subject: [PATCH 050/180] Clearing delegates before cancelling the requests. --- Core/Transports/BoshTransport.h | 4 ++-- Core/Transports/BoshTransport.m | 28 ++++++++++++---------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 1b622f4..4544b8d 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -62,13 +62,13 @@ typedef enum { NSString *STREAM_NS; NSString *BODY_NS; NSString *XMPP_NS; - + long long nextRidToSend; NSMutableArray *pendingXMPPStanzas; BoshWindowManager *boshWindowManager; BoshTransportState state; - + NSMutableSet *pendingHttpRequests; MulticastDelegate *multicastDelegate; } diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index c79ff24..3a27841 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -332,6 +332,15 @@ - (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)att [requestPayload release]; } +- (void)sendTerminateRequest +{ + NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"terminate", @"type", nil]; + NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; + [self sendRequest:nil attributes:attr namespaces:ns responseHandler:@selector(disconnectSessionResponseHandler:) errorHandler:nil]; + for ( ASIHTTPRequest *pendingRequest in pendingHttpRequests ) [pendingRequest clearDelegatesAndCancel]; + [pendingHttpRequests removeAllObjects]; +} + - (void)trySendingStanzas { if( [boshWindowManager canSendMoreRequests]) @@ -341,14 +350,8 @@ - (void)trySendingStanzas [self sendRequest:pendingXMPPStanzas attributes:nil namespaces:nil responseHandler:nil errorHandler:nil]; [pendingXMPPStanzas removeAllObjects]; } - if( state == DISCONNECTING ) - { - NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"terminate", @"type", nil]; - NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; - [self sendRequest:nil attributes:attr namespaces:ns responseHandler:@selector(disconnectSessionResponseHandler:) errorHandler:nil]; - } + if( state == DISCONNECTING ) [self sendTerminateRequest]; } - } - (void)sendRequestsToHold @@ -356,12 +359,7 @@ - (void)sendRequestsToHold while( [boshWindowManager canLetServerHoldRequests:[self.hold unsignedIntValue]] ) [self sendRequest:nil attributes:nil namespaces:nil responseHandler:nil errorHandler:nil]; - if( state == DISCONNECTING ) - { - NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"terminate", @"type", nil]; - NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; - [self sendRequest:nil attributes:attr namespaces:ns responseHandler:@selector(disconnectSessionResponseHandler:) errorHandler:nil]; - } + if( state == DISCONNECTING ) [self sendTerminateRequest]; } /* @@ -428,6 +426,7 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); state = DISCONNECTED; [boshWindowManager release]; + boshWindowManager = nil; [pendingXMPPStanzas removeAllObjects]; nextRidToSend = [self generateRid]; ack = [NSNumber numberWithInt:1]; @@ -437,9 +436,6 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request inactivity = [NSNumber numberWithInt:0]; self.sid = nil; self.authid = nil; - for ( ASIHTTPRequest *pendingRequest in pendingHttpRequests ) - [pendingRequest cancel]; - [pendingHttpRequests removeAllObjects]; [multicastDelegate transportDidDisconnect:self]; } From 31d0cd3490e36ca8a7c2976bc2d244d95a58a408 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Mon, 4 Apr 2011 13:46:00 +0530 Subject: [PATCH 051/180] Clearing pending requests after the response for terminate comes back - irrespective of whta the response is --- Core/Transports/BoshTransport.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 3a27841..cf55bbb 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -337,8 +337,6 @@ - (void)sendTerminateRequest NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"terminate", @"type", nil]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; [self sendRequest:nil attributes:attr namespaces:ns responseHandler:@selector(disconnectSessionResponseHandler:) errorHandler:nil]; - for ( ASIHTTPRequest *pendingRequest in pendingHttpRequests ) [pendingRequest clearDelegatesAndCancel]; - [pendingHttpRequests removeAllObjects]; } - (void)trySendingStanzas @@ -424,6 +422,8 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request { NSLog(@"disconnectSessionResponseHandler"); NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); + for ( ASIHTTPRequest *pendingRequest in pendingHttpRequests ) [pendingRequest clearDelegatesAndCancel]; + [pendingHttpRequests removeAllObjects]; state = DISCONNECTED; [boshWindowManager release]; boshWindowManager = nil; From 165d968f2b87b7b2da3196a95006b10e38a363de Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Wed, 6 Apr 2011 19:40:40 +0530 Subject: [PATCH 052/180] Handling the cases where server sends terminate type in response --- Core/Transports/BoshTransport.h | 8 ++- Core/Transports/BoshTransport.m | 102 +++++++++++++++++++++++++------- 2 files changed, 87 insertions(+), 23 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 4544b8d..dbf4de4 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -21,7 +21,8 @@ typedef enum { CONNECTED = 0, CONNECTING = 1, DISCONNECTING = 2, - DISCONNECTED = 3 + DISCONNECTED = 3, + TERMINATED = 4 } BoshTransportState; @protocol BoshWindowProtocol @@ -48,6 +49,7 @@ typedef enum { - (void)sentRequest:(NSXMLElement *)request; - (void)recievedResponse:(NSXMLElement *)response forRid:(long long)rid; - (BOOL)canSendMoreRequests; +- (NSNumber *)maxRidReceived; - (BOOL)canLetServerHoldRequests:(long long)hold; - (NSXMLElement *)getRequestForRid:(long long)rid; - (id)initWithDelegate:(id)del rid:(long long)rid; @@ -56,7 +58,6 @@ typedef enum { @interface BoshTransport : NSObject { NSString *boshVersion; - NSNumber *ack; NSString *content; NSString *STREAM_NS; @@ -71,6 +72,7 @@ typedef enum { NSMutableSet *pendingHttpRequests; MulticastDelegate *multicastDelegate; + NSError *disconnectError_; } @property(retain) XMPPJID *myJID; @@ -84,6 +86,7 @@ typedef enum { @property(copy) NSString *authid; @property(copy) NSString *sid; @property(copy) NSString *url; +@property(readonly) NSError *disconnectError; /* init Methods */ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)host; @@ -110,6 +113,7 @@ typedef enum { /* Methods used internally */ - (BOOL)canConnect; +- (void)handleTerminateInResponse:(NSXMLElement *)parsedResponse; - (NSString *)logRequestResponse:(NSData *)data; - (void)createSessionResponseHandler:(ASIHTTPRequest *)request; - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index cf55bbb..4a80db9 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -42,6 +42,11 @@ @implementation BoshWindowManager @synthesize windowSize; @synthesize outstandingRequests; +- (NSNumber *)maxRidReceived +{ + return [NSNumber numberWithLongLong:maxRidReceived]; +} + - (long long)getRidInBody:(NSXMLElement *)body { return [[[body attributeForName:@"rid"] stringValue] longLongValue]; @@ -121,6 +126,7 @@ - (void) dealloc @end @interface BoshTransport() +@property(readwrite, assign) NSError *disconnectError; - (void)setInactivity:(NSString *)givenInactivity; - (void)setSecure:(NSString *)isSecure; - (void)setRequests:(NSString *)maxRequests; @@ -141,6 +147,7 @@ @implementation BoshTransport @synthesize secure; @synthesize authid; @synthesize requests; +@synthesize disconnectError = disconnectError_; #pragma mark - #pragma mark Private Accessor Method Implementation @@ -183,7 +190,6 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< content = [NSString stringWithFormat:@"text/xml; charset=utf-8"]; wait_ = [[NSNumber alloc] initWithDouble:60.0]; hold_ = [[NSNumber alloc] initWithInt:1]; - ack = [NSNumber numberWithInt:1]; nextRidToSend = [self generateRid]; @@ -201,7 +207,7 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< domain_ = [domain copy]; myJID_ = nil; state = DISCONNECTED; - + disconnectError_ = nil; /* Keeping a random capacity right now */ pendingXMPPStanzas = [[NSMutableArray alloc] initWithCapacity:25]; pendingHttpRequests = [[NSMutableSet alloc] initWithCapacity:3]; @@ -312,8 +318,8 @@ - (BOOL)canConnect - (BOOL) createSession:(NSError **)error { - NSArray *keys = [NSArray arrayWithObjects:@"content", @"hold", @"to", @"ver", @"wait", @"ack", @"xml:lang", @"from", @"secure", nil]; - NSArray *objects = [NSArray arrayWithObjects:content, self.hold, self.domain, boshVersion, self.wait, ack, self.lang, [self.myJID bare], @"false", nil]; + NSArray *keys = [NSArray arrayWithObjects:@"content", @"hold", @"to", @"ver", @"wait", @"xml:lang", @"from", @"secure", @"inactivity", nil]; + NSArray *objects = [NSArray arrayWithObjects:content, self.hold, self.domain, boshVersion, self.wait, self.lang, [self.myJID bare], @"false", @"10", nil]; NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjects:[self convertToStrings:objects] forKeys:keys]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; @@ -382,13 +388,20 @@ - (void)broadcastStanzas:(NSXMLNode *)node #pragma mark - #pragma mark HTTP Request Response +/* + handle terminate in session creation + type = terminate sent with content = + use the sid of the request not response. +*/ - (void)createSessionResponseHandler:(ASIHTTPRequest *)request { NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); - state = CONNECTED; NSXMLElement *rootElement = [self parseXMLData:[request responseData]]; - + + [self handleTerminateInResponse:rootElement]; + NSArray *responseAttributes = [rootElement attributes]; + /* Setting inactivity, sid, wait, hold, ack, lang, authid, secure, requests */ for(NSXMLNode *attr in responseAttributes) { @@ -405,12 +418,10 @@ - (void)createSessionResponseHandler:(ASIHTTPRequest *)request boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:[self getRidInRequest:rootElement]]; [boshWindowManager setWindowSize:[requests unsignedIntValue]]; - + state = CONNECTED; [multicastDelegate transportDidConnect:self]; [multicastDelegate transportDidStartNegotiation:self]; - NSLog(@"%ud", [(NSXMLNode *)rootElement childCount]); - NSLog(@"root elemetn = %@", rootElement); if( [(NSXMLNode *)rootElement childCount] > 0 ) [self broadcastStanzas:rootElement]; @@ -422,14 +433,20 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request { NSLog(@"disconnectSessionResponseHandler"); NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); - for ( ASIHTTPRequest *pendingRequest in pendingHttpRequests ) [pendingRequest clearDelegatesAndCancel]; + if(self.disconnectError) + { + [multicastDelegate transportWillDisconnect:self withError:self.disconnectError]; + [disconnectError_ release]; + } + for ( ASIHTTPRequest *pendingRequest in pendingHttpRequests ) + if(pendingRequest != request) [pendingRequest clearDelegatesAndCancel]; + [pendingHttpRequests removeAllObjects]; state = DISCONNECTED; [boshWindowManager release]; boshWindowManager = nil; [pendingXMPPStanzas removeAllObjects]; nextRidToSend = [self generateRid]; - ack = [NSNumber numberWithInt:1]; self.hold = [NSNumber numberWithInt:1]; self.wait = [NSNumber numberWithDouble:60.0]; [inactivity release]; @@ -437,17 +454,49 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request self.sid = nil; self.authid = nil; [multicastDelegate transportDidDisconnect:self]; + NSLog(@"Disconnect handler completed"); } -/* Should call processRequestQueue after some timeOut */ +- (void)handleTerminateInResponse:(NSXMLElement *)parsedResponse +{ + NSXMLNode *typeAttribute = [parsedResponse attributeForName:@"type"]; + if( typeAttribute != nil && [[typeAttribute stringValue] isEqualToString:@"terminate"] ) + { + disconnectError_ = [[NSError alloc] initWithDomain:[[parsedResponse attributeForName:@"condition"] stringValue] code:1 userInfo:nil]; + state = TERMINATED; + } +} + +/* + Should call processRequestQueue after some timeOut + Handle terminate response sent in any request. + */ - (void)requestFinished:(ASIHTTPRequest *)request { - NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); - NSString *postBodyString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; + NSData *responseData = [request responseData]; + NSLog(@"BOSH: RECD[%@", [self logRequestResponse:responseData]); [pendingHttpRequests removeObject:request]; + NSXMLElement *parsedResponse = [self parseXMLData:responseData]; + + [self handleTerminateInResponse:parsedResponse]; + + NSString *postBodyString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; NSXMLElement *postBody = [[NSXMLElement alloc] initWithXMLString:postBodyString error:nil]; - [boshWindowManager recievedResponse:[self parseXMLData:[request responseData]] forRid:[self getRidInRequest:postBody]]; - [self sendRequestsToHold]; + [boshWindowManager recievedResponse:parsedResponse forRid:[self getRidInRequest:postBody]]; + + if(state == TERMINATED) [self disconnectSessionResponseHandler:request]; + else [self sendRequestsToHold]; + + [postBody release]; + [postBodyString release]; +} + +- (void)resendRequest:(ASIHTTPRequest *)request +{ + + [self sendHTTPRequestWithBody:[self parseXMLData:[request postBody]] + responseHandler:[request didFinishSelector] + errorHandler:[request didFailSelector]]; } /* Not sending terminate request to the server - just disconnecting */ @@ -456,14 +505,17 @@ - (void)requestFailed:(ASIHTTPRequest *)request if( ![self isConnected] ) return ; NSError *error = [request error]; NSLog(@"BOSH: Request Failed[%@", [self logRequestResponse:[request postBody]]); - if ( [error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType ) - { - [request startSynchronous]; + NSLog(@"Failure HTTP code = %d, domain = %@", [request responseStatusCode],[[request error] domain]); + if( [error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType ) { + NSLog(@"Resending the request"); + [pendingHttpRequests removeObject:request]; + [self resendRequest:request]; } else { + NSLog(@"disconnecting due to request failure"); [multicastDelegate transportWillDisconnect:self withError:error]; - [self disconnectSessionResponseHandler:nil]; + [self disconnectSessionResponseHandler:request]; } } @@ -495,7 +547,15 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMut /* Adding sid attribute on every outgoing request */ if( self.sid ) [attributes setValue:self.sid forKey:@"sid"]; - + + /* Adding ack attribute on every outgoing request */ + if( boshWindowManager != nil ) { + NSNumber *ack = [boshWindowManager maxRidReceived]; + if( [ack longLongValue] != nextRidToSend - 1) [attributes setValue:[ack stringValue] forKey:@"ack"]; + + } + else [attributes setValue:@"1" forKey:@"ack"]; + [attributes setValue:[NSString stringWithFormat:@"%d", nextRidToSend] forKey:@"rid"]; /* Adding the BODY_NS namespace on every outgoing request */ From 9297826a178822256de01abc57cbcb1c4dada7b7 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Fri, 8 Apr 2011 01:49:16 +0530 Subject: [PATCH 053/180] Handling the case where the connection request recieves a terminate response - Also doing some refactoring to make the code more manageble --- Core/Transports/BoshTransport.h | 32 ++++++- Core/Transports/BoshTransport.m | 164 +++++++++++++++----------------- 2 files changed, 107 insertions(+), 89 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index dbf4de4..c2947f5 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -17,6 +17,30 @@ typedef enum { NAMESPACE_TYPE = 1 } XMLNodeType; +/* + host-unknown + host-gone + item-not-found + policy-violation + remote-connection-failed + bad-request + internal-server-error + remote-stream-error + undefined-condition + */ + +typedef enum { + HOST_UNKNOWN = 0, + HOST_GONE = 1, + ITEM_NOT_FOUND = 2, + POLICY_VIOLATION = 3, + REMOTE_CONNECTION_FAILED = 4, + BAD_REQUEST = 5, + INTERNAL_SERVER_ERROR = 6, + REMOTE_STREAM_ERROR = 7, + UNDEFINED_CONDITION = 8 +} BoshTerminateConditions; + typedef enum { CONNECTED = 0, CONNECTING = 1, @@ -113,18 +137,18 @@ typedef enum { /* Methods used internally */ - (BOOL)canConnect; -- (void)handleTerminateInResponse:(NSXMLElement *)parsedResponse; +- (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse; - (NSString *)logRequestResponse:(NSData *)data; -- (void)createSessionResponseHandler:(ASIHTTPRequest *)request; +- (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse; - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request; - (long long)generateRid; - (NSArray *)convertToStrings:(NSArray *)array; - (SEL)setterForProperty:(NSString *)property; - (NSNumber *)numberFromString:(NSString *)stringNumber; -- (void)sendHTTPRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler; +- (void)sendHTTPRequestWithBody:(NSXMLElement *)body; - (void)broadcastStanzas:(NSXMLNode *)node; - (void)trySendingStanzas; -- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler; +- (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; - (long long)getRidInRequest:(NSXMLElement *)body; - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; - (NSArray *)createXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNodeType)type; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 4a80db9..7992e85 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -19,7 +19,8 @@ @implementation RequestResponsePair - (id) initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response { - if( (self = [super init]) ) { + if( (self = [super init]) ) + { request_ = request; response_ = response; } @@ -63,7 +64,6 @@ - (id)initWithDelegate:(id)del rid:(long long)rid { window = [[NSMutableDictionary alloc] initWithCapacity:4]; windowSize = 0; - outstandingRequests = 0; maxRidSent = rid; maxRidReceived = rid; delegate = del; @@ -162,6 +162,7 @@ - (void)setInactivity:(NSString *)inactivityString - (void)setRequests:(NSString *)requestsString { NSNumber *maxRequests = [self numberFromString:requestsString]; + [boshWindowManager setWindowSize:[maxRequests longLongValue]]; [requests autorelease]; requests = [maxRequests retain]; } @@ -208,9 +209,13 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< myJID_ = nil; state = DISCONNECTED; disconnectError_ = nil; + /* Keeping a random capacity right now */ pendingXMPPStanzas = [[NSMutableArray alloc] initWithCapacity:25]; pendingHttpRequests = [[NSMutableSet alloc] initWithCapacity:3]; + + boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:(nextRidToSend - 1)]; + [boshWindowManager setWindowSize:1]; } return self; } @@ -241,10 +246,9 @@ - (void)restartStream return ; } NSLog(@"Bosh: Will Restart Stream"); - NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: self.lang, @"xml:lang", @"true", @"xmpp:restart", nil]; + NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"true", @"xmpp:restart", nil]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys:XMPP_NS, @"xmpp", nil]; - - [self sendRequest:nil attributes:attr namespaces:ns responseHandler:nil errorHandler:nil]; + [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:attr namespaces:ns]; } - (void)disconnect @@ -257,7 +261,6 @@ - (void)disconnect NSLog(@"Bosh: Will Terminate Session"); state = DISCONNECTING; [multicastDelegate transportWillDisconnect:self]; - } - (BOOL)sendStanza:(NSXMLElement *)stanza @@ -316,25 +319,24 @@ - (BOOL)canConnect return YES; } -- (BOOL) createSession:(NSError **)error +- (BOOL)createSession:(NSError **)error { - NSArray *keys = [NSArray arrayWithObjects:@"content", @"hold", @"to", @"ver", @"wait", @"xml:lang", @"from", @"secure", @"inactivity", nil]; - NSArray *objects = [NSArray arrayWithObjects:content, self.hold, self.domain, boshVersion, self.wait, self.lang, [self.myJID bare], @"false", @"10", nil]; + NSArray *keys = [NSArray arrayWithObjects:@"content", @"hold", @"to", @"ver", @"wait", @"from", @"secure", @"inactivity", nil]; + NSArray *objects = [NSArray arrayWithObjects:content, self.hold, self.domain, boshVersion, self.wait, [self.myJID bare], @"false", @"10", nil]; NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjects:[self convertToStrings:objects] forKeys:keys]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; - NSXMLElement *requestPayload = [self newBodyElementWithPayload:nil attributes:attr namespaces:ns]; - [self sendHTTPRequestWithBody:requestPayload responseHandler:@selector(createSessionResponseHandler:) errorHandler:nil]; - [requestPayload release]; + [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:attr namespaces:ns]; + return YES; } -- (void)sendRequest:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler +- (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces { NSXMLElement *requestPayload = [self newBodyElementWithPayload:bodyPayload attributes:attributes namespaces:namespaces]; NSLog(@"request paylaod = %@", requestPayload); [boshWindowManager sentRequest:requestPayload]; - [self sendHTTPRequestWithBody:requestPayload responseHandler:responseHandler errorHandler:errorHandler]; + [self sendHTTPRequestWithBody:requestPayload]; [requestPayload release]; } @@ -342,28 +344,29 @@ - (void)sendTerminateRequest { NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"terminate", @"type", nil]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; - [self sendRequest:nil attributes:attr namespaces:ns responseHandler:@selector(disconnectSessionResponseHandler:) errorHandler:nil]; + [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:attr namespaces:ns]; } - (void)trySendingStanzas { - if( [boshWindowManager canSendMoreRequests]) - { - if ([pendingXMPPStanzas count] > 0 ) + if( [boshWindowManager canSendMoreRequests]) + { + if([pendingXMPPStanzas count] > 0) { - [self sendRequest:pendingXMPPStanzas attributes:nil namespaces:nil responseHandler:nil errorHandler:nil]; + [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas attributes:nil namespaces:nil]; [pendingXMPPStanzas removeAllObjects]; } - if( state == DISCONNECTING ) [self sendTerminateRequest]; - } + else if(state == DISCONNECTING) + [self sendTerminateRequest]; + } } - (void)sendRequestsToHold { - while( [boshWindowManager canLetServerHoldRequests:[self.hold unsignedIntValue]] ) - [self sendRequest:nil attributes:nil namespaces:nil responseHandler:nil errorHandler:nil]; - - if( state == DISCONNECTING ) [self sendTerminateRequest]; + while( [boshWindowManager canLetServerHoldRequests:[self.hold unsignedIntValue]] && state == CONNECTED) + { + [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:nil namespaces:nil]; + } } /* @@ -388,21 +391,37 @@ - (void)broadcastStanzas:(NSXMLNode *)node #pragma mark - #pragma mark HTTP Request Response -/* - handle terminate in session creation - type = terminate sent with content = - use the sid of the request not response. -*/ -- (void)createSessionResponseHandler:(ASIHTTPRequest *)request -{ - NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); - NSXMLElement *rootElement = [self parseXMLData:[request responseData]]; - - [self handleTerminateInResponse:rootElement]; - - NSArray *responseAttributes = [rootElement attributes]; +/* + host-unknown + host-gone + item-not-found + policy-violation + remote-connection-failed + bad-request + internal-server-error + remote-stream-error + undefined-condition + */ +- (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse +{ + NSXMLNode *typeAttribute = [parsedResponse attributeForName:@"type"]; + if( typeAttribute != nil && [[typeAttribute stringValue] isEqualToString:@"terminate"] ) + { + NSString *condition = [[parsedResponse attributeForName:@"condition"] stringValue]; + if( [condition isEqualToString:@""] ) + + disconnectError_ = [[NSError alloc] initWithDomain:[[parsedResponse attributeForName:@"condition"] stringValue] code:1 userInfo:nil]; + state = TERMINATED; + } + else if( !self.sid ) + [self createSessionResponseHandler:parsedResponse]; +} + +- (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse +{ + NSArray *responseAttributes = [parsedResponse attributes]; - /* Setting inactivity, sid, wait, hold, ack, lang, authid, secure, requests */ + /* Setting inactivity, sid, wait, hold, lang, authid, secure, requests */ for(NSXMLNode *attr in responseAttributes) { NSString *attrName = [attr name]; @@ -416,23 +435,14 @@ - (void)createSessionResponseHandler:(ASIHTTPRequest *)request /* Not doing anything with namespaces right now - because chirkut doesn't send it */ //NSArray *responseNamespaces = [rootElement namespaces]; - boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:[self getRidInRequest:rootElement]]; - [boshWindowManager setWindowSize:[requests unsignedIntValue]]; state = CONNECTED; [multicastDelegate transportDidConnect:self]; [multicastDelegate transportDidStartNegotiation:self]; - - if( [(NSXMLNode *)rootElement childCount] > 0 ) - [self broadcastStanzas:rootElement]; - - /* should we send these requests after a delay?? */ - [self sendRequestsToHold]; } - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request { NSLog(@"disconnectSessionResponseHandler"); - NSLog(@"BOSH: RECD[%@", [self logRequestResponse:[request responseData]]); if(self.disconnectError) { [multicastDelegate transportWillDisconnect:self withError:self.disconnectError]; @@ -442,10 +452,10 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request if(pendingRequest != request) [pendingRequest clearDelegatesAndCancel]; [pendingHttpRequests removeAllObjects]; - state = DISCONNECTED; - [boshWindowManager release]; - boshWindowManager = nil; [pendingXMPPStanzas removeAllObjects]; + + state = DISCONNECTED; + nextRidToSend = [self generateRid]; self.hold = [NSNumber numberWithInt:1]; self.wait = [NSNumber numberWithDouble:60.0]; @@ -453,19 +463,13 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request inactivity = [NSNumber numberWithInt:0]; self.sid = nil; self.authid = nil; + [boshWindowManager release]; + boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:(nextRidToSend - 1)]; + [boshWindowManager setWindowSize:1]; + [multicastDelegate transportDidDisconnect:self]; - NSLog(@"Disconnect handler completed"); } -- (void)handleTerminateInResponse:(NSXMLElement *)parsedResponse -{ - NSXMLNode *typeAttribute = [parsedResponse attributeForName:@"type"]; - if( typeAttribute != nil && [[typeAttribute stringValue] isEqualToString:@"terminate"] ) - { - disconnectError_ = [[NSError alloc] initWithDomain:[[parsedResponse attributeForName:@"condition"] stringValue] code:1 userInfo:nil]; - state = TERMINATED; - } -} /* Should call processRequestQueue after some timeOut @@ -478,38 +482,34 @@ - (void)requestFinished:(ASIHTTPRequest *)request [pendingHttpRequests removeObject:request]; NSXMLElement *parsedResponse = [self parseXMLData:responseData]; - [self handleTerminateInResponse:parsedResponse]; + [self handleAttributesInResponse:parsedResponse]; NSString *postBodyString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; NSXMLElement *postBody = [[NSXMLElement alloc] initWithXMLString:postBodyString error:nil]; [boshWindowManager recievedResponse:parsedResponse forRid:[self getRidInRequest:postBody]]; if(state == TERMINATED) [self disconnectSessionResponseHandler:request]; - else [self sendRequestsToHold]; + else { + [self trySendingStanzas]; + [self sendRequestsToHold]; + } [postBody release]; [postBodyString release]; } -- (void)resendRequest:(ASIHTTPRequest *)request -{ - - [self sendHTTPRequestWithBody:[self parseXMLData:[request postBody]] - responseHandler:[request didFinishSelector] - errorHandler:[request didFailSelector]]; -} - /* Not sending terminate request to the server - just disconnecting */ - (void)requestFailed:(ASIHTTPRequest *)request { if( ![self isConnected] ) return ; NSError *error = [request error]; + [pendingHttpRequests removeObject:request]; + NSLog(@"BOSH: Request Failed[%@", [self logRequestResponse:[request postBody]]); - NSLog(@"Failure HTTP code = %d, domain = %@", [request responseStatusCode],[[request error] domain]); + NSLog(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); if( [error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType ) { NSLog(@"Resending the request"); - [pendingHttpRequests removeObject:request]; - [self resendRequest:request]; + [self sendHTTPRequestWithBody:[self parseXMLData:[request postBody]]]; } else { @@ -519,7 +519,7 @@ - (void)requestFailed:(ASIHTTPRequest *)request } } -- (void)sendHTTPRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)responseHandler errorHandler:(SEL)errorHandler +- (void)sendHTTPRequestWithBody:(NSXMLElement *)body { NSURL *url = [NSURL URLWithString:self.url]; ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; @@ -528,11 +528,8 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body responseHandler:(SEL)respon [request setTimeOutSeconds:[self.wait doubleValue]+4]; if(body) [request appendPostData:[[body compactXMLString] dataUsingEncoding:NSUTF8StringEncoding]]; - if(responseHandler) [request setDidFinishSelector:responseHandler]; - if(errorHandler) [request setDidFailSelector:errorHandler]; - - [request startAsynchronous]; [pendingHttpRequests addObject:request]; + [request startAsynchronous]; NSLog(@"BOSH: SEND[%@", [self logRequestResponse:[request postBody]]); return; } @@ -544,15 +541,12 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMut { attributes = attributes?attributes:[NSMutableDictionary dictionaryWithCapacity:3]; namespaces = namespaces?namespaces:[NSMutableDictionary dictionaryWithCapacity:1]; - - /* Adding sid attribute on every outgoing request */ - if( self.sid ) [attributes setValue:self.sid forKey:@"sid"]; - /* Adding ack attribute on every outgoing request */ - if( boshWindowManager != nil ) { + /* Adding ack and sid attribute on every outgoing request after sid is created */ + if( self.sid ) { + [attributes setValue:self.sid forKey:@"sid"]; NSNumber *ack = [boshWindowManager maxRidReceived]; if( [ack longLongValue] != nextRidToSend - 1) [attributes setValue:[ack stringValue] forKey:@"ack"]; - } else [attributes setValue:@"1" forKey:@"ack"]; From e68947addfb41a9d326b638ea49098d34ae4c23b Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Fri, 8 Apr 2011 02:09:36 +0530 Subject: [PATCH 054/180] Catching Terminate conditions in all responses --- Core/Transports/BoshTransport.h | 18 +++++++++--------- Core/Transports/BoshTransport.m | 30 +++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index c2947f5..4c9e92f 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -30,15 +30,15 @@ typedef enum { */ typedef enum { - HOST_UNKNOWN = 0, - HOST_GONE = 1, - ITEM_NOT_FOUND = 2, - POLICY_VIOLATION = 3, - REMOTE_CONNECTION_FAILED = 4, - BAD_REQUEST = 5, - INTERNAL_SERVER_ERROR = 6, - REMOTE_STREAM_ERROR = 7, - UNDEFINED_CONDITION = 8 + HOST_UNKNOWN = 1, + HOST_GONE = 2, + ITEM_NOT_FOUND = 3, + POLICY_VIOLATION = 4, + REMOTE_CONNECTION_FAILED = 5, + BAD_REQUEST = 6, + INTERNAL_SERVER_ERROR = 7, + REMOTE_STREAM_ERROR = 8, + UNDEFINED_CONDITION = 9 } BoshTerminateConditions; typedef enum { diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 7992e85..5d026a3 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -407,10 +407,30 @@ - (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse NSXMLNode *typeAttribute = [parsedResponse attributeForName:@"type"]; if( typeAttribute != nil && [[typeAttribute stringValue] isEqualToString:@"terminate"] ) { - NSString *condition = [[parsedResponse attributeForName:@"condition"] stringValue]; - if( [condition isEqualToString:@""] ) - - disconnectError_ = [[NSError alloc] initWithDomain:[[parsedResponse attributeForName:@"condition"] stringValue] code:1 userInfo:nil]; + NSXMLNode *conditionNode = [parsedResponse attributeForName:@"condition"]; + if(conditionNode != nil) + { + NSString *condition = [conditionNode stringValue]; + if( [condition isEqualToString:@"host-unknown"] ) + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:HOST_UNKNOWN userInfo:nil]; + else if ( [condition isEqualToString:@"host-gone"] ) + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:HOST_GONE userInfo:nil]; + else if( [condition isEqualToString:@"item-not-found"] ) + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:ITEM_NOT_FOUND userInfo:nil]; + else if ( [condition isEqualToString:@"policy-violation"] ) + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:POLICY_VIOLATION userInfo:nil]; + else if( [condition isEqualToString:@"remote-connection-failed"] ) + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:REMOTE_CONNECTION_FAILED userInfo:nil]; + else if ( [condition isEqualToString:@"bad-request"] ) + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:BAD_REQUEST userInfo:nil]; + else if( [condition isEqualToString:@"internal-server-error"] ) + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:INTERNAL_SERVER_ERROR userInfo:nil]; + else if ( [condition isEqualToString:@"remote-stream-error"] ) + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:REMOTE_STREAM_ERROR userInfo:nil]; + else if ( [condition isEqualToString:@"undefined-condition"] ) + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:UNDEFINED_CONDITION userInfo:nil]; + else NSAssert( false, @"Terminate Condition Not Valid"); + } state = TERMINATED; } else if( !self.sid ) @@ -443,7 +463,7 @@ - (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request { NSLog(@"disconnectSessionResponseHandler"); - if(self.disconnectError) + if(self.disconnectError != nil) { [multicastDelegate transportWillDisconnect:self withError:self.disconnectError]; [disconnectError_ release]; From 613bc42482206bb8f619468954cb11a6ccb2f8a8 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Tue, 12 Apr 2011 17:10:45 +0530 Subject: [PATCH 055/180] Removing extraneous logging. Bug: disconnect called as soon as possible --- Core/Transports/BoshTransport.m | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 5d026a3..7cf89fa 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -261,6 +261,7 @@ - (void)disconnect NSLog(@"Bosh: Will Terminate Session"); state = DISCONNECTING; [multicastDelegate transportWillDisconnect:self]; + if ( [boshWindowManager canSendMoreRequests] ) [self trySendingStanzas]; } - (BOOL)sendStanza:(NSXMLElement *)stanza @@ -334,7 +335,7 @@ - (BOOL)createSession:(NSError **)error - (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces { NSXMLElement *requestPayload = [self newBodyElementWithPayload:bodyPayload attributes:attributes namespaces:namespaces]; - NSLog(@"request paylaod = %@", requestPayload); + //NSLog(@"request paylaod = %@", requestPayload); [boshWindowManager sentRequest:requestPayload]; [self sendHTTPRequestWithBody:requestPayload]; [requestPayload release]; @@ -356,17 +357,14 @@ - (void)trySendingStanzas [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas attributes:nil namespaces:nil]; [pendingXMPPStanzas removeAllObjects]; } - else if(state == DISCONNECTING) - [self sendTerminateRequest]; + else if(state == DISCONNECTING) [self sendTerminateRequest]; } } - (void)sendRequestsToHold { while( [boshWindowManager canLetServerHoldRequests:[self.hold unsignedIntValue]] && state == CONNECTED) - { [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:nil namespaces:nil]; - } } /* @@ -382,7 +380,7 @@ - (void)broadcastStanzas:(NSXMLNode *)node if([node level] == level + 1) { - NSLog(@"BOSH: Passing to delegates the stanza = %@", node); + //NSLog(@"BOSH: Passing to delegates the stanza = %@", node); [multicastDelegate transport:self didReceiveStanza:[(NSXMLElement *)node copy]]; } } @@ -467,6 +465,7 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request { [multicastDelegate transportWillDisconnect:self withError:self.disconnectError]; [disconnectError_ release]; + self.disconnectError = nil; } for ( ASIHTTPRequest *pendingRequest in pendingHttpRequests ) if(pendingRequest != request) [pendingRequest clearDelegatesAndCancel]; @@ -498,14 +497,15 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request - (void)requestFinished:(ASIHTTPRequest *)request { NSData *responseData = [request responseData]; - NSLog(@"BOSH: RECD[%@", [self logRequestResponse:responseData]); + NSString *postBodyString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; + NSXMLElement *postBody = [[NSXMLElement alloc] initWithXMLString:postBodyString error:nil]; + long long rid = [self getRidInRequest:postBody]; + + NSLog(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); [pendingHttpRequests removeObject:request]; NSXMLElement *parsedResponse = [self parseXMLData:responseData]; [self handleAttributesInResponse:parsedResponse]; - - NSString *postBodyString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; - NSXMLElement *postBody = [[NSXMLElement alloc] initWithXMLString:postBodyString error:nil]; [boshWindowManager recievedResponse:parsedResponse forRid:[self getRidInRequest:postBody]]; if(state == TERMINATED) [self disconnectSessionResponseHandler:request]; From d67b968fefaecebb1b75fcc708c544a57ee67819 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Tue, 12 Apr 2011 19:53:52 +0530 Subject: [PATCH 056/180] Adding retrying logic to BOSH --- Core/Transports/BoshTransport.h | 3 +++ Core/Transports/BoshTransport.m | 37 ++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 4c9e92f..01e2e1f 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -95,6 +95,8 @@ typedef enum { BoshTransportState state; NSMutableSet *pendingHttpRequests; + NSMutableDictionary *retryCountForPendingRequests; + MulticastDelegate *multicastDelegate; NSError *disconnectError_; } @@ -146,6 +148,7 @@ typedef enum { - (SEL)setterForProperty:(NSString *)property; - (NSNumber *)numberFromString:(NSString *)stringNumber; - (void)sendHTTPRequestWithBody:(NSXMLElement *)body; +- (void)sendHTTPRequestWithBody:(NSXMLElement *)body retriedTimes:(NSInteger)times; - (void)broadcastStanzas:(NSXMLNode *)node; - (void)trySendingStanzas; - (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 7cf89fa..fbf8237 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -125,6 +125,8 @@ - (void) dealloc } @end +const int RETRY_COUNT_LIMIT = 3; + @interface BoshTransport() @property(readwrite, assign) NSError *disconnectError; - (void)setInactivity:(NSString *)givenInactivity; @@ -209,10 +211,11 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< myJID_ = nil; state = DISCONNECTED; disconnectError_ = nil; - + /* Keeping a random capacity right now */ pendingXMPPStanzas = [[NSMutableArray alloc] initWithCapacity:25]; pendingHttpRequests = [[NSMutableSet alloc] initWithCapacity:3]; + retryCountForPendingRequests = [[NSMutableDictionary alloc] initWithCapacity:3]; boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:(nextRidToSend - 1)]; [boshWindowManager setWindowSize:1]; @@ -472,6 +475,7 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request [pendingHttpRequests removeAllObjects]; [pendingXMPPStanzas removeAllObjects]; + [retryCountForPendingRequests removeAllObjects]; state = DISCONNECTED; @@ -502,7 +506,10 @@ - (void)requestFinished:(ASIHTTPRequest *)request long long rid = [self getRidInRequest:postBody]; NSLog(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); + [pendingHttpRequests removeObject:request]; + [retryCountForPendingRequests removeObjectForKey:request]; + NSXMLElement *parsedResponse = [self parseXMLData:responseData]; [self handleAttributesInResponse:parsedResponse]; @@ -518,18 +525,31 @@ - (void)requestFinished:(ASIHTTPRequest *)request [postBodyString release]; } +- (void)resendRequest:(NSInteger)count { + +} + /* Not sending terminate request to the server - just disconnecting */ - (void)requestFailed:(ASIHTTPRequest *)request { if( ![self isConnected] ) return ; NSError *error = [request error]; + int tried = [[retryCountForPendingRequests objectForKey:request] intValue]; + [pendingHttpRequests removeObject:request]; + [retryCountForPendingRequests removeObjectForKey:request]; NSLog(@"BOSH: Request Failed[%@", [self logRequestResponse:[request postBody]]); NSLog(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); - if( [error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType ) { + + BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && ( tried < RETRY_COUNT_LIMIT ); + + if( shouldReconnect ) + { NSLog(@"Resending the request"); - [self sendHTTPRequestWithBody:[self parseXMLData:[request postBody]]]; + [self sendHTTPRequestWithBody:[ self parseXMLData:[request postBody]] retriedTimes:tried]; + //[self performSelector:@selector(resendRequest:) withObject:[request postBody] afterDelay:(1<<[[retryCountForPendingRequests objectForKey:request] intValue])]; + //NSTimer timerWithTimeInterval:self.retryAfterTime target:self selector:@selector(resendRequest) userInfo:request repeats:NO; } else { @@ -539,16 +559,23 @@ - (void)requestFailed:(ASIHTTPRequest *)request } } -- (void)sendHTTPRequestWithBody:(NSXMLElement *)body +- (void)sendHTTPRequestWithBody:(DDXMLElement *)body +{ + [self sendHTTPRequestWithBody:body retriedTimes:0]; +} + +- (void)sendHTTPRequestWithBody:(NSXMLElement *)body retriedTimes:(NSInteger)times { NSURL *url = [NSURL URLWithString:self.url]; ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; [request setRequestMethod:@"POST"]; [request setDelegate:self]; - [request setTimeOutSeconds:[self.wait doubleValue]+4]; + [request setTimeOutSeconds:2]; if(body) [request appendPostData:[[body compactXMLString] dataUsingEncoding:NSUTF8StringEncoding]]; [pendingHttpRequests addObject:request]; + [retryCountForPendingRequests setObject:[NSNumber numberWithInt:times] forKey:request]; + [request startAsynchronous]; NSLog(@"BOSH: SEND[%@", [self logRequestResponse:[request postBody]]); return; From 12b5a20940c7220af6ee853ba55b7bda327619ba Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Wed, 13 Apr 2011 17:22:23 +0530 Subject: [PATCH 057/180] Changing the reconnect logic in BOSH --- Core/Transports/BoshTransport.h | 4 ++-- Core/Transports/BoshTransport.m | 37 +++++++++++++-------------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 01e2e1f..cf6695c 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -95,10 +95,11 @@ typedef enum { BoshTransportState state; NSMutableSet *pendingHttpRequests; - NSMutableDictionary *retryCountForPendingRequests; MulticastDelegate *multicastDelegate; NSError *disconnectError_; + + int retryCounter; } @property(retain) XMPPJID *myJID; @@ -148,7 +149,6 @@ typedef enum { - (SEL)setterForProperty:(NSString *)property; - (NSNumber *)numberFromString:(NSString *)stringNumber; - (void)sendHTTPRequestWithBody:(NSXMLElement *)body; -- (void)sendHTTPRequestWithBody:(NSXMLElement *)body retriedTimes:(NSInteger)times; - (void)broadcastStanzas:(NSXMLNode *)node; - (void)trySendingStanzas; - (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index fbf8237..fc48a66 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -125,7 +125,8 @@ - (void) dealloc } @end -const int RETRY_COUNT_LIMIT = 3; +static const int RETRY_COUNT_LIMIT = 25; +static const NSTimeInterval RETRY_DELAY = 1.0; @interface BoshTransport() @property(readwrite, assign) NSError *disconnectError; @@ -215,7 +216,7 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< /* Keeping a random capacity right now */ pendingXMPPStanzas = [[NSMutableArray alloc] initWithCapacity:25]; pendingHttpRequests = [[NSMutableSet alloc] initWithCapacity:3]; - retryCountForPendingRequests = [[NSMutableDictionary alloc] initWithCapacity:3]; + retryCounter = 0; boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:(nextRidToSend - 1)]; [boshWindowManager setWindowSize:1]; @@ -475,7 +476,6 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request [pendingHttpRequests removeAllObjects]; [pendingXMPPStanzas removeAllObjects]; - [retryCountForPendingRequests removeAllObjects]; state = DISCONNECTED; @@ -508,7 +508,7 @@ - (void)requestFinished:(ASIHTTPRequest *)request NSLog(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); [pendingHttpRequests removeObject:request]; - [retryCountForPendingRequests removeObjectForKey:request]; + retryCounter = 0; NSXMLElement *parsedResponse = [self parseXMLData:responseData]; @@ -525,30 +525,25 @@ - (void)requestFinished:(ASIHTTPRequest *)request [postBodyString release]; } -- (void)resendRequest:(NSInteger)count { - -} /* Not sending terminate request to the server - just disconnecting */ - (void)requestFailed:(ASIHTTPRequest *)request { if( ![self isConnected] ) return ; NSError *error = [request error]; - int tried = [[retryCountForPendingRequests objectForKey:request] intValue]; [pendingHttpRequests removeObject:request]; - [retryCountForPendingRequests removeObjectForKey:request]; NSLog(@"BOSH: Request Failed[%@", [self logRequestResponse:[request postBody]]); NSLog(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); - BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && ( tried < RETRY_COUNT_LIMIT ); - + BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && ( retryCounter < RETRY_COUNT_LIMIT ); + ++retryCounter; if( shouldReconnect ) { NSLog(@"Resending the request"); - [self sendHTTPRequestWithBody:[ self parseXMLData:[request postBody]] retriedTimes:tried]; - //[self performSelector:@selector(resendRequest:) withObject:[request postBody] afterDelay:(1<<[[retryCountForPendingRequests objectForKey:request] intValue])]; + [self performSelector:@selector(sendHTTPRequestWithBody:) withObject:[self parseXMLData:[request postBody]] afterDelay:RETRY_DELAY]; + //[self sendHTTPRequestWithBody:[ self parseXMLData:[request postBody]] retriedTimes:tried]; //NSTimer timerWithTimeInterval:self.retryAfterTime target:self selector:@selector(resendRequest) userInfo:request repeats:NO; } else @@ -559,22 +554,20 @@ - (void)requestFailed:(ASIHTTPRequest *)request } } -- (void)sendHTTPRequestWithBody:(DDXMLElement *)body -{ - [self sendHTTPRequestWithBody:body retriedTimes:0]; -} - -- (void)sendHTTPRequestWithBody:(NSXMLElement *)body retriedTimes:(NSInteger)times +- (void)sendHTTPRequestWithBody:(NSXMLElement *)body { NSURL *url = [NSURL URLWithString:self.url]; + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; [request setRequestMethod:@"POST"]; [request setDelegate:self]; - [request setTimeOutSeconds:2]; + [request setTimeOutSeconds:([self.wait doubleValue] + 4)]; + + if(body) { + [request appendPostData:[[body compactXMLString] dataUsingEncoding:NSUTF8StringEncoding]]; + } - if(body) [request appendPostData:[[body compactXMLString] dataUsingEncoding:NSUTF8StringEncoding]]; [pendingHttpRequests addObject:request]; - [retryCountForPendingRequests setObject:[NSNumber numberWithInt:times] forKey:request]; [request startAsynchronous]; NSLog(@"BOSH: SEND[%@", [self logRequestResponse:[request postBody]]); From cda8b06bb68d4f65ccd133e4a1ca4aef16e2f647 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Wed, 13 Apr 2011 22:21:38 +0530 Subject: [PATCH 058/180] Refactored BoshWindowManager and Removed TERMINATED state --- Core/Transports/BoshTransport.h | 44 +++--- Core/Transports/BoshTransport.m | 248 +++++++++++++++++--------------- 2 files changed, 156 insertions(+), 136 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index cf6695c..d4772cb 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -46,13 +46,8 @@ typedef enum { CONNECTING = 1, DISCONNECTING = 2, DISCONNECTED = 3, - TERMINATED = 4 } BoshTransportState; -@protocol BoshWindowProtocol -- (void)broadcastStanzas:(NSXMLNode *)node; -@end - @interface RequestResponsePair : NSObject @property(retain) NSXMLElement *request; @property(retain) NSXMLElement *response; @@ -60,27 +55,32 @@ typedef enum { - (void)dealloc; @end +#pragma mark - + +/** + * Handles the in-order processing of responses. + **/ @interface BoshWindowManager : NSObject { - NSMutableDictionary *window; - long long maxRidReceived; + long long maxRidReceived; // all rid value less than equal to maxRidReceived are processed. long long maxRidSent; - id delegate; + NSMutableSet *receivedRids; } -@property long long windowSize; -@property(readonly) long long outstandingRequests; +@property unsigned int windowSize; +@property (readonly) long long maxRidReceived; -- (void)sentRequest:(NSXMLElement *)request; -- (void)recievedResponse:(NSXMLElement *)response forRid:(long long)rid; -- (BOOL)canSendMoreRequests; -- (NSNumber *)maxRidReceived; -- (BOOL)canLetServerHoldRequests:(long long)hold; -- (NSXMLElement *)getRequestForRid:(long long)rid; -- (id)initWithDelegate:(id)del rid:(long long)rid; +- (id)initWithRid:(long long)rid; +- (void)sentRequestForRid:(long long)rid; +- (void)recievedResponseForRid:(long long)rid; +- (BOOL)isWindowFull; +- (BOOL)isWindowEmpty; - (void)dealloc; @end -@interface BoshTransport : NSObject { + +#pragma mark - + +@interface BoshTransport : NSObject { NSString *boshVersion; NSString *content; @@ -89,12 +89,13 @@ typedef enum { NSString *XMPP_NS; long long nextRidToSend; - + long long maxRidProcessed; + NSMutableArray *pendingXMPPStanzas; BoshWindowManager *boshWindowManager; BoshTransportState state; - NSMutableSet *pendingHttpRequests; + NSMutableDictionary *requestResponsePairs; MulticastDelegate *multicastDelegate; NSError *disconnectError_; @@ -143,7 +144,7 @@ typedef enum { - (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse; - (NSString *)logRequestResponse:(NSData *)data; - (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse; -- (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request; +- (void)handleDisconnection; - (long long)generateRid; - (NSArray *)convertToStrings:(NSArray *)array; - (SEL)setterForProperty:(NSString *)property; @@ -157,6 +158,5 @@ typedef enum { - (NSArray *)createXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNodeType)type; - (NSXMLElement *)parseXMLData:(NSData *)xml; - (NSXMLElement *)parseXMLString:(NSString *)xml; -- (void)sendRequestsToHold; - (BOOL) createSession:(NSError **)error; @end diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index fc48a66..cb762b4 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -10,6 +10,55 @@ #import "DDXML.h" #import "NSXMLElementAdditions.h" +@interface NSMutableSet(BoshTransport) +- (void)addLongLong:(long long)number; +- (void)removeLongLong:(long long)number; +- (BOOL)containsLongLong:(long long)number; +@end + +@interface NSMutableDictionary(BoshTransport) +- (void)setObject:(id)anObject forLongLongKey:(long long)number; +- (void)removeObjectForLongLongKey:(long long)number; +- (id)objectForLongLongKey:(long long)number; +@end + +@implementation NSMutableSet(BoshTransport) +- (void)addLongLong:(long long)number +{ + NSNumber *nsNumber = [NSNumber numberWithLongLong:number]; + [self addObject:nsNumber]; +} +- (void)removeLongLong:(long long)number +{ + NSNumber *nsNumber = [NSNumber numberWithLongLong:number]; + [self removeObject:nsNumber]; +} +- (BOOL)containsLongLong:(long long)number +{ + NSNumber *nsNumber = [NSNumber numberWithLongLong:number]; + return [self containsObject:nsNumber]; +} +@end + +@implementation NSMutableDictionary(BoshTransport) +- (void)setObject:(id)anObject forLongLongKey:(long long)number +{ + NSNumber *nsNumber = [NSNumber numberWithLongLong:number]; + [self setObject:anObject forKey:nsNumber]; +} + +- (void)removeObjectForLongLongKey:(long long)number +{ + NSNumber *nsNumber = [NSNumber numberWithLongLong:number]; + [self removeObjectForKey:nsNumber]; +} +- (id)objectForLongLongKey:(long long)number +{ + NSNumber *nsNumber = [NSNumber numberWithLongLong:number]; + return [self objectForKey:nsNumber]; +} +@end + #pragma - #pragma RequestResponsePair Class @implementation RequestResponsePair @@ -41,86 +90,51 @@ - (void)dealloc @implementation BoshWindowManager @synthesize windowSize; -@synthesize outstandingRequests; - -- (NSNumber *)maxRidReceived -{ - return [NSNumber numberWithLongLong:maxRidReceived]; -} - -- (long long)getRidInBody:(NSXMLElement *)body -{ - return [[[body attributeForName:@"rid"] stringValue] longLongValue]; -} - -- (NSString *)stringFromInt:(long long)num -{ - return [NSString stringWithFormat:@"%qi",num]; -} +@synthesize maxRidReceived; -- (id)initWithDelegate:(id)del rid:(long long)rid +- (id)initWithRid:(long long)rid { if((self = [super init])) { - window = [[NSMutableDictionary alloc] initWithCapacity:4]; windowSize = 0; maxRidSent = rid; maxRidReceived = rid; - delegate = del; + receivedRids = [[NSMutableSet alloc] initWithCapacity:2]; } return self; } -- (void)sentRequest:(NSXMLElement *)request +- (void)sentRequestForRid:(long long)rid { - NSAssert( [self canSendMoreRequests], @"Sending request when should not be: Exceeding request count" ); - long long requestRid = [self getRidInBody:request]; - NSAssert ( requestRid == maxRidSent + 1, @"Sending request with rid = %qi greater than expected rid = %qi", requestRid, maxRidSent + 1); + NSAssert( ![self isWindowFull], @"Sending request when should not be: Exceeding request count" ); + NSAssert ( rid == maxRidSent + 1, @"Sending request with rid = %qi greater than expected rid = %qi", rid, maxRidSent + 1); ++maxRidSent; - [window setValue:[[RequestResponsePair alloc] initWithRequest:request response:nil] forKey:[self stringFromInt:requestRid]]; } -- (void)processResponses +- (void)recievedResponseForRid:(long long)rid { - while (true) + NSAssert(rid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", rid, maxRidReceived); + NSAssert(rid <= maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); + [receivedRids addLongLong:rid]; + while ( [receivedRids containsLongLong:(maxRidReceived + 1)] ) { - RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:(maxRidReceived+1)]]; - if( requestResponsePair.response == nil ) break; - [[requestResponsePair.response retain] autorelease]; - [window removeObjectForKey:[ self stringFromInt:(maxRidReceived + 1) ]]; ++maxRidReceived; - [delegate broadcastStanzas:requestResponsePair.response]; } } -- (void)recievedResponse:(NSXMLElement *)response forRid:(long long)rid +- (BOOL)isWindowFull { - NSAssert(rid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", rid, maxRidReceived); - NSAssert(rid <= maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); - RequestResponsePair *requestResponsePair = [window valueForKey:[self stringFromInt:rid]]; - NSAssert( requestResponsePair != nil, @"Response rid not in queue"); - requestResponsePair.response = response; - [self processResponses]; + return (maxRidSent - maxRidReceived) == windowSize; } -- (BOOL)canSendMoreRequests +- (BOOL)isWindowEmpty { - return (maxRidSent - maxRidReceived) < windowSize; -} - -- (BOOL)canLetServerHoldRequests:(long long)hold -{ - return (maxRidSent - maxRidReceived) < hold; -} -- (NSXMLElement *)getRequestForRid:(long long)rid -{ - NSAssert( rid - maxRidReceived > 0 && [window count] > (rid - maxRidReceived), @"Error Access request for rid = %qi, where maxRidReceived = %qi and [requestQueue count] = %qi", rid, maxRidReceived, [window count]); - return [window valueForKey:[self stringFromInt:rid]]; + return (maxRidSent - maxRidReceived) < 1; } - (void) dealloc { - [window release]; + [receivedRids release]; [super dealloc]; } @end @@ -196,6 +210,7 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< hold_ = [[NSNumber alloc] initWithInt:1]; nextRidToSend = [self generateRid]; + maxRidProcessed = nextRidToSend - 1; multicastDelegate = [[MulticastDelegate alloc] init]; if( delegate != nil ) [multicastDelegate addDelegate:delegate]; @@ -215,10 +230,10 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< /* Keeping a random capacity right now */ pendingXMPPStanzas = [[NSMutableArray alloc] initWithCapacity:25]; - pendingHttpRequests = [[NSMutableSet alloc] initWithCapacity:3]; + requestResponsePairs = [[NSMutableDictionary alloc] initWithCapacity:3]; retryCounter = 0; - boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:(nextRidToSend - 1)]; + boshWindowManager = [[BoshWindowManager alloc] initWithRid:(nextRidToSend - 1)]; [boshWindowManager setWindowSize:1]; } return self; @@ -265,7 +280,7 @@ - (void)disconnect NSLog(@"Bosh: Will Terminate Session"); state = DISCONNECTING; [multicastDelegate transportWillDisconnect:self]; - if ( [boshWindowManager canSendMoreRequests] ) [self trySendingStanzas]; + [self trySendingStanzas]; } - (BOOL)sendStanza:(NSXMLElement *)stanza @@ -339,8 +354,8 @@ - (BOOL)createSession:(NSError **)error - (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces { NSXMLElement *requestPayload = [self newBodyElementWithPayload:bodyPayload attributes:attributes namespaces:namespaces]; - //NSLog(@"request paylaod = %@", requestPayload); - [boshWindowManager sentRequest:requestPayload]; + int rid = [self getRidInRequest:requestPayload]; + [boshWindowManager sentRequestForRid:rid]; [self sendHTTPRequestWithBody:requestPayload]; [requestPayload release]; } @@ -354,23 +369,24 @@ - (void)sendTerminateRequest - (void)trySendingStanzas { - if( [boshWindowManager canSendMoreRequests]) + if( state != DISCONNECTED && ![boshWindowManager isWindowFull] ) { - if([pendingXMPPStanzas count] > 0) - { - [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas attributes:nil namespaces:nil]; - [pendingXMPPStanzas removeAllObjects]; + if (state == CONNECTED) { + if ( [pendingXMPPStanzas count] > 0 ) + { + [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas attributes:nil namespaces:nil]; + [pendingXMPPStanzas removeAllObjects]; + } else if ( [boshWindowManager isWindowEmpty] ) { + [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:nil namespaces:nil]; + } + } + else if(state == DISCONNECTING) + { + [self sendTerminateRequest]; } - else if(state == DISCONNECTING) [self sendTerminateRequest]; } } -- (void)sendRequestsToHold -{ - while( [boshWindowManager canLetServerHoldRequests:[self.hold unsignedIntValue]] && state == CONNECTED) - [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:nil namespaces:nil]; -} - /* For each received stanza the client might send out packets. We should ideally put all the request in the queue and call @@ -384,7 +400,6 @@ - (void)broadcastStanzas:(NSXMLNode *)node if([node level] == level + 1) { - //NSLog(@"BOSH: Passing to delegates the stanza = %@", node); [multicastDelegate transport:self didReceiveStanza:[(NSXMLElement *)node copy]]; } } @@ -433,10 +448,12 @@ - (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:UNDEFINED_CONDITION userInfo:nil]; else NSAssert( false, @"Terminate Condition Not Valid"); } - state = TERMINATED; + state = DISCONNECTED; } else if( !self.sid ) + { [self createSessionResponseHandler:parsedResponse]; + } } - (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse @@ -451,7 +468,9 @@ - (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse SEL setter = [self setterForProperty:attrName]; if([self respondsToSelector:setter]) + { [self performSelector:setter withObject:attrValue]; + } } /* Not doing anything with namespaces right now - because chirkut doesn't send it */ @@ -462,7 +481,7 @@ - (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse [multicastDelegate transportDidStartNegotiation:self]; } -- (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request +- (void)handleDisconnection { NSLog(@"disconnectSessionResponseHandler"); if(self.disconnectError != nil) @@ -471,28 +490,33 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request [disconnectError_ release]; self.disconnectError = nil; } - for ( ASIHTTPRequest *pendingRequest in pendingHttpRequests ) - if(pendingRequest != request) [pendingRequest clearDelegatesAndCancel]; - - [pendingHttpRequests removeAllObjects]; [pendingXMPPStanzas removeAllObjects]; - state = DISCONNECTED; - - nextRidToSend = [self generateRid]; - self.hold = [NSNumber numberWithInt:1]; - self.wait = [NSNumber numberWithDouble:60.0]; - [inactivity release]; - inactivity = [NSNumber numberWithInt:0]; - self.sid = nil; - self.authid = nil; - [boshWindowManager release]; - boshWindowManager = [[BoshWindowManager alloc] initWithDelegate:self rid:(nextRidToSend - 1)]; - [boshWindowManager setWindowSize:1]; - [multicastDelegate transportDidDisconnect:self]; } +- (NSXMLElement *)newXMLElementFromData:(NSData *)data +{ + NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; + return [[NSXMLElement alloc] initWithXMLString:string error:nil]; +} + +- (void)processResponses +{ + while ( maxRidProcessed < [boshWindowManager maxRidReceived] ) + { + ++maxRidProcessed; + RequestResponsePair *pair = [requestResponsePairs objectForLongLongKey:maxRidProcessed]; + NSAssert( [pair response], @"Processing nil response" ); + [self handleAttributesInResponse:[pair response]]; + [self broadcastStanzas:[pair response]]; + [requestResponsePairs removeObjectForLongLongKey:maxRidProcessed]; + if ( state == DISCONNECTED ) + { + [self handleDisconnection]; + } + } +} /* Should call processRequestQueue after some timeOut @@ -501,28 +525,22 @@ - (void)disconnectSessionResponseHandler:(ASIHTTPRequest *)request - (void)requestFinished:(ASIHTTPRequest *)request { NSData *responseData = [request responseData]; - NSString *postBodyString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; - NSXMLElement *postBody = [[NSXMLElement alloc] initWithXMLString:postBodyString error:nil]; + NSXMLElement *postBody = [self newXMLElementFromData:[request postBody]]; long long rid = [self getRidInRequest:postBody]; NSLog(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); - [pendingHttpRequests removeObject:request]; retryCounter = 0; - NSXMLElement *parsedResponse = [self parseXMLData:responseData]; + RequestResponsePair *requestResponsePair = [requestResponsePairs objectForLongLongKey:rid]; + [requestResponsePair setResponse:parsedResponse]; - [self handleAttributesInResponse:parsedResponse]; - [boshWindowManager recievedResponse:parsedResponse forRid:[self getRidInRequest:postBody]]; - - if(state == TERMINATED) [self disconnectSessionResponseHandler:request]; - else { - [self trySendingStanzas]; - [self sendRequestsToHold]; - } - + [boshWindowManager recievedResponseForRid:rid]; + [self processResponses]; + + [self trySendingStanzas]; + [postBody release]; - [postBodyString release]; } @@ -531,43 +549,43 @@ - (void)requestFailed:(ASIHTTPRequest *)request { if( ![self isConnected] ) return ; NSError *error = [request error]; - - [pendingHttpRequests removeObject:request]; NSLog(@"BOSH: Request Failed[%@", [self logRequestResponse:[request postBody]]); NSLog(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); - BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && ( retryCounter < RETRY_COUNT_LIMIT ); + BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && + ( retryCounter < RETRY_COUNT_LIMIT ) && + (state == CONNECTED); ++retryCounter; if( shouldReconnect ) { NSLog(@"Resending the request"); [self performSelector:@selector(sendHTTPRequestWithBody:) withObject:[self parseXMLData:[request postBody]] afterDelay:RETRY_DELAY]; - //[self sendHTTPRequestWithBody:[ self parseXMLData:[request postBody]] retriedTimes:tried]; - //NSTimer timerWithTimeInterval:self.retryAfterTime target:self selector:@selector(resendRequest) userInfo:request repeats:NO; } else { NSLog(@"disconnecting due to request failure"); [multicastDelegate transportWillDisconnect:self withError:error]; - [self disconnectSessionResponseHandler:request]; + state = DISCONNECTED; + [self handleDisconnection]; } } - (void)sendHTTPRequestWithBody:(NSXMLElement *)body { NSURL *url = [NSURL URLWithString:self.url]; - ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; [request setRequestMethod:@"POST"]; [request setDelegate:self]; [request setTimeOutSeconds:([self.wait doubleValue] + 4)]; - if(body) { + if(body) + { [request appendPostData:[[body compactXMLString] dataUsingEncoding:NSUTF8StringEncoding]]; } - [pendingHttpRequests addObject:request]; + RequestResponsePair *requestResponsePair = [[RequestResponsePair alloc] initWithRequest:body response:nil]; + [requestResponsePairs setObject:requestResponsePair forLongLongKey:[self getRidInRequest:body]]; [request startAsynchronous]; NSLog(@"BOSH: SEND[%@", [self logRequestResponse:[request postBody]]); @@ -577,7 +595,9 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body #pragma mark - #pragma mark utilities -- (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces +- (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload + attributes:(NSMutableDictionary *)attributes + namespaces:(NSMutableDictionary *)namespaces { attributes = attributes?attributes:[NSMutableDictionary dictionaryWithCapacity:3]; namespaces = namespaces?namespaces:[NSMutableDictionary dictionaryWithCapacity:1]; @@ -585,8 +605,8 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMut /* Adding ack and sid attribute on every outgoing request after sid is created */ if( self.sid ) { [attributes setValue:self.sid forKey:@"sid"]; - NSNumber *ack = [boshWindowManager maxRidReceived]; - if( [ack longLongValue] != nextRidToSend - 1) [attributes setValue:[ack stringValue] forKey:@"ack"]; + long long ack = maxRidProcessed; + if( ack != nextRidToSend - 1) [attributes setValue:[NSString stringWithFormat:@"%qi", ack] forKey:@"ack"]; } else [attributes setValue:@"1" forKey:@"ack"]; From 7ea743e5d504bcded170324e2f4c8e04bdd42e0b Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Thu, 14 Apr 2011 15:29:04 +0530 Subject: [PATCH 059/180] Changing Bosh attributes from NSNumbers to scalar types --- Core/Transports/BoshTransport.h | 14 ++---- Core/Transports/BoshTransport.m | 88 +++++++++++++-------------------- 2 files changed, 38 insertions(+), 64 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index d4772cb..6acdef2 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -83,11 +83,6 @@ typedef enum { @interface BoshTransport : NSObject { NSString *boshVersion; - NSString *content; - NSString *STREAM_NS; - NSString *BODY_NS; - NSString *XMPP_NS; - long long nextRidToSend; long long maxRidProcessed; @@ -104,13 +99,13 @@ typedef enum { } @property(retain) XMPPJID *myJID; -@property(retain) NSNumber *wait; -@property(retain) NSNumber *hold; +@property(assign) unsigned int wait; +@property(assign) unsigned int hold; @property(copy) NSString *lang; @property(copy) NSString *domain; -@property(readonly) NSNumber *inactivity; +@property(readonly) unsigned int inactivity; @property(readonly) BOOL secure; -@property(readonly) NSNumber *requests; +@property(readonly) unsigned int requests; @property(copy) NSString *authid; @property(copy) NSString *sid; @property(copy) NSString *url; @@ -146,7 +141,6 @@ typedef enum { - (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse; - (void)handleDisconnection; - (long long)generateRid; -- (NSArray *)convertToStrings:(NSArray *)array; - (SEL)setterForProperty:(NSString *)property; - (NSNumber *)numberFromString:(NSString *)stringNumber; - (void)sendHTTPRequestWithBody:(NSXMLElement *)body; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index cb762b4..df53d29 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -142,6 +142,10 @@ - (void) dealloc static const int RETRY_COUNT_LIMIT = 25; static const NSTimeInterval RETRY_DELAY = 1.0; +static const NSString *CONTENT_TYPE = @"text/xml; charset=utf-8"; +static const NSString *BODY_NS = @"http://jabber.org/protocol/httpbind"; +static const NSString *XMPP_NS = @"urn:xmpp:xbosh"; + @interface BoshTransport() @property(readwrite, assign) NSError *disconnectError; - (void)setInactivity:(NSString *)givenInactivity; @@ -172,16 +176,14 @@ @implementation BoshTransport - (void)setInactivity:(NSString *)inactivityString { NSNumber *givenInactivity = [self numberFromString:inactivityString]; - [inactivity autorelease]; - inactivity = [givenInactivity retain]; + inactivity = [givenInactivity unsignedIntValue]; } - (void)setRequests:(NSString *)requestsString { NSNumber *maxRequests = [self numberFromString:requestsString]; - [boshWindowManager setWindowSize:[maxRequests longLongValue]]; - [requests autorelease]; - requests = [maxRequests retain]; + [boshWindowManager setWindowSize:[maxRequests unsignedIntValue]]; + requests = [maxRequests unsignedIntValue]; } - (void)setSecure:(NSString *)isSecure @@ -205,23 +207,18 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< { boshVersion = @"1.6"; lang_ = @"en"; - content = [NSString stringWithFormat:@"text/xml; charset=utf-8"]; - wait_ = [[NSNumber alloc] initWithDouble:60.0]; - hold_ = [[NSNumber alloc] initWithInt:1]; + wait_ = 60.0; + hold_ = 1; nextRidToSend = [self generateRid]; maxRidProcessed = nextRidToSend - 1; multicastDelegate = [[MulticastDelegate alloc] init]; if( delegate != nil ) [multicastDelegate addDelegate:delegate]; - - STREAM_NS = @"http://etherx.jabber.org/streams"; - BODY_NS = @"http://jabber.org/protocol/httpbind"; - XMPP_NS = @"urn:xmpp:xbosh"; sid_ = nil; - inactivity = [[NSNumber alloc] initWithInt:0]; - requests = [[NSNumber alloc] initWithInt:2]; + inactivity = 0.0; + requests = 2; url_ = [url copy]; domain_ = [domain copy]; myJID_ = nil; @@ -341,9 +338,17 @@ - (BOOL)canConnect - (BOOL)createSession:(NSError **)error { - NSArray *keys = [NSArray arrayWithObjects:@"content", @"hold", @"to", @"ver", @"wait", @"from", @"secure", @"inactivity", nil]; - NSArray *objects = [NSArray arrayWithObjects:content, self.hold, self.domain, boshVersion, self.wait, [self.myJID bare], @"false", @"10", nil]; - NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjects:[self convertToStrings:objects] forKeys:keys]; + NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithCapacity:8]; + + [attr setObject:CONTENT_TYPE forKey:@"content"]; + [attr setObject:[NSString stringWithFormat:@"%u", self.hold] forKey:@"hold"]; + [attr setObject:self.domain forKey:@"to"]; + [attr setObject:boshVersion forKey:@"ver"]; + [attr setObject:[NSString stringWithFormat:@"%u", self.wait] forKey:@"wait"]; + [attr setObject:[self.myJID bare] forKey:@"from"]; + [attr setObject:@"false" forKey:@"secure"]; + [attr setObject:[NSString stringWithFormat:@"%u", self.inactivity] forKey:@"inactivity"]; + NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:attr namespaces:ns]; @@ -408,17 +413,6 @@ - (void)broadcastStanzas:(NSXMLNode *)node #pragma mark - #pragma mark HTTP Request Response -/* - host-unknown - host-gone - item-not-found - policy-violation - remote-connection-failed - bad-request - internal-server-error - remote-stream-error - undefined-condition - */ - (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse { NSXMLNode *typeAttribute = [parsedResponse attributeForName:@"type"]; @@ -532,12 +526,17 @@ - (void)requestFinished:(ASIHTTPRequest *)request retryCounter = 0; NSXMLElement *parsedResponse = [self parseXMLData:responseData]; + if ( !parsedResponse ) + { + [self requestFailed:request]; + return; + } RequestResponsePair *requestResponsePair = [requestResponsePairs objectForLongLongKey:rid]; [requestResponsePair setResponse:parsedResponse]; [boshWindowManager recievedResponseForRid:rid]; [self processResponses]; - + [self trySendingStanzas]; [postBody release]; @@ -547,7 +546,7 @@ - (void)requestFinished:(ASIHTTPRequest *)request /* Not sending terminate request to the server - just disconnecting */ - (void)requestFailed:(ASIHTTPRequest *)request { - if( ![self isConnected] ) return ; + //if( ![self isConnected] ) return ; NSError *error = [request error]; NSLog(@"BOSH: Request Failed[%@", [self logRequestResponse:[request postBody]]); @@ -577,7 +576,7 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; [request setRequestMethod:@"POST"]; [request setDelegate:self]; - [request setTimeOutSeconds:([self.wait doubleValue] + 4)]; + [request setTimeOutSeconds:(self.wait + 4)]; if(body) { @@ -635,23 +634,16 @@ - (long long)getRidInRequest:(NSXMLElement *)body return body && [body attributeForName:@"rid"]?[[[body attributeForName:@"rid"] stringValue] longLongValue]:-1; } -- (NSXMLElement *)returnRootElement:(NSXMLDocument *)doc -{ - NSXMLElement *rootElement = [doc rootElement]; - //[doc autorelease]; - return rootElement; -} - - (NSXMLElement *)parseXMLString:(NSString *)xml { - NSXMLDocument *doc = [[NSXMLDocument alloc] initWithXMLString:xml options:0 error:nil]; - return [self returnRootElement:doc]; + NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:xml options:0 error:nil] autorelease]; + return [doc rootElement]; } - (NSXMLElement *)parseXMLData:(NSData *)xml { - NSXMLDocument *doc = [[NSXMLDocument alloc] initWithData:xml options:0 error:nil]; - return [self returnRootElement:doc]; + NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithData:xml options:0 error:nil] autorelease]; + return [doc rootElement]; } - (NSArray *)createXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNodeType)type @@ -678,14 +670,6 @@ - (long long)generateRid return (arc4random() % 1000000000LL + 1000000001LL); } -- (NSArray *)convertToStrings:(NSArray *)array -{ - NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:0]; - for(id element in array) - [mutableArray addObject:[NSString stringWithFormat:@"%@", element]]; - return [mutableArray autorelease]; -} - - (SEL)setterForProperty:(NSString *)property { NSString *setter = @"set"; @@ -713,11 +697,7 @@ - (NSString *)logRequestResponse:(NSData *)data - (void)dealloc { - [wait_ release]; - [hold_ release]; [multicastDelegate release]; - [inactivity release]; - [requests release]; [url_ release]; [domain_ release]; [myJID_ release]; From ed16ed485c3fde668d7d5d424f70e3b2b414f327 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Thu, 14 Apr 2011 16:58:55 +0530 Subject: [PATCH 060/180] BOSH now accepts NSURL instead of NSString, rid is incremented by makeAndSendRequest instead newXMLBody --- Core/Transports/BoshTransport.h | 26 +----- Core/Transports/BoshTransport.m | 145 +++++++++++++++++++++++--------- 2 files changed, 106 insertions(+), 65 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 6acdef2..a3093ee 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -108,12 +108,12 @@ typedef enum { @property(readonly) unsigned int requests; @property(copy) NSString *authid; @property(copy) NSString *sid; -@property(copy) NSString *url; +@property(copy) NSURL *url; @property(readonly) NSError *disconnectError; /* init Methods */ -- (id)initWithUrl:(NSString *)url forDomain:(NSString *)host; -- (id)initWithUrl:(NSString *)url forDomain:(NSString *)host withDelegate:(id)delegate; +- (id)initWithUrl:(NSURL *)url forDomain:(NSString *)host; +- (id)initWithUrl:(NSURL *)url forDomain:(NSString *)host withDelegate:(id)delegate; - (void)dealloc; @@ -133,24 +133,4 @@ typedef enum { - (float)serverXmppStreamVersionNumber; - (BOOL)sendStanza:(NSXMLElement *)stanza; - (BOOL)sendStanzaWithString:(NSString *)string; - -/* Methods used internally */ -- (BOOL)canConnect; -- (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse; -- (NSString *)logRequestResponse:(NSData *)data; -- (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse; -- (void)handleDisconnection; -- (long long)generateRid; -- (SEL)setterForProperty:(NSString *)property; -- (NSNumber *)numberFromString:(NSString *)stringNumber; -- (void)sendHTTPRequestWithBody:(NSXMLElement *)body; -- (void)broadcastStanzas:(NSXMLNode *)node; -- (void)trySendingStanzas; -- (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; -- (long long)getRidInRequest:(NSXMLElement *)body; -- (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; -- (NSArray *)createXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNodeType)type; -- (NSXMLElement *)parseXMLData:(NSData *)xml; -- (NSXMLElement *)parseXMLString:(NSString *)xml; -- (BOOL) createSession:(NSError **)error; @end diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index df53d29..6476ebf 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -151,6 +151,29 @@ @interface BoshTransport() - (void)setInactivity:(NSString *)givenInactivity; - (void)setSecure:(NSString *)isSecure; - (void)setRequests:(NSString *)maxRequests; +- (BOOL)canConnect; +- (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse; +- (NSString *)logRequestResponse:(NSData *)data; +- (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse; +- (void)handleDisconnection; +- (long long)generateRid; +- (SEL)setterForProperty:(NSString *)property; +- (NSNumber *)numberFromString:(NSString *)stringNumber; +- (void)sendHTTPRequestWithBody:(NSXMLElement *)body; +- (void)broadcastStanzas:(NSXMLNode *)node; +- (void)trySendingStanzas; +- (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload + attributes:(NSMutableDictionary *)attributes + namespaces:(NSMutableDictionary *)namespaces; +- (long long)getRidInRequest:(NSXMLElement *)body; +- (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload + attributes:(NSMutableDictionary *)attributes + namespaces:(NSMutableDictionary *)namespaces; +- (NSArray *)newXMLNodeArrayFromDictionary:(NSDictionary *)dict + ofType:(XMLNodeType)type; +- (NSXMLElement *)parseXMLData:(NSData *)xml; +- (NSXMLElement *)parseXMLString:(NSString *)xml; +- (BOOL) createSession:(NSError **)error; @end #pragma - @@ -195,12 +218,14 @@ - (void)setSecure:(NSString *)isSecure #pragma mark - #pragma mark init -- (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain +- (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain { return [self initWithUrl:url forDomain:(NSString *)domain withDelegate:nil]; } -- (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id)delegate +- (id)initWithUrl:(NSURL *)url + forDomain:(NSString *)domain + withDelegate:(id)delegate { self = [super init]; if(self) @@ -219,7 +244,8 @@ - (id)initWithUrl:(NSString *)url forDomain:(NSString *)domain withDelegate:(id< sid_ = nil; inactivity = 0.0; requests = 2; - url_ = [url copy]; + url_ = [url retain]; + domain_ = [domain copy]; myJID_ = nil; state = DISCONNECTED; @@ -356,20 +382,24 @@ - (BOOL)createSession:(NSError **)error return YES; } -- (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces +- (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload + attributes:(NSMutableDictionary *)attributes + namespaces:(NSMutableDictionary *)namespaces { - NSXMLElement *requestPayload = [self newBodyElementWithPayload:bodyPayload attributes:attributes namespaces:namespaces]; - int rid = [self getRidInRequest:requestPayload]; - [boshWindowManager sentRequestForRid:rid]; + NSXMLElement *requestPayload = [self newBodyElementWithPayload:bodyPayload + attributes:attributes + namespaces:namespaces]; [self sendHTTPRequestWithBody:requestPayload]; - [requestPayload release]; + [boshWindowManager sentRequestForRid:nextRidToSend]; + ++nextRidToSend; + + [requestPayload release]; } - (void)sendTerminateRequest { NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"terminate", @"type", nil]; - NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: BODY_NS, @"", nil]; - [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:attr namespaces:ns]; + [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:attr namespaces:nil]; } - (void)trySendingStanzas @@ -379,10 +409,12 @@ - (void)trySendingStanzas if (state == CONNECTED) { if ( [pendingXMPPStanzas count] > 0 ) { - [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas attributes:nil namespaces:nil]; + [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas + attributes:nil namespaces:nil]; [pendingXMPPStanzas removeAllObjects]; } else if ( [boshWindowManager isWindowEmpty] ) { - [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:nil namespaces:nil]; + [self makeBodyAndSendHTTPRequestWithPayload:nil + attributes:nil namespaces:nil]; } } else if(state == DISCONNECTING) @@ -405,7 +437,8 @@ - (void)broadcastStanzas:(NSXMLNode *)node if([node level] == level + 1) { - [multicastDelegate transport:self didReceiveStanza:[(NSXMLElement *)node copy]]; + [multicastDelegate transport:self + didReceiveStanza:[(NSXMLElement *)node copy]]; } } } @@ -423,23 +456,32 @@ - (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse { NSString *condition = [conditionNode stringValue]; if( [condition isEqualToString:@"host-unknown"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:HOST_UNKNOWN userInfo:nil]; + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + code:HOST_UNKNOWN userInfo:nil]; else if ( [condition isEqualToString:@"host-gone"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:HOST_GONE userInfo:nil]; + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + code:HOST_GONE userInfo:nil]; else if( [condition isEqualToString:@"item-not-found"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:ITEM_NOT_FOUND userInfo:nil]; + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + code:ITEM_NOT_FOUND userInfo:nil]; else if ( [condition isEqualToString:@"policy-violation"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:POLICY_VIOLATION userInfo:nil]; + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + code:POLICY_VIOLATION userInfo:nil]; else if( [condition isEqualToString:@"remote-connection-failed"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:REMOTE_CONNECTION_FAILED userInfo:nil]; + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + code:REMOTE_CONNECTION_FAILED userInfo:nil]; else if ( [condition isEqualToString:@"bad-request"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:BAD_REQUEST userInfo:nil]; + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + code:BAD_REQUEST userInfo:nil]; else if( [condition isEqualToString:@"internal-server-error"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:INTERNAL_SERVER_ERROR userInfo:nil]; + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + code:INTERNAL_SERVER_ERROR userInfo:nil]; else if ( [condition isEqualToString:@"remote-stream-error"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:REMOTE_STREAM_ERROR userInfo:nil]; + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + code:REMOTE_STREAM_ERROR userInfo:nil]; else if ( [condition isEqualToString:@"undefined-condition"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" code:UNDEFINED_CONDITION userInfo:nil]; + disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + code:UNDEFINED_CONDITION userInfo:nil]; else NSAssert( false, @"Terminate Condition Not Valid"); } state = DISCONNECTED; @@ -559,7 +601,9 @@ - (void)requestFailed:(ASIHTTPRequest *)request if( shouldReconnect ) { NSLog(@"Resending the request"); - [self performSelector:@selector(sendHTTPRequestWithBody:) withObject:[self parseXMLData:[request postBody]] afterDelay:RETRY_DELAY]; + [self performSelector:@selector(sendHTTPRequestWithBody:) + withObject:[self parseXMLData:[request postBody]] + afterDelay:RETRY_DELAY]; } else { @@ -572,8 +616,7 @@ - (void)requestFailed:(ASIHTTPRequest *)request - (void)sendHTTPRequestWithBody:(NSXMLElement *)body { - NSURL *url = [NSURL URLWithString:self.url]; - ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:self.url]; [request setRequestMethod:@"POST"]; [request setDelegate:self]; [request setTimeOutSeconds:(self.wait + 4)]; @@ -583,8 +626,8 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body [request appendPostData:[[body compactXMLString] dataUsingEncoding:NSUTF8StringEncoding]]; } - RequestResponsePair *requestResponsePair = [[RequestResponsePair alloc] initWithRequest:body response:nil]; - [requestResponsePairs setObject:requestResponsePair forLongLongKey:[self getRidInRequest:body]]; + RequestResponsePair *pair = [[RequestResponsePair alloc] initWithRequest:body response:nil]; + [requestResponsePairs setObject:pair forLongLongKey:[self getRidInRequest:body]]; [request startAsynchronous]; NSLog(@"BOSH: SEND[%@", [self logRequestResponse:[request postBody]]); @@ -602,29 +645,42 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload namespaces = namespaces?namespaces:[NSMutableDictionary dictionaryWithCapacity:1]; /* Adding ack and sid attribute on every outgoing request after sid is created */ - if( self.sid ) { + if( self.sid ) + { [attributes setValue:self.sid forKey:@"sid"]; long long ack = maxRidProcessed; - if( ack != nextRidToSend - 1) [attributes setValue:[NSString stringWithFormat:@"%qi", ack] forKey:@"ack"]; + if( ack != nextRidToSend - 1 ) + { + [attributes setValue:[NSString stringWithFormat:@"%qi", ack] forKey:@"ack"]; + } + } + else + { + [attributes setValue:@"1" forKey:@"ack"]; } - else [attributes setValue:@"1" forKey:@"ack"]; [attributes setValue:[NSString stringWithFormat:@"%d", nextRidToSend] forKey:@"rid"]; - - /* Adding the BODY_NS namespace on every outgoing request */ [namespaces setValue:BODY_NS forKey:@""]; NSXMLElement *body = [[NSXMLElement alloc] initWithName:@"body"]; - [body setNamespaces:[self createXMLNodeArrayFromDictionary:namespaces ofType:NAMESPACE_TYPE]]; - [body setAttributes:[self createXMLNodeArrayFromDictionary:attributes ofType:ATTR_TYPE]]; - + NSArray *namespaceArray = [self newXMLNodeArrayFromDictionary:namespaces + ofType:NAMESPACE_TYPE]; + NSArray *attributesArray = [self newXMLNodeArrayFromDictionary:attributes + ofType:ATTR_TYPE]; + [body setNamespaces:namespaceArray]; + [body setAttributes:attributesArray]; + [namespaceArray release]; + [attributesArray release]; + if(payload != nil) + { for(NSXMLElement *child in payload) + { [body addChild:[[child copy] autorelease]]; - - ++nextRidToSend; - + } + } + return body; } @@ -636,17 +692,22 @@ - (long long)getRidInRequest:(NSXMLElement *)body - (NSXMLElement *)parseXMLString:(NSString *)xml { - NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:xml options:0 error:nil] autorelease]; + NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:xml + options:0 + error:nil] autorelease]; return [doc rootElement]; } - (NSXMLElement *)parseXMLData:(NSData *)xml { - NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithData:xml options:0 error:nil] autorelease]; + NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithData:xml + options:0 + error:nil] autorelease]; return [doc rootElement]; } -- (NSArray *)createXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNodeType)type +- (NSArray *)newXMLNodeArrayFromDictionary:(NSDictionary *)dict + ofType:(XMLNodeType)type { NSMutableArray *array = [[NSMutableArray alloc] init]; for (NSString *key in dict) { @@ -662,7 +723,7 @@ - (NSArray *)createXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNo [array addObject:node]; } - return [array autorelease]; + return array; } - (long long)generateRid From 1bc1f02e345cd9907ed6eb4d4a0a229615f3f41b Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 15 Apr 2011 14:17:07 +0530 Subject: [PATCH 061/180] Fix NSAsserts with multiple printf args. --- Core/Transports/BoshTransport.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 6476ebf..ba5861d 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -106,15 +106,15 @@ - (id)initWithRid:(long long)rid - (void)sentRequestForRid:(long long)rid { - NSAssert( ![self isWindowFull], @"Sending request when should not be: Exceeding request count" ); - NSAssert ( rid == maxRidSent + 1, @"Sending request with rid = %qi greater than expected rid = %qi", rid, maxRidSent + 1); + NSAssert(![self isWindowFull], @"Sending request when should not be: Exceeding request count" ); + NSAssert2(rid == maxRidSent + 1, @"Sending request with rid = %qi greater than expected rid = %qi", rid, maxRidSent + 1); ++maxRidSent; } - (void)recievedResponseForRid:(long long)rid { - NSAssert(rid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", rid, maxRidReceived); - NSAssert(rid <= maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); + NSAssert2(rid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", rid, maxRidReceived); + NSAssert3(rid <= maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); [receivedRids addLongLong:rid]; while ( [receivedRids containsLongLong:(maxRidReceived + 1)] ) { From 08e3493005c6689d5d1648b038d11deb76136848 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Tue, 19 Apr 2011 14:01:28 +0530 Subject: [PATCH 062/180] Fix attributes sent in session creation request. --- Core/Transports/BoshTransport.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index ba5861d..17d769c 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -373,7 +373,8 @@ - (BOOL)createSession:(NSError **)error [attr setObject:[NSString stringWithFormat:@"%u", self.wait] forKey:@"wait"]; [attr setObject:[self.myJID bare] forKey:@"from"]; [attr setObject:@"false" forKey:@"secure"]; - [attr setObject:[NSString stringWithFormat:@"%u", self.inactivity] forKey:@"inactivity"]; + [attr setObject:@"en" forKey:@"xml:lang"]; + [attr setObject:@"1.0" forKey:@"xmpp:version"]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; From 6ea6b8ab10c72dd27c50b4b23c0cb56df3a02ff2 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 20 Apr 2011 13:49:50 +0530 Subject: [PATCH 063/180] Fix plain auth success handling. --- Core/XMPPStream.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index dd477a6..2576d14 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1096,6 +1096,7 @@ - (void)handleAuth1:(NSXMLElement *)response { // We are successfully authenticated (via sasl:plain) [self setIsAuthenticated:YES]; + state = STATE_NEGOTIATING; // Now we start our negotiation over again... [self restartStream]; From 4057562da79524886cad2fe2f7ddbaacfeac37cf Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Wed, 20 Apr 2011 14:12:23 +0530 Subject: [PATCH 064/180] rid now asssociated with userInfo attribute of http request --- Core/Transports/BoshTransport.h | 1 + Core/Transports/BoshTransport.m | 112 ++++++++++++++++---------------- 2 files changed, 56 insertions(+), 57 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index a3093ee..4bad46d 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -46,6 +46,7 @@ typedef enum { CONNECTING = 1, DISCONNECTING = 2, DISCONNECTED = 3, + TERMINATING = 4 } BoshTransportState; @interface RequestResponsePair : NSObject diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 17d769c..6da1b72 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -153,19 +153,18 @@ - (void)setSecure:(NSString *)isSecure; - (void)setRequests:(NSString *)maxRequests; - (BOOL)canConnect; - (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse; -- (NSString *)logRequestResponse:(NSData *)data; - (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse; - (void)handleDisconnection; - (long long)generateRid; +- (long long)getRidFromRequest:(ASIHTTPRequest *)request; - (SEL)setterForProperty:(NSString *)property; - (NSNumber *)numberFromString:(NSString *)stringNumber; -- (void)sendHTTPRequestWithBody:(NSXMLElement *)body; +- (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid; - (void)broadcastStanzas:(NSXMLNode *)node; - (void)trySendingStanzas; - (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; -- (long long)getRidInRequest:(NSXMLElement *)body; - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces; @@ -390,7 +389,7 @@ - (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload NSXMLElement *requestPayload = [self newBodyElementWithPayload:bodyPayload attributes:attributes namespaces:namespaces]; - [self sendHTTPRequestWithBody:requestPayload]; + [self sendHTTPRequestWithBody:requestPayload rid:nextRidToSend]; [boshWindowManager sentRequestForRid:nextRidToSend]; ++nextRidToSend; @@ -411,16 +410,19 @@ - (void)trySendingStanzas if ( [pendingXMPPStanzas count] > 0 ) { [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas - attributes:nil namespaces:nil]; + attributes:nil + namespaces:nil]; [pendingXMPPStanzas removeAllObjects]; } else if ( [boshWindowManager isWindowEmpty] ) { [self makeBodyAndSendHTTPRequestWithPayload:nil - attributes:nil namespaces:nil]; + attributes:nil + namespaces:nil]; } } else if(state == DISCONNECTING) { - [self sendTerminateRequest]; + [self sendTerminateRequest]; + state = TERMINATING; } } } @@ -555,6 +557,40 @@ - (void)processResponses } } +- (void)resendRequest:(ASIHTTPRequest *)request +{ + long long rid = [self getRidFromRequest:request]; + [self sendHTTPRequestWithBody:[self parseXMLData:[request postBody]] rid:rid]; +} + +- (void)requestFailed:(ASIHTTPRequest *)request +{ + NSError *error = [request error]; + long long rid = [self getRidFromRequest:request]; + NSString *requestString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; + NSLog(@"BOSH: Request Failed[%qi] = %@", rid, requestString); + NSLog(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); + + BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && + ( retryCounter < RETRY_COUNT_LIMIT ) && + (state == CONNECTED); + ++retryCounter; + if(shouldReconnect) + { + NSLog(@"Resending the request"); + [self performSelector:@selector(resendRequest:) + withObject:request + afterDelay:RETRY_DELAY]; + } + else + { + NSLog(@"disconnecting due to request failure"); + [multicastDelegate transportWillDisconnect:self withError:error]; + state = DISCONNECTED; + [self handleDisconnection]; + } +} + /* Should call processRequestQueue after some timeOut Handle terminate response sent in any request. @@ -563,7 +599,7 @@ - (void)requestFinished:(ASIHTTPRequest *)request { NSData *responseData = [request responseData]; NSXMLElement *postBody = [self newXMLElementFromData:[request postBody]]; - long long rid = [self getRidInRequest:postBody]; + long long rid = [self getRidFromRequest:request]; NSLog(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); @@ -581,63 +617,39 @@ - (void)requestFinished:(ASIHTTPRequest *)request [self processResponses]; [self trySendingStanzas]; - - [postBody release]; -} - - -/* Not sending terminate request to the server - just disconnecting */ -- (void)requestFailed:(ASIHTTPRequest *)request -{ - //if( ![self isConnected] ) return ; - NSError *error = [request error]; - - NSLog(@"BOSH: Request Failed[%@", [self logRequestResponse:[request postBody]]); - NSLog(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); - BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && - ( retryCounter < RETRY_COUNT_LIMIT ) && - (state == CONNECTED); - ++retryCounter; - if( shouldReconnect ) - { - NSLog(@"Resending the request"); - [self performSelector:@selector(sendHTTPRequestWithBody:) - withObject:[self parseXMLData:[request postBody]] - afterDelay:RETRY_DELAY]; - } - else - { - NSLog(@"disconnecting due to request failure"); - [multicastDelegate transportWillDisconnect:self withError:error]; - state = DISCONNECTED; - [self handleDisconnection]; - } + [postBody release]; } -- (void)sendHTTPRequestWithBody:(NSXMLElement *)body +- (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid { ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:self.url]; [request setRequestMethod:@"POST"]; [request setDelegate:self]; [request setTimeOutSeconds:(self.wait + 4)]; - + request.userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithLongLong:rid] + forKey:@"rid"]; if(body) { [request appendPostData:[[body compactXMLString] dataUsingEncoding:NSUTF8StringEncoding]]; } RequestResponsePair *pair = [[RequestResponsePair alloc] initWithRequest:body response:nil]; - [requestResponsePairs setObject:pair forLongLongKey:[self getRidInRequest:body]]; + [requestResponsePairs setObject:pair forLongLongKey:rid]; [request startAsynchronous]; - NSLog(@"BOSH: SEND[%@", [self logRequestResponse:[request postBody]]); + NSLog(@"BOSH: SEND[%qi] = %@", rid, body); return; } #pragma mark - #pragma mark utilities +- (long long)getRidFromRequest:(ASIHTTPRequest *)request +{ + return [[[request userInfo] objectForKey:@"rid"] longLongValue]; +} + - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces @@ -685,12 +697,6 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload return body; } -- (long long)getRidInRequest:(NSXMLElement *)body -{ - - return body && [body attributeForName:@"rid"]?[[[body attributeForName:@"rid"] stringValue] longLongValue]:-1; -} - - (NSXMLElement *)parseXMLString:(NSString *)xml { NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:xml @@ -749,14 +755,6 @@ - (NSNumber *)numberFromString:(NSString *)stringNumber return number; } -- (NSString *)logRequestResponse:(NSData *)data -{ - NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - NSXMLElement *ele = [self parseXMLData:data]; - long long rid = [self getRidInRequest:ele]; - return [NSString stringWithFormat:@"%qi] = %@", rid, [dataString autorelease]]; -} - - (void)dealloc { [multicastDelegate release]; From 3f588fd71425721a0c922ddb9e9f796c5bbad5fa Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Sat, 23 Apr 2011 17:41:56 +0530 Subject: [PATCH 065/180] Adding inactivity attribute in session creation --- Core/Transports/BoshTransport.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 6da1b72..1e1295a 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -374,6 +374,7 @@ - (BOOL)createSession:(NSError **)error [attr setObject:@"false" forKey:@"secure"]; [attr setObject:@"en" forKey:@"xml:lang"]; [attr setObject:@"1.0" forKey:@"xmpp:version"]; + [attr setObject:@"3600" forKey:@"inactivity"]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; @@ -437,7 +438,6 @@ - (void)broadcastStanzas:(NSXMLNode *)node NSUInteger level = [node level]; while( (node = [node nextNode]) ) { - if([node level] == level + 1) { [multicastDelegate transport:self From 4a4d659d56d76c4fbe84d290d72dfeb523892d02 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Wed, 4 May 2011 15:12:38 +0530 Subject: [PATCH 066/180] Adding Exp BackOff --- Core/Transports/BoshTransport.h | 11 ++++++----- Core/Transports/BoshTransport.m | 16 +++++++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 4bad46d..9f313af 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -82,21 +82,22 @@ typedef enum { #pragma mark - @interface BoshTransport : NSObject { - NSString *boshVersion; + NSString *boshVersion; long long nextRidToSend; - long long maxRidProcessed; + long long maxRidProcessed; - NSMutableArray *pendingXMPPStanzas; - BoshWindowManager *boshWindowManager; + NSMutableArray *pendingXMPPStanzas; + BoshWindowManager *boshWindowManager; BoshTransportState state; NSMutableDictionary *requestResponsePairs; - MulticastDelegate *multicastDelegate; + MulticastDelegate *multicastDelegate; NSError *disconnectError_; int retryCounter; + NSTimeInterval nextRequestDelay; } @property(retain) XMPPJID *myJID; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 1e1295a..a4f4d01 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -141,6 +141,9 @@ - (void) dealloc static const int RETRY_COUNT_LIMIT = 25; static const NSTimeInterval RETRY_DELAY = 1.0; +static const NSTimeInterval DELAY_UPPER_LIMIT = 128.0; +static const NSTimeInterval DELAY_EXPONENTIATING_FACTOR = 2.0; +static const NSTimeInterval INITIAL_RETRY_DELAY = 1.0; static const NSString *CONTENT_TYPE = @"text/xml; charset=utf-8"; static const NSString *BODY_NS = @"http://jabber.org/protocol/httpbind"; @@ -254,6 +257,7 @@ - (id)initWithUrl:(NSURL *)url pendingXMPPStanzas = [[NSMutableArray alloc] initWithCapacity:25]; requestResponsePairs = [[NSMutableDictionary alloc] initWithCapacity:3]; retryCounter = 0; + nextRequestDelay = INITIAL_RETRY_DELAY; boshWindowManager = [[BoshWindowManager alloc] initWithRid:(nextRidToSend - 1)]; [boshWindowManager setWindowSize:1]; @@ -571,16 +575,16 @@ - (void)requestFailed:(ASIHTTPRequest *)request NSLog(@"BOSH: Request Failed[%qi] = %@", rid, requestString); NSLog(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); - BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && - ( retryCounter < RETRY_COUNT_LIMIT ) && - (state == CONNECTED); - ++retryCounter; + BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && ( retryCounter < RETRY_COUNT_LIMIT ) && + (state == CONNECTED); if(shouldReconnect) { NSLog(@"Resending the request"); [self performSelector:@selector(resendRequest:) withObject:request - afterDelay:RETRY_DELAY]; + afterDelay:nextRequestDelay]; + ++retryCounter; + nextRequestDelay *= DELAY_EXPONENTIATING_FACTOR; } else { @@ -604,6 +608,8 @@ - (void)requestFinished:(ASIHTTPRequest *)request NSLog(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); retryCounter = 0; + nextRequestDelay = INITIAL_RETRY_DELAY; + NSXMLElement *parsedResponse = [self parseXMLData:responseData]; if ( !parsedResponse ) { From 660ab798df214e1490e5fc3f94be0566c85c742a Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Sun, 8 May 2011 14:22:27 +0530 Subject: [PATCH 067/180] Adding ua = iphone --- Core/Transports/BoshTransport.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index a4f4d01..344350f 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -379,6 +379,7 @@ - (BOOL)createSession:(NSError **)error [attr setObject:@"en" forKey:@"xml:lang"]; [attr setObject:@"1.0" forKey:@"xmpp:version"]; [attr setObject:@"3600" forKey:@"inactivity"]; + [attr setObject:@"iphone" forKey:@"ua"]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; From 3f0dd2ed2a39f8f0a678fb2ee7075fe0b58da6dc Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Mon, 9 May 2011 19:05:44 +0530 Subject: [PATCH 068/180] Commenting a breaking code fragmant. Could be something to do with updating xmpp library. --- Extensions/XEP-0153/XMPPvCardAvatarModule.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Extensions/XEP-0153/XMPPvCardAvatarModule.m b/Extensions/XEP-0153/XMPPvCardAvatarModule.m index df84514..25be3d3 100644 --- a/Extensions/XEP-0153/XMPPvCardAvatarModule.m +++ b/Extensions/XEP-0153/XMPPvCardAvatarModule.m @@ -151,12 +151,15 @@ - (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule * If the client subsequently obtains an avatar image (e.g., by updating or retrieving the vCard), * it SHOULD then publish a new stanza with character data in the element. */ + + /* TODO: This fragment is breaking the code, need to check why. Commenting for now. if ([jid isEqual:[[aXmppStream myJID] bareJID]]) { XMPPPresence *presence = aXmppStream.myPresence; if (presence) [aXmppStream sendElement:presence]; } + */ } From c51ce14ce74b70881c449aba358b27b3b93e0cbd Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Mon, 9 May 2011 19:06:19 +0530 Subject: [PATCH 069/180] Added support for EXTVAL in fetching avatar. Making asynchronous http call to the given URL. --- Extensions/XEP-0153/XMPPvCardAvatarModule.h | 9 +++++ Extensions/XEP-0153/XMPPvCardAvatarModule.m | 44 +++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/Extensions/XEP-0153/XMPPvCardAvatarModule.h b/Extensions/XEP-0153/XMPPvCardAvatarModule.h index 42ec2d3..b826e58 100644 --- a/Extensions/XEP-0153/XMPPvCardAvatarModule.h +++ b/Extensions/XEP-0153/XMPPvCardAvatarModule.h @@ -21,6 +21,7 @@ @protocol XMPPvCardAvatarStorage; +@protocol XMPPvCardAvatarDelegate; @interface XMPPvCardAvatarModule : XMPPModule { @@ -47,4 +48,12 @@ - (void)clearvCardTempForJID:(XMPPJID *)jid; +@end + +@protocol XMPPvCardAvatarDelegate + +- (void)xmppvCardAvatarModule:(XMPPvCardAvatarModule *)vCardTempModule + didReceivePhotoData:(NSData *)photo + forJID:(XMPPJID *)jid; + @end \ No newline at end of file diff --git a/Extensions/XEP-0153/XMPPvCardAvatarModule.m b/Extensions/XEP-0153/XMPPvCardAvatarModule.m index 25be3d3..68a2e28 100644 --- a/Extensions/XEP-0153/XMPPvCardAvatarModule.m +++ b/Extensions/XEP-0153/XMPPvCardAvatarModule.m @@ -28,6 +28,8 @@ #import "XMPPPresence.h" #import "XMPPvCardTempModule.h" +#import "ASIHTTPRequest.h" + NSString *const kXMPPvCardAvatarElement = @"x"; NSString *const kXMPPvCardAvatarNS = @"vcard-temp:x:update"; @@ -146,6 +148,36 @@ - (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule didReceivevCardTemp:(XMPPvCardTemp *)vCardTemp forJID:(XMPPJID *)jid xmppStream:(XMPPStream *)aXmppStream { + NSData *photo = vCardTemp.photo; + if (photo == nil) { + //Check for EXTVAL, an external URL. + NSXMLElement *photoElement = [vCardTemp elementForName:@"PHOTO"]; + NSXMLElement *extval = [photoElement elementForName:@"EXTVAL"]; + + if (extval) { + //Fetch from the URL. + NSURL *url = [NSURL URLWithString:[extval stringValue]]; + + //Set user agent, otherwise, its getting too-many-redirection error. + NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithCapacity:4]; + [headers setObject:@"Firefox" forKey:@"User-Agent"]; + + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + [request setDelegate:self]; + [request setRequestMethod:@"GET"]; + [request setRequestHeaders:headers]; + [request setNumberOfTimesToRetryOnTimeout:4]; + [request setUserInfo:[NSDictionary dictionaryWithObject:jid forKey:@"jid"]]; + [request setDidFinishSelector:@selector(fetchedPhoto:)]; + [request startAsynchronous]; + } + } + + if (photo != nil) { + [multicastDelegate xmppvCardAvatarModule:self + didReceivePhotoData:photo + forJID:jid]; + } /* * XEP-0153 4.1.3 * If the client subsequently obtains an avatar image (e.g., by updating or retrieving the vCard), @@ -163,6 +195,18 @@ - (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule } +#pragma mark - Get photo from external URL + +- (void)fetchedPhoto:(ASIHTTPRequest *)request { + NSData *photo = [request responseData]; + if ([photo length] == 0) { + return; + } + [multicastDelegate xmppvCardAvatarModule:self + didReceivePhotoData:[request responseData] + forJID:[request.userInfo objectForKey:@"jid"]]; +} + #pragma mark - #pragma mark Getter/setter methods From c671c2956ffba000b9b7751fdd34f0a02df0d737 Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Wed, 11 May 2011 16:01:02 +0530 Subject: [PATCH 070/180] Not repeating a call when avatar is fetched for two cases- http and xmpp. Also, myPresence element need to be retained before using it to send the latest presence. --- Extensions/XEP-0153/XMPPvCardAvatarModule.m | 47 ++++++++++++--------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/Extensions/XEP-0153/XMPPvCardAvatarModule.m b/Extensions/XEP-0153/XMPPvCardAvatarModule.m index 68a2e28..bb390c9 100644 --- a/Extensions/XEP-0153/XMPPvCardAvatarModule.m +++ b/Extensions/XEP-0153/XMPPvCardAvatarModule.m @@ -140,6 +140,31 @@ - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)prese } +#pragma mark - Private method for received my avatar + +- (void)didReceivePhoto:(NSData *)photo forJid:(XMPPJID *)jid { + [multicastDelegate xmppvCardAvatarModule:self + didReceivePhotoData:photo + forJID:jid]; + /* + * XEP-0153 4.1.3 + * If the client subsequently obtains an avatar image (e.g., by updating or retrieving the vCard), + * it SHOULD then publish a new stanza with character data in the element. + */ + + if ([jid isEqual:[self.xmppStream.myJID bareJID]]) { + //MyPresence is being released before it it set to the latest presence. + //In this case, the only presence element was being released and so, errors were encountered. + //Hence, presence should be retained for some time. + XMPPPresence *presence = [self.xmppStream.myPresence retain]; + if (presence) { + [self.xmppStream sendElement:presence]; + } + [presence release]; + } +} + + #pragma mark - #pragma mark XMPPvCardTempModuleDelegate @@ -174,24 +199,8 @@ - (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule } if (photo != nil) { - [multicastDelegate xmppvCardAvatarModule:self - didReceivePhotoData:photo - forJID:jid]; + [self didReceivePhoto:photo forJid:jid]; } - /* - * XEP-0153 4.1.3 - * If the client subsequently obtains an avatar image (e.g., by updating or retrieving the vCard), - * it SHOULD then publish a new stanza with character data in the element. - */ - - /* TODO: This fragment is breaking the code, need to check why. Commenting for now. - if ([jid isEqual:[[aXmppStream myJID] bareJID]]) - { - XMPPPresence *presence = aXmppStream.myPresence; - if (presence) - [aXmppStream sendElement:presence]; - } - */ } @@ -202,9 +211,7 @@ - (void)fetchedPhoto:(ASIHTTPRequest *)request { if ([photo length] == 0) { return; } - [multicastDelegate xmppvCardAvatarModule:self - didReceivePhotoData:[request responseData] - forJID:[request.userInfo objectForKey:@"jid"]]; + [self didReceivePhoto:photo forJid:[request.userInfo objectForKey:@"jid"]]; } #pragma mark - From 76dc8248be20b46f1b96ecc6235cf4fcf1bb3ca9 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Fri, 13 May 2011 14:45:10 +0530 Subject: [PATCH 071/180] Making inactivity configurable --- Core/Transports/BoshTransport.h | 2 +- Core/Transports/BoshTransport.m | 26 ++++++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 9f313af..f180c55 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -105,7 +105,7 @@ typedef enum { @property(assign) unsigned int hold; @property(copy) NSString *lang; @property(copy) NSString *domain; -@property(readonly) unsigned int inactivity; +@property(assign) unsigned int inactivity; @property(readonly) BOOL secure; @property(readonly) unsigned int requests; @property(copy) NSString *authid; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 344350f..6df069b 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -151,9 +151,10 @@ - (void) dealloc @interface BoshTransport() @property(readwrite, assign) NSError *disconnectError; -- (void)setInactivity:(NSString *)givenInactivity; -- (void)setSecure:(NSString *)isSecure; -- (void)setRequests:(NSString *)maxRequests; +- (void)setInactivityFromString:(NSString *)givenInactivity; +- (void)setSecureFromString:(NSString *)isSecure; +- (void)setRequestsFromString:(NSString *)maxRequests; +- (void)setSidFromString:(NSString *)sid; - (BOOL)canConnect; - (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse; - (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse; @@ -198,20 +199,25 @@ @implementation BoshTransport #pragma mark - #pragma mark Private Accessor Method Implementation -- (void)setInactivity:(NSString *)inactivityString +- (void)setSidFromString:(NSString *)sid { + self.sid = sid; +} + +- (void)setInactivityFromString:(NSString *)inactivityString { + NSLog(@"Setting inactiviey"); NSNumber *givenInactivity = [self numberFromString:inactivityString]; inactivity = [givenInactivity unsignedIntValue]; } -- (void)setRequests:(NSString *)requestsString +- (void)setRequestsFromString:(NSString *)requestsString { NSNumber *maxRequests = [self numberFromString:requestsString]; [boshWindowManager setWindowSize:[maxRequests unsignedIntValue]]; requests = [maxRequests unsignedIntValue]; } -- (void)setSecure:(NSString *)isSecure +- (void)setSecureFromString:(NSString *)isSecure { if ([isSecure isEqualToString:@"true"]) secure=YES; else secure = NO; @@ -244,7 +250,7 @@ - (id)initWithUrl:(NSURL *)url if( delegate != nil ) [multicastDelegate addDelegate:delegate]; sid_ = nil; - inactivity = 0.0; + inactivity = 48 * 3600; requests = 2; url_ = [url retain]; @@ -378,7 +384,7 @@ - (BOOL)createSession:(NSError **)error [attr setObject:@"false" forKey:@"secure"]; [attr setObject:@"en" forKey:@"xml:lang"]; [attr setObject:@"1.0" forKey:@"xmpp:version"]; - [attr setObject:@"3600" forKey:@"inactivity"]; + [attr setObject:[NSString stringWithFormat:@"%u", self.inactivity] forKey:@"inactivity"]; [attr setObject:@"iphone" forKey:@"ua"]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; @@ -520,7 +526,7 @@ - (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse /* Not doing anything with namespaces right now - because chirkut doesn't send it */ //NSArray *responseNamespaces = [rootElement namespaces]; - state = CONNECTED; + state = CONNECTED; [multicastDelegate transportDidConnect:self]; [multicastDelegate transportDidStartNegotiation:self]; } @@ -749,7 +755,7 @@ - (SEL)setterForProperty:(NSString *)property { NSString *setter = @"set"; setter = [setter stringByAppendingString:[property capitalizedString]]; - setter = [setter stringByAppendingString:@":"]; + setter = [setter stringByAppendingString:@"FromString:"]; return NSSelectorFromString(setter); } From 86e3094def4e910009dfb5938cc348ca847a8a3e Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Mon, 16 May 2011 16:28:52 +0530 Subject: [PATCH 072/180] Adding Read Receipts to XMPPMessage --- Core/XMPPMessage.h | 9 ++++++--- Core/XMPPMessage.m | 41 +++++++++++++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/Core/XMPPMessage.h b/Core/XMPPMessage.h index be91a64..52787e3 100644 --- a/Core/XMPPMessage.h +++ b/Core/XMPPMessage.h @@ -26,9 +26,12 @@ - (BOOL)isMessageWithBody; - (BOOL)hasReceiptRequest; -- (BOOL)hasReceiptResponse; -- (NSString *)extractReceiptResponseID; -- (XMPPMessage *)generateReceiptResponse; +- (BOOL)hasReceivedReceiptResponse; +- (BOOL)hasReadReceiptResponse; +- (NSString *)extractReceivedReceiptResponseID; +- (NSString *)extractReadReceiptResponseID; +- (XMPPMessage *)generateReadReceiptResponse; +- (XMPPMessage *)generateReceivedReceiptResponse; - (NSError *)errorMessage; diff --git a/Core/XMPPMessage.m b/Core/XMPPMessage.m index 95595f4..4b5cf69 100644 --- a/Core/XMPPMessage.m +++ b/Core/XMPPMessage.m @@ -95,21 +95,37 @@ - (BOOL)hasReceiptRequest return (receiptRequest != nil); } -- (BOOL)hasReceiptResponse +- (BOOL)hasReceivedReceiptResponse { - NSXMLElement *receiptResponse = [self elementForName:@"received" xmlns:@"urn:xmpp:receipts"]; + NSXMLElement *receivedReceiptResponse = [self elementForName:@"received" xmlns:@"urn:xmpp:receipts"]; - return (receiptResponse != nil); + return (receivedReceiptResponse != nil); } -- (NSString *)extractReceiptResponseID +- (BOOL)hasReadReceiptResponse { - NSXMLElement *receiptResponse = [self elementForName:@"received" xmlns:@"urn:xmpp:receipts"]; + NSXMLElement *readReceiptResponse = [self elementForName:@"read" xmlns:@"urn:xmpp:receipts"]; - return [receiptResponse attributeStringValueForName:@"id"]; + return (readReceiptResponse != nil); } -- (XMPPMessage *)generateReceiptResponse +- (NSString *)extractReceivedReceiptResponseID +{ + NSXMLElement *receivedReceiptResponse = [self elementForName:@"received" xmlns:@"urn:xmpp:receipts"]; + + return [receivedReceiptResponse attributeStringValueForName:@"id"]; +} + + +- (NSString *)extractReadReceiptResponseID +{ + NSXMLElement *readReceiptResponse = [self elementForName:@"read" xmlns:@"urn:xmpp:receipts"]; + + return [readReceiptResponse attributeStringValueForName:@"id"]; +} + + +- (XMPPMessage *)generateReceiptResponse:(NSString *)receiptType { // Example: // @@ -117,7 +133,7 @@ - (XMPPMessage *)generateReceiptResponse // // - NSXMLElement *received = [NSXMLElement elementWithName:@"received" xmlns:@"urn:xmpp:receipts"]; + NSXMLElement *received = [NSXMLElement elementWithName:receiptType xmlns:@"urn:xmpp:receipts"]; NSXMLElement *message = [NSXMLElement elementWithName:@"message"]; @@ -138,4 +154,13 @@ - (XMPPMessage *)generateReceiptResponse return [[self class] messageFromElement:message]; } +- (XMPPMessage *)generateReceivedReceiptResponse +{ + return [self generateReceiptResponse:@"received"]; +} + +- (XMPPMessage *)generateReadReceiptResponse +{ + return [self generateReceiptResponse:@"read"]; +} @end From 125ad2fcfa371f43deff95007281306c6c24227f Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 18 May 2011 01:32:35 +0530 Subject: [PATCH 073/180] Do not do initial roster update on a different thread. --- Roster/XMPPRoster.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roster/XMPPRoster.m b/Roster/XMPPRoster.m index 0e39df7..4dd13cb 100644 --- a/Roster/XMPPRoster.m +++ b/Roster/XMPPRoster.m @@ -380,7 +380,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { if (![self hasRoster]) { - [self performSelectorInBackground:@selector(updateRosterWithQuery:) withObject:query]; + [self updateRosterWithQuery:query]; } else { NSArray *items = [query elementsForName:@"item"]; for (NSXMLElement *item in items) From 74bc19344c944c687389ffb3c4b6201f365d7f71 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sat, 28 May 2011 08:46:09 +0530 Subject: [PATCH 074/180] Ensure that is not attempted before xmppStream is authenticated. --- Core/XMPPStream.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index af8963f..feb8df2 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -940,7 +940,7 @@ - (void)handleStreamFeatures // Don't forget about that NSXMLElement bug you reported to apple (xmlns is required or element won't be found) NSXMLElement *f_bind = [features elementForName:@"bind" xmlns:@"urn:ietf:params:xml:ns:xmpp-bind"]; - if (f_bind) + if ([self isAuthenticated] && f_bind) { // Binding is required for this connection state = STATE_BINDING; From 5e511bd5f503a7fe1b33ff3a031ec044c095ae27 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Sun, 29 May 2011 23:52:04 +0530 Subject: [PATCH 075/180] Indentation fixes --- Core/Transports/BoshTransport.h | 4 +- Core/Transports/BoshTransport.m | 75 ++++++++++++++++++--------------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index f180c55..90b02dd 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -62,8 +62,8 @@ typedef enum { * Handles the in-order processing of responses. **/ @interface BoshWindowManager : NSObject { - long long maxRidReceived; // all rid value less than equal to maxRidReceived are processed. - long long maxRidSent; + long long maxRidReceived; // all rid value less than equal to maxRidReceived are processed. + long long maxRidSent; NSMutableSet *receivedRids; } diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 6df069b..e5d8ff5 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -68,19 +68,19 @@ @implementation RequestResponsePair - (id) initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response { - if( (self = [super init]) ) + if( (self = [super init]) ) { - request_ = request; - response_ = response; - } - return self; + request_ = request; + response_ = response; + } + return self; } - (void)dealloc { - [request_ release]; - [response_ release]; - [super dealloc]; + [request_ release]; + [response_ release]; + [super dealloc]; } @end @@ -94,42 +94,42 @@ @implementation BoshWindowManager - (id)initWithRid:(long long)rid { - if((self = [super init])) - { - windowSize = 0; - maxRidSent = rid; - maxRidReceived = rid; + if((self = [super init])) + { + windowSize = 0; + maxRidSent = rid; + maxRidReceived = rid; receivedRids = [[NSMutableSet alloc] initWithCapacity:2]; - } - return self; + } + return self; } - (void)sentRequestForRid:(long long)rid { - NSAssert(![self isWindowFull], @"Sending request when should not be: Exceeding request count" ); - NSAssert2(rid == maxRidSent + 1, @"Sending request with rid = %qi greater than expected rid = %qi", rid, maxRidSent + 1); - ++maxRidSent; + NSAssert(![self isWindowFull], @"Sending request when should not be: Exceeding request count" ); + NSAssert2(rid == maxRidSent + 1, @"Sending request with rid = %qi greater than expected rid = %qi", rid, maxRidSent + 1); + ++maxRidSent; } - (void)recievedResponseForRid:(long long)rid { - NSAssert2(rid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", rid, maxRidReceived); - NSAssert3(rid <= maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); + NSAssert2(rid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", rid, maxRidReceived); + NSAssert3(rid <= maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); [receivedRids addLongLong:rid]; - while ( [receivedRids containsLongLong:(maxRidReceived + 1)] ) - { - ++maxRidReceived; - } + while ( [receivedRids containsLongLong:(maxRidReceived + 1)] ) + { + ++maxRidReceived; + } } - (BOOL)isWindowFull { - return (maxRidSent - maxRidReceived) == windowSize; + return (maxRidSent - maxRidReceived) == windowSize; } - (BOOL)isWindowEmpty { - return (maxRidSent - maxRidReceived) < 1; + return (maxRidSent - maxRidReceived) < 1; } - (void) dealloc @@ -199,13 +199,13 @@ @implementation BoshTransport #pragma mark - #pragma mark Private Accessor Method Implementation -- (void)setSidFromString:(NSString *)sid { - self.sid = sid; +- (void)setSidFromString:(NSString *)sid +{ + self.sid = sid; } - (void)setInactivityFromString:(NSString *)inactivityString { - NSLog(@"Setting inactiviey"); NSNumber *givenInactivity = [self numberFromString:inactivityString]; inactivity = [givenInactivity unsignedIntValue]; } @@ -398,7 +398,7 @@ - (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces { - NSXMLElement *requestPayload = [self newBodyElementWithPayload:bodyPayload + NSXMLElement *requestPayload = [self newBodyElementWithPayload:bodyPayload attributes:attributes namespaces:namespaces]; [self sendHTTPRequestWithBody:requestPayload rid:nextRidToSend]; @@ -671,7 +671,7 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload namespaces = namespaces?namespaces:[NSMutableDictionary dictionaryWithCapacity:1]; /* Adding ack and sid attribute on every outgoing request after sid is created */ - if( self.sid ) + if( self.sid ) { [attributes setValue:self.sid forKey:@"sid"]; long long ack = maxRidProcessed; @@ -696,7 +696,7 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload ofType:ATTR_TYPE]; [body setNamespaces:namespaceArray]; [body setAttributes:attributesArray]; - [namespaceArray release]; + [namespaceArray release]; [attributesArray release]; if(payload != nil) @@ -730,16 +730,23 @@ - (NSArray *)newXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNodeType)type { NSMutableArray *array = [[NSMutableArray alloc] init]; - for (NSString *key in dict) { + for (NSString *key in dict) + { NSString *value = [dict objectForKey:key]; NSXMLNode *node; if(type == ATTR_TYPE) + { node = [NSXMLNode attributeWithName:key stringValue:value]; + } else if(type == NAMESPACE_TYPE) + { node = [NSXMLNode namespaceWithName:key stringValue:value]; - else + } + else + { NSLog(@"BOSH: Wrong Type Passed to createArrayFrom Dictionary"); + } [array addObject:node]; } From 66b03becedc565d66d9f7c0f9cf6c306f11235b5 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Mon, 30 May 2011 00:15:26 +0530 Subject: [PATCH 076/180] Sending another bosh request before processing the current response --- Core/Transports/BoshTransport.m | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index e5d8ff5..87428a3 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -558,7 +558,6 @@ - (void)processResponses ++maxRidProcessed; RequestResponsePair *pair = [requestResponsePairs objectForLongLongKey:maxRidProcessed]; NSAssert( [pair response], @"Processing nil response" ); - [self handleAttributesInResponse:[pair response]]; [self broadcastStanzas:[pair response]]; [requestResponsePairs removeObjectForLongLongKey:maxRidProcessed]; if ( state == DISCONNECTED ) @@ -609,7 +608,6 @@ - (void)requestFailed:(ASIHTTPRequest *)request - (void)requestFinished:(ASIHTTPRequest *)request { NSData *responseData = [request responseData]; - NSXMLElement *postBody = [self newXMLElementFromData:[request postBody]]; long long rid = [self getRidFromRequest:request]; NSLog(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); @@ -627,11 +625,9 @@ - (void)requestFinished:(ASIHTTPRequest *)request [requestResponsePair setResponse:parsedResponse]; [boshWindowManager recievedResponseForRid:rid]; - [self processResponses]; - + [self handleAttributesInResponse:parsedResponse]; [self trySendingStanzas]; - - [postBody release]; + [self processResponses]; } - (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid From 8da526e030056e05452a23788065fd09962085ac Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Mon, 30 May 2011 00:44:43 +0530 Subject: [PATCH 077/180] Sending empty requests till we get the terminate response from the server --- Core/Transports/BoshTransport.m | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 87428a3..7f09954 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -425,7 +425,9 @@ - (void)trySendingStanzas attributes:nil namespaces:nil]; [pendingXMPPStanzas removeAllObjects]; - } else if ( [boshWindowManager isWindowEmpty] ) { + } + else if ([boshWindowManager isWindowEmpty]) + { [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:nil namespaces:nil]; @@ -436,6 +438,13 @@ - (void)trySendingStanzas [self sendTerminateRequest]; state = TERMINATING; } + else if ([boshWindowManager isWindowEmpty] && state == TERMINATING) + { + /* sending more empty requests till we get a terminate response */ + [self makeBodyAndSendHTTPRequestWithPayload:nil + attributes:nil + namespaces:nil]; + } } } From ee9fb28b3213ba20b0001d81309cb81185946413 Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Mon, 30 May 2011 17:56:56 +0530 Subject: [PATCH 078/180] Defining a repeating string in header. --- Core/Transports/BoshTransport.h | 2 ++ Core/Transports/BoshTransport.m | 18 +++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 9f313af..fcef7ed 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -29,6 +29,8 @@ typedef enum { undefined-condition */ +#define BoshTerminateConditionDomain @"BoshTerminateCondition" + typedef enum { HOST_UNKNOWN = 1, HOST_GONE = 2, diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 344350f..00b5599 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -464,31 +464,31 @@ - (void)handleAttributesInResponse:(NSXMLElement *)parsedResponse { NSString *condition = [conditionNode stringValue]; if( [condition isEqualToString:@"host-unknown"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + disconnectError_ = [[NSError alloc] initWithDomain:BoshTerminateConditionDomain code:HOST_UNKNOWN userInfo:nil]; else if ( [condition isEqualToString:@"host-gone"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + disconnectError_ = [[NSError alloc] initWithDomain:BoshTerminateConditionDomain code:HOST_GONE userInfo:nil]; else if( [condition isEqualToString:@"item-not-found"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + disconnectError_ = [[NSError alloc] initWithDomain:BoshTerminateConditionDomain code:ITEM_NOT_FOUND userInfo:nil]; else if ( [condition isEqualToString:@"policy-violation"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + disconnectError_ = [[NSError alloc] initWithDomain:BoshTerminateConditionDomain code:POLICY_VIOLATION userInfo:nil]; else if( [condition isEqualToString:@"remote-connection-failed"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + disconnectError_ = [[NSError alloc] initWithDomain:BoshTerminateConditionDomain code:REMOTE_CONNECTION_FAILED userInfo:nil]; else if ( [condition isEqualToString:@"bad-request"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + disconnectError_ = [[NSError alloc] initWithDomain:BoshTerminateConditionDomain code:BAD_REQUEST userInfo:nil]; else if( [condition isEqualToString:@"internal-server-error"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + disconnectError_ = [[NSError alloc] initWithDomain:BoshTerminateConditionDomain code:INTERNAL_SERVER_ERROR userInfo:nil]; else if ( [condition isEqualToString:@"remote-stream-error"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + disconnectError_ = [[NSError alloc] initWithDomain:BoshTerminateConditionDomain code:REMOTE_STREAM_ERROR userInfo:nil]; else if ( [condition isEqualToString:@"undefined-condition"] ) - disconnectError_ = [[NSError alloc] initWithDomain:@"BoshTerminateCondition" + disconnectError_ = [[NSError alloc] initWithDomain:BoshTerminateConditionDomain code:UNDEFINED_CONDITION userInfo:nil]; else NSAssert( false, @"Terminate Condition Not Valid"); } From 88892827886121404256092cee9813ab3f330645 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Tue, 31 May 2011 20:15:11 +0530 Subject: [PATCH 079/180] Revert "Sending another bosh request before processing the current response" This reverts commit 66b03becedc565d66d9f7c0f9cf6c306f11235b5. This was done to fix policy-violation errors which were reported by Openfire BOSH. --- Core/Transports/BoshTransport.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 934f83d..71b1d86 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -567,6 +567,7 @@ - (void)processResponses ++maxRidProcessed; RequestResponsePair *pair = [requestResponsePairs objectForLongLongKey:maxRidProcessed]; NSAssert( [pair response], @"Processing nil response" ); + [self handleAttributesInResponse:[pair response]]; [self broadcastStanzas:[pair response]]; [requestResponsePairs removeObjectForLongLongKey:maxRidProcessed]; if ( state == DISCONNECTED ) @@ -617,6 +618,7 @@ - (void)requestFailed:(ASIHTTPRequest *)request - (void)requestFinished:(ASIHTTPRequest *)request { NSData *responseData = [request responseData]; + NSXMLElement *postBody = [self newXMLElementFromData:[request postBody]]; long long rid = [self getRidFromRequest:request]; NSLog(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); @@ -634,9 +636,11 @@ - (void)requestFinished:(ASIHTTPRequest *)request [requestResponsePair setResponse:parsedResponse]; [boshWindowManager recievedResponseForRid:rid]; - [self handleAttributesInResponse:parsedResponse]; - [self trySendingStanzas]; [self processResponses]; + + [self trySendingStanzas]; + + [postBody release]; } - (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid From c7a7f731f199cb2a4a53a7b9a11e4ceb79802878 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Thu, 2 Jun 2011 20:13:02 +0530 Subject: [PATCH 080/180] Iterating over only the first level children of the root node --- Core/Transports/BoshTransport.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 71b1d86..b12b169 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -455,15 +455,15 @@ - (void)trySendingStanzas */ - (void)broadcastStanzas:(NSXMLNode *)node { - NSUInteger level = [node level]; - while( (node = [node nextNode]) ) + node = [node childAtIndex:0]; + if (!node) { - if([node level] == level + 1) - { - [multicastDelegate transport:self - didReceiveStanza:[(NSXMLElement *)node copy]]; - } + return; } + do + { + [multicastDelegate transport:self didReceiveStanza:[(NSXMLElement *)node copy]]; + }while (node = [node nextSibling]); } #pragma mark - From bf8f406783706c047120c7bf954489ab3b6a73d3 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 3 Jun 2011 22:30:56 +0530 Subject: [PATCH 081/180] Ensure that only one element first + NSXMLElement *xElement = [presence elementForName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS]; + if (xElement) { + [xElement detach]; + } + // create a new one now + xElement = [NSXMLElement elementWithName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS]; NSXMLElement *photoElement = nil; - NSXMLElement *xElement = [NSXMLElement elementWithName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS]; NSString *photoHash = [_moduleStorage photoHashForJID:[sender myJID]]; From ee5c3139827ef842a333aac02d1287470c1843a8 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 6 Jun 2011 17:00:26 +0530 Subject: [PATCH 082/180] Revert all changes made to vCard avatar module. Most changes were related to extval, which is not really needed right now. --- Extensions/XEP-0153/XMPPvCardAvatarModule.h | 9 --- Extensions/XEP-0153/XMPPvCardAvatarModule.m | 85 +++------------------ 2 files changed, 12 insertions(+), 82 deletions(-) diff --git a/Extensions/XEP-0153/XMPPvCardAvatarModule.h b/Extensions/XEP-0153/XMPPvCardAvatarModule.h index b826e58..42ec2d3 100644 --- a/Extensions/XEP-0153/XMPPvCardAvatarModule.h +++ b/Extensions/XEP-0153/XMPPvCardAvatarModule.h @@ -21,7 +21,6 @@ @protocol XMPPvCardAvatarStorage; -@protocol XMPPvCardAvatarDelegate; @interface XMPPvCardAvatarModule : XMPPModule { @@ -48,12 +47,4 @@ - (void)clearvCardTempForJID:(XMPPJID *)jid; -@end - -@protocol XMPPvCardAvatarDelegate - -- (void)xmppvCardAvatarModule:(XMPPvCardAvatarModule *)vCardTempModule - didReceivePhotoData:(NSData *)photo - forJID:(XMPPJID *)jid; - @end \ No newline at end of file diff --git a/Extensions/XEP-0153/XMPPvCardAvatarModule.m b/Extensions/XEP-0153/XMPPvCardAvatarModule.m index 2604697..df84514 100644 --- a/Extensions/XEP-0153/XMPPvCardAvatarModule.m +++ b/Extensions/XEP-0153/XMPPvCardAvatarModule.m @@ -28,8 +28,6 @@ #import "XMPPPresence.h" #import "XMPPvCardTempModule.h" -#import "ASIHTTPRequest.h" - NSString *const kXMPPvCardAvatarElement = @"x"; NSString *const kXMPPvCardAvatarNS = @"vcard-temp:x:update"; @@ -102,15 +100,8 @@ - (void)xmppStreamDidAuthenticate:(XMPPStream *)sender { - (void)xmppStream:(XMPPStream *)sender willSendPresence:(XMPPPresence *)presence { // add our photo info to the presence stanza - - // remove existing element first - NSXMLElement *xElement = [presence elementForName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS]; - if (xElement) { - [xElement detach]; - } - // create a new one now - xElement = [NSXMLElement elementWithName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS]; NSXMLElement *photoElement = nil; + NSXMLElement *xElement = [NSXMLElement elementWithName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS]; NSString *photoHash = [_moduleStorage photoHashForJID:[sender myJID]]; @@ -147,31 +138,6 @@ - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)prese } -#pragma mark - Private method for received my avatar - -- (void)didReceivePhoto:(NSData *)photo forJid:(XMPPJID *)jid { - [multicastDelegate xmppvCardAvatarModule:self - didReceivePhotoData:photo - forJID:jid]; - /* - * XEP-0153 4.1.3 - * If the client subsequently obtains an avatar image (e.g., by updating or retrieving the vCard), - * it SHOULD then publish a new stanza with character data in the element. - */ - - if ([jid isEqual:[self.xmppStream.myJID bareJID]]) { - //MyPresence is being released before it it set to the latest presence. - //In this case, the only presence element was being released and so, errors were encountered. - //Hence, presence should be retained for some time. - XMPPPresence *presence = [self.xmppStream.myPresence retain]; - if (presence) { - [self.xmppStream sendElement:presence]; - } - [presence release]; - } -} - - #pragma mark - #pragma mark XMPPvCardTempModuleDelegate @@ -180,47 +146,20 @@ - (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule didReceivevCardTemp:(XMPPvCardTemp *)vCardTemp forJID:(XMPPJID *)jid xmppStream:(XMPPStream *)aXmppStream { - NSData *photo = vCardTemp.photo; - if (photo == nil) { - //Check for EXTVAL, an external URL. - NSXMLElement *photoElement = [vCardTemp elementForName:@"PHOTO"]; - NSXMLElement *extval = [photoElement elementForName:@"EXTVAL"]; - - if (extval) { - //Fetch from the URL. - NSURL *url = [NSURL URLWithString:[extval stringValue]]; - - //Set user agent, otherwise, its getting too-many-redirection error. - NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithCapacity:4]; - [headers setObject:@"Firefox" forKey:@"User-Agent"]; - - ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; - [request setDelegate:self]; - [request setRequestMethod:@"GET"]; - [request setRequestHeaders:headers]; - [request setNumberOfTimesToRetryOnTimeout:4]; - [request setUserInfo:[NSDictionary dictionaryWithObject:jid forKey:@"jid"]]; - [request setDidFinishSelector:@selector(fetchedPhoto:)]; - [request startAsynchronous]; - } - } - - if (photo != nil) { - [self didReceivePhoto:photo forJid:jid]; - } + /* + * XEP-0153 4.1.3 + * If the client subsequently obtains an avatar image (e.g., by updating or retrieving the vCard), + * it SHOULD then publish a new stanza with character data in the element. + */ + if ([jid isEqual:[[aXmppStream myJID] bareJID]]) + { + XMPPPresence *presence = aXmppStream.myPresence; + if (presence) + [aXmppStream sendElement:presence]; + } } -#pragma mark - Get photo from external URL - -- (void)fetchedPhoto:(ASIHTTPRequest *)request { - NSData *photo = [request responseData]; - if ([photo length] == 0) { - return; - } - [self didReceivePhoto:photo forJid:[request.userInfo objectForKey:@"jid"]]; -} - #pragma mark - #pragma mark Getter/setter methods From b4af5f9d45818216bf2db1c4fbaa6200c8f31709 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 6 Jun 2011 18:36:05 +0530 Subject: [PATCH 083/180] Bugfix: Retain myPresence before sending it. --- Extensions/XEP-0153/XMPPvCardAvatarModule.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Extensions/XEP-0153/XMPPvCardAvatarModule.m b/Extensions/XEP-0153/XMPPvCardAvatarModule.m index df84514..01418f3 100644 --- a/Extensions/XEP-0153/XMPPvCardAvatarModule.m +++ b/Extensions/XEP-0153/XMPPvCardAvatarModule.m @@ -153,9 +153,11 @@ - (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule */ if ([jid isEqual:[[aXmppStream myJID] bareJID]]) { - XMPPPresence *presence = aXmppStream.myPresence; - if (presence) + XMPPPresence *presence = [aXmppStream.myPresence retain]; + if (presence) { [aXmppStream sendElement:presence]; + } + [presence release]; } } From c6f1b5aed14cd117048017f2e4044143f11001a8 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Tue, 7 Jun 2011 18:11:37 +0530 Subject: [PATCH 084/180] Bugfix: make sure that only one vcard-update element is included with presence. --- Extensions/XEP-0153/XMPPvCardAvatarModule.m | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Extensions/XEP-0153/XMPPvCardAvatarModule.m b/Extensions/XEP-0153/XMPPvCardAvatarModule.m index 01418f3..877b93f 100644 --- a/Extensions/XEP-0153/XMPPvCardAvatarModule.m +++ b/Extensions/XEP-0153/XMPPvCardAvatarModule.m @@ -98,11 +98,17 @@ - (void)xmppStreamDidAuthenticate:(XMPPStream *)sender { } -- (void)xmppStream:(XMPPStream *)sender willSendPresence:(XMPPPresence *)presence { +- (void)xmppStream:(XMPPStream *)sender willSendPresence:(XMPPPresence *)presence { // add our photo info to the presence stanza - NSXMLElement *photoElement = nil; - NSXMLElement *xElement = [NSXMLElement elementWithName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS]; + // remove current first + NSXMLElement *xElement = [presence elementForName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS]; + [xElement detach]; + + // create new xElement + xElement = [NSXMLElement elementWithName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS]; + NSXMLElement *photoElement = nil; + NSString *photoHash = [_moduleStorage photoHashForJID:[sender myJID]]; if (photoHash != nil) { From 94cbe1dd954c3318b054a48710d73d9306e389ac Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Tue, 7 Jun 2011 18:42:20 +0530 Subject: [PATCH 085/180] In AvatarModule, if releasing moduleStorage, better retain it. --- Extensions/XEP-0153/XMPPvCardAvatarModule.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extensions/XEP-0153/XMPPvCardAvatarModule.m b/Extensions/XEP-0153/XMPPvCardAvatarModule.m index 2604697..f26d8d9 100644 --- a/Extensions/XEP-0153/XMPPvCardAvatarModule.m +++ b/Extensions/XEP-0153/XMPPvCardAvatarModule.m @@ -46,7 +46,7 @@ @implementation XMPPvCardAvatarModule - (id)initWithvCardTempModule:(XMPPvCardTempModule *)xmppvCardTempModule { if ((self = [super initWithStream:xmppvCardTempModule.xmppStream])) { _xmppvCardTempModule = [xmppvCardTempModule retain]; - _moduleStorage = (id )xmppvCardTempModule.moduleStorage; + _moduleStorage = [(id )xmppvCardTempModule.moduleStorage retain]; [_xmppvCardTempModule addDelegate:self]; } From 750d19c4c170af99d781f03df3ad613d41ca2655 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Thu, 9 Jun 2011 17:00:51 +0530 Subject: [PATCH 086/180] Upon accepting a buddy request, do not automatically add the buddy to the roster. --- Roster/XMPPRoster.m | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Roster/XMPPRoster.m b/Roster/XMPPRoster.m index 4dd13cb..0520404 100644 --- a/Roster/XMPPRoster.m +++ b/Roster/XMPPRoster.m @@ -224,10 +224,6 @@ - (void)acceptBuddyRequest:(XMPPJID *)jid [response addAttributeWithName:@"type" stringValue:@"subscribed"]; [xmppStream sendElement:response]; - - // Add user to our roster - - [self addBuddy:jid withNickname:nil]; } - (void)rejectBuddyRequest:(XMPPJID *)jid From 88631dbb6c9e671f2cdf2a1324603f9a0c72aade Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Thu, 16 Jun 2011 20:37:01 +0530 Subject: [PATCH 087/180] Added transport delegate methods for willSend and didSend stanzas. --- Core/Transports/BoshTransport.m | 2 ++ Core/Transports/XMPPSocketTransport.m | 15 +++++++++++++-- Core/XMPPTransportProtocol.h | 2 ++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index b12b169..d998e35 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -322,8 +322,10 @@ - (BOOL)sendStanza:(NSXMLElement *)stanza NSLog(@"BOSH: Need to be connected to be able to send stanza"); return NO; } + [multicastDelegate transport:self willSendStanza:stanza]; [pendingXMPPStanzas addObject:stanza]; [self trySendingStanzas]; + [multicastDelegate transport:self didSendStanza:stanza]; return YES; } diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index edb936c..7891781 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -95,6 +95,14 @@ - (void)dealloc [super dealloc]; } +- (NSXMLElement *)parseXMLString:(NSString *)xml +{ + NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:xml + options:0 + error:nil] autorelease]; + return [doc rootElement]; +} + - (void)addDelegate:(id)delegate { [multicastDelegate addDelegate:delegate]; @@ -158,12 +166,15 @@ - (BOOL)sendString:(NSString *)string - (BOOL)sendStanzaWithString:(NSString *)string { - return [self sendString:string]; + return [self sendStanza:[self parseXMLString:string]]; } - (BOOL)sendStanza:(NSXMLElement *)stanza { - return [self sendStanzaWithString:[stanza compactXMLString]]; + [multicastDelegate transport:self willSendStanza:stanza]; + BOOL sent = [self sendString:[stanza compactXMLString]]; + [multicastDelegate transport:self didSendStanza:stanza]; + return sent; } /** diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 7dd9ceb..3ab769e 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -48,6 +48,8 @@ - (void)transportWillDisconnect:(id)transport withError:(NSError *)err; - (void)transportDidDisconnect:(id )transport; - (void)transport:(id )transport willSecureWithSettings:(NSDictionary *)settings; +- (void)transport:(id )transport willSendStanza:(NSXMLElement *)stanza; +- (void)transport:(id )transport didSendStanza:(NSXMLElement *)stanza; - (void)transport:(id )transport didReceiveStanza:(NSXMLElement *)stanza; - (void)transport:(id )transport didReceiveError:(id)error; - (void)transportDidSecure:(id )transport; From 3945438eba5f93e1a0056708ac665513a28af78e Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Fri, 17 Jun 2011 15:15:35 +0530 Subject: [PATCH 088/180] Added support for specifying host and port values in BoshTransport, thus an option of 'route' attribute. --- Core/Transports/BoshTransport.h | 17 +++++++++++-- Core/Transports/BoshTransport.m | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index b87c4a3..0fcd27e 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -107,6 +107,8 @@ typedef enum { @property(assign) unsigned int hold; @property(copy) NSString *lang; @property(copy) NSString *domain; +@property(copy) NSString *host; +@property(assign) unsigned int port; @property(assign) unsigned int inactivity; @property(readonly) BOOL secure; @property(readonly) unsigned int requests; @@ -116,8 +118,19 @@ typedef enum { @property(readonly) NSError *disconnectError; /* init Methods */ -- (id)initWithUrl:(NSURL *)url forDomain:(NSString *)host; -- (id)initWithUrl:(NSURL *)url forDomain:(NSString *)host withDelegate:(id)delegate; +- (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain; +- (id)initWithUrl:(NSURL *)url + forDomain:(NSString *)domain + host:(NSString *)host + port:(unsigned int)port; +- (id)initWithUrl:(NSURL *)url + forDomain:(NSString *)domain + withDelegate:(id)delegate; +- (id)initWithUrl:(NSURL *)url + forDomain:(NSString *)domain + host:(NSString *)host + port:(unsigned int)port + withDelegate:(id)delegate; - (void)dealloc; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index d998e35..c32c632 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -187,6 +187,8 @@ @implementation BoshTransport @synthesize hold = hold_; @synthesize lang = lang_; @synthesize domain = domain_; +@synthesize host = host_; +@synthesize port = port_; @synthesize myJID = myJID_; @synthesize sid = sid_; @synthesize url = url_; @@ -231,8 +233,33 @@ - (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain return [self initWithUrl:url forDomain:(NSString *)domain withDelegate:nil]; } +- (id)initWithUrl:(NSURL *)url + forDomain:(NSString *)domain + host:(NSString *)host + port:(unsigned int)port +{ + return [self initWithUrl:url + forDomain:domain + host:host + port:port + withDelegate:nil]; +} + +- (id)initWithUrl:(NSURL *)url + forDomain:(NSString *)domain + withDelegate:(id)delegate +{ + return [self initWithUrl:url + forDomain:domain + host:nil + port:0 + withDelegate:delegate]; +} + - (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain + host:(NSString *)host + port:(unsigned int)port withDelegate:(id)delegate { self = [super init]; @@ -255,6 +282,17 @@ - (id)initWithUrl:(NSURL *)url url_ = [url retain]; domain_ = [domain copy]; + host_ = nil; + port_ = 0; + + if (host != nil) { + host_ = [host copy]; + } + + if (port) { + port_ = port; + } + myJID_ = nil; state = DISCONNECTED; disconnectError_ = nil; @@ -388,6 +426,10 @@ - (BOOL)createSession:(NSError **)error [attr setObject:@"1.0" forKey:@"xmpp:version"]; [attr setObject:[NSString stringWithFormat:@"%u", self.inactivity] forKey:@"inactivity"]; [attr setObject:@"iphone" forKey:@"ua"]; + if (self.host != nil && self.port) { + NSString *route = [NSString stringWithFormat:@"xmpp:%@:%u", self.host, self.port]; + [attr setObject:route forKey:@"route"]; + } NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; @@ -791,6 +833,7 @@ - (void)dealloc [multicastDelegate release]; [url_ release]; [domain_ release]; + [host_ release]; [myJID_ release]; [authid release]; [sid_ release]; From 07e7a032014ea665424abd0fb1da5fe4de055fac Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Mon, 20 Jun 2011 15:43:54 +0530 Subject: [PATCH 089/180] Removing received rids from the window manager --- Core/Transports/BoshTransport.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index c32c632..383a178 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -119,6 +119,7 @@ - (void)recievedResponseForRid:(long long)rid while ( [receivedRids containsLongLong:(maxRidReceived + 1)] ) { ++maxRidReceived; + [receivedRids removeLongLong:maxRidReceived]; } } @@ -378,7 +379,7 @@ - (void)addDelegate:(id)delegate [multicastDelegate addDelegate:delegate]; } -- (void) removeDelegate:(id)delegate +- (void)removeDelegate:(id)delegate { [multicastDelegate removeDelegate:delegate]; } From 132598dae72b2f9df38246e78a4a0c5ebb36e7d1 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Mon, 20 Jun 2011 15:46:30 +0530 Subject: [PATCH 090/180] Fixing spelling mistake --- Core/Transports/BoshTransport.h | 2 +- Core/Transports/BoshTransport.m | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 0fcd27e..57605ef 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -74,7 +74,7 @@ typedef enum { - (id)initWithRid:(long long)rid; - (void)sentRequestForRid:(long long)rid; -- (void)recievedResponseForRid:(long long)rid; +- (void)receivedResponseForRid:(long long)rid; - (BOOL)isWindowFull; - (BOOL)isWindowEmpty; - (void)dealloc; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 383a178..6497be6 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -111,10 +111,10 @@ - (void)sentRequestForRid:(long long)rid ++maxRidSent; } -- (void)recievedResponseForRid:(long long)rid +- (void)receivedResponseForRid:(long long)rid { NSAssert2(rid > maxRidReceived, @"Recieving response for rid = %qi where maxRidReceived = %qi", rid, maxRidReceived); - NSAssert3(rid <= maxRidReceived + windowSize, @"Recieved response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); + NSAssert3(rid <= maxRidReceived + windowSize, @"Received response for a request outside the rid window. responseRid = %qi, maxRidReceived = %qi, windowSize = %qi", rid, maxRidReceived, windowSize); [receivedRids addLongLong:rid]; while ( [receivedRids containsLongLong:(maxRidReceived + 1)] ) { @@ -680,7 +680,7 @@ - (void)requestFinished:(ASIHTTPRequest *)request RequestResponsePair *requestResponsePair = [requestResponsePairs objectForLongLongKey:rid]; [requestResponsePair setResponse:parsedResponse]; - [boshWindowManager recievedResponseForRid:rid]; + [boshWindowManager receivedResponseForRid:rid]; [self processResponses]; [self trySendingStanzas]; From a120f5dc516937dd0b356024520db22cbe1e9340 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Mon, 20 Jun 2011 17:25:33 +0530 Subject: [PATCH 091/180] cancelling http request whose response is not yet returned during dealloc --- Core/Transports/BoshTransport.m | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 6497be6..2e776f1 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -152,6 +152,8 @@ - (void) dealloc @interface BoshTransport() @property(readwrite, assign) NSError *disconnectError; +@property(retain) NSMutableSet *pendingHTTPRequests; + - (void)setInactivityFromString:(NSString *)givenInactivity; - (void)setSecureFromString:(NSString *)isSecure; - (void)setRequestsFromString:(NSString *)maxRequests; @@ -198,6 +200,7 @@ @implementation BoshTransport @synthesize authid; @synthesize requests; @synthesize disconnectError = disconnectError_; +@synthesize pendingHTTPRequests = pendingHTTPRequests_; #pragma mark - #pragma mark Private Accessor Method Implementation @@ -304,6 +307,8 @@ - (id)initWithUrl:(NSURL *)url retryCounter = 0; nextRequestDelay = INITIAL_RETRY_DELAY; + pendingHTTPRequests_ = [[NSMutableSet alloc] initWithCapacity:2]; + boshWindowManager = [[BoshWindowManager alloc] initWithRid:(nextRidToSend - 1)]; [boshWindowManager setWindowSize:1]; } @@ -508,7 +513,7 @@ - (void)broadcastStanzas:(NSXMLNode *)node do { [multicastDelegate transport:self didReceiveStanza:[(NSXMLElement *)node copy]]; - }while (node = [node nextSibling]); + }while ((node = [node nextSibling])); } #pragma mark - @@ -636,6 +641,8 @@ - (void)requestFailed:(ASIHTTPRequest *)request NSLog(@"BOSH: Request Failed[%qi] = %@", rid, requestString); NSLog(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); + [pendingHTTPRequests_ removeObject:request]; + BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && ( retryCounter < RETRY_COUNT_LIMIT ) && (state == CONNECTED); if(shouldReconnect) @@ -680,6 +687,8 @@ - (void)requestFinished:(ASIHTTPRequest *)request RequestResponsePair *requestResponsePair = [requestResponsePairs objectForLongLongKey:rid]; [requestResponsePair setResponse:parsedResponse]; + [pendingHTTPRequests_ removeObject:request]; + [boshWindowManager receivedResponseForRid:rid]; [self processResponses]; @@ -704,6 +713,8 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid RequestResponsePair *pair = [[RequestResponsePair alloc] initWithRequest:body response:nil]; [requestResponsePairs setObject:pair forLongLongKey:rid]; + [pendingHTTPRequests_ addObject:request]; + [request startAsynchronous]; NSLog(@"BOSH: SEND[%qi] = %@", rid, body); return; @@ -831,6 +842,14 @@ - (NSNumber *)numberFromString:(NSString *)stringNumber - (void)dealloc { + for (ASIHTTPRequest *request in pendingHTTPRequests_) + { + NSLog(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); + [request clearDelegatesAndCancel]; + } + [pendingHTTPRequests_ removeAllObjects]; + [pendingHTTPRequests_ release]; + [multicastDelegate release]; [url_ release]; [domain_ release]; From 5ae3327a6e6895d12e72bdd0ee7ca91ac08e02b1 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Thu, 23 Jun 2011 15:47:05 +0530 Subject: [PATCH 092/180] BOSH: Use DDLogSend and DDLogRecvPre to log sent and received HTTP body elements. --- Core/Transports/BoshTransport.m | 7 +++---- Core/Transports/XMPPSocketTransport.h | 9 --------- Core/XMPPTransportProtocol.h | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 2e776f1..b4d7c30 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -669,11 +669,10 @@ - (void)requestFailed:(ASIHTTPRequest *)request */ - (void)requestFinished:(ASIHTTPRequest *)request { + long long rid = [self getRidFromRequest:request]; + DDLogRecvPre(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); NSData *responseData = [request responseData]; NSXMLElement *postBody = [self newXMLElementFromData:[request postBody]]; - long long rid = [self getRidFromRequest:request]; - - NSLog(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); retryCounter = 0; nextRequestDelay = INITIAL_RETRY_DELAY; @@ -716,7 +715,7 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid [pendingHTTPRequests_ addObject:request]; [request startAsynchronous]; - NSLog(@"BOSH: SEND[%qi] = %@", rid, body); + DDLogSend(@"BOSH: SEND[%qi] = %@", rid, body); return; } diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index bd785f6..2f42864 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -19,15 +19,6 @@ @class XMPPJID; @class RFSRVResolver; -// Define the debugging state -#define DEBUG_SEND YES -#define DEBUG_RECV_PRE NO // Prints data before going to xmpp parser -#define DEBUG_RECV_POST YES // Prints data as it comes out of xmpp parser - -#define DDLogSend(format, ...) do{ if(DEBUG_SEND) NSLog((format), ##__VA_ARGS__); }while(0) -#define DDLogRecvPre(format, ...) do{ if(DEBUG_RECV_PRE) NSLog((format), ##__VA_ARGS__); }while(0) -#define DDLogRecvPost(format, ...) do{ if(DEBUG_RECV_POST) NSLog((format), ##__VA_ARGS__); }while(0) - // Define the various timeouts (in seconds) for retreiving various parts of the XML stream #define TIMEOUT_WRITE 10 #define TIMEOUT_READ_START 10 diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 3ab769e..14d03e8 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -11,6 +11,24 @@ #import "DDXML.h" #endif +// Define the debugging state + +#ifndef DEBUG_SEND + #define DEBUG_SEND NO +#endif + +#ifndef DEBUG_RECV_PRE + #define DEBUG_RECV_PRE NO // Prints data before going to xmpp parser +#endif + +#ifndef DEBUG_RECV_POST + #define DEBUG_RECV_POST NO // Prints data as it comes out of xmpp parser +#endif + +#define DDLogSend(format, ...) do{ if(DEBUG_SEND) NSLog((format), ##__VA_ARGS__); }while(0) +#define DDLogRecvPre(format, ...) do{ if(DEBUG_RECV_PRE) NSLog((format), ##__VA_ARGS__); }while(0) +#define DDLogRecvPost(format, ...) do{ if(DEBUG_RECV_POST) NSLog((format), ##__VA_ARGS__); }while(0) + @class XMPPJID; @protocol XMPPTransportProtocol From 3717148d907edd30d6a659038d942df05db83dc7 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Thu, 23 Jun 2011 15:59:19 +0530 Subject: [PATCH 093/180] BOSH: Replace all NSLog calls with DDLog*. --- Core/Transports/BoshTransport.m | 36 ++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index b4d7c30..113b858 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -9,6 +9,7 @@ #import "BoshTransport.h" #import "DDXML.h" #import "NSXMLElementAdditions.h" +#import "DDLog.h" @interface NSMutableSet(BoshTransport) - (void)addLongLong:(long long)number; @@ -325,7 +326,7 @@ - (float)serverXmppStreamVersionNumber - (BOOL)connect:(NSError **)error { - NSLog(@"BOSH: Connecting to %@ with jid = %@", self.domain, [self.myJID bare]); + DDLogInfo(@"BOSH: Connecting to %@ with jid = %@", self.domain, [self.myJID bare]); if(![self canConnect]) return NO; state = CONNECTING; @@ -337,10 +338,10 @@ - (void)restartStream { if(![self isConnected]) { - NSLog(@"BOSH: Need to be connected to restart the stream."); + DDLogError(@"BOSH: Need to be connected to restart the stream."); return ; } - NSLog(@"Bosh: Will Restart Stream"); + DDLogVerbose(@"Bosh: Will Restart Stream"); NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"true", @"xmpp:restart", nil]; NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys:XMPP_NS, @"xmpp", nil]; [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:attr namespaces:ns]; @@ -350,10 +351,10 @@ - (void)disconnect { if(![self isConnected]) { - NSLog(@"BOSH: Need to be connected to disconnect"); + DDLogError(@"BOSH: Need to be connected to disconnect"); return; } - NSLog(@"Bosh: Will Terminate Session"); + DDLogInfo(@"Bosh: Will Terminate Session"); state = DISCONNECTING; [multicastDelegate transportWillDisconnect:self]; [self trySendingStanzas]; @@ -363,7 +364,7 @@ - (BOOL)sendStanza:(NSXMLElement *)stanza { if (![self isConnected]) { - NSLog(@"BOSH: Need to be connected to be able to send stanza"); + DDLogError(@"BOSH: Need to be connected to be able to send stanza"); return NO; } [multicastDelegate transport:self willSendStanza:stanza]; @@ -401,17 +402,17 @@ - (BOOL)canConnect { if( state != DISCONNECTED ) { - NSLog(@"@BOSH: Either disconnecting or still connected to the server. Disconnect First."); + DDLogVerbose(@"@BOSH: Either disconnecting or still connected to the server. Disconnect First."); return NO; } if(!self.domain) { - NSLog(@"BOSH: Called Connect with specifying the domain"); + DDLogVerbose(@"BOSH: Called Connect with specifying the domain"); return NO; } if(!self.myJID) { - NSLog(@"BOSH: Called connect without setting the jid"); + DDLogVerbose(@"BOSH: Called connect without setting the jid"); return NO; } return YES; @@ -592,7 +593,6 @@ - (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse - (void)handleDisconnection { - NSLog(@"disconnectSessionResponseHandler"); if(self.disconnectError != nil) { [multicastDelegate transportWillDisconnect:self withError:self.disconnectError]; @@ -637,9 +637,13 @@ - (void)requestFailed:(ASIHTTPRequest *)request { NSError *error = [request error]; long long rid = [self getRidFromRequest:request]; + +#if DEBUG_WARN NSString *requestString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; - NSLog(@"BOSH: Request Failed[%qi] = %@", rid, requestString); - NSLog(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); + DDLogWarn(@"BOSH: Request Failed[%qi] = %@", rid, requestString); + DDLogWarn(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); + [requestString release]; +#endif [pendingHTTPRequests_ removeObject:request]; @@ -647,7 +651,7 @@ - (void)requestFailed:(ASIHTTPRequest *)request (state == CONNECTED); if(shouldReconnect) { - NSLog(@"Resending the request"); + DDLogInfo(@"Resending the request"); [self performSelector:@selector(resendRequest:) withObject:request afterDelay:nextRequestDelay]; @@ -656,7 +660,7 @@ - (void)requestFailed:(ASIHTTPRequest *)request } else { - NSLog(@"disconnecting due to request failure"); + DDLogWarn(@"disconnecting due to request failure"); [multicastDelegate transportWillDisconnect:self withError:error]; state = DISCONNECTED; [self handleDisconnection]; @@ -809,7 +813,7 @@ - (NSArray *)newXMLNodeArrayFromDictionary:(NSDictionary *)dict } else { - NSLog(@"BOSH: Wrong Type Passed to createArrayFrom Dictionary"); + DDLogError(@"BOSH: Wrong Type Passed to createArrayFrom Dictionary"); } [array addObject:node]; @@ -843,7 +847,7 @@ - (void)dealloc { for (ASIHTTPRequest *request in pendingHTTPRequests_) { - NSLog(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); + DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); [request clearDelegatesAndCancel]; } [pendingHTTPRequests_ removeAllObjects]; From e9f3a1cd35859646161ac57655f7598cab179f1d Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 24 Jun 2011 00:33:28 +0530 Subject: [PATCH 094/180] BOSH: Use 'nonatomic' properties for RequestResponsePair. --- Core/Transports/BoshTransport.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 57605ef..b557a4f 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -52,8 +52,8 @@ typedef enum { } BoshTransportState; @interface RequestResponsePair : NSObject -@property(retain) NSXMLElement *request; -@property(retain) NSXMLElement *response; +@property (nonatomic, retain) NSXMLElement *request; +@property (nonatomic, retain) NSXMLElement *response; - (id)initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response; - (void)dealloc; @end From c3af735d3901aba58a347c6db0a33a674610fd9a Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 24 Jun 2011 00:34:10 +0530 Subject: [PATCH 095/180] BOSH: Retain 'request' and 'response' in RequestResponsePair. --- Core/Transports/BoshTransport.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 113b858..f9b15e8 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -71,8 +71,8 @@ - (id) initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response { if( (self = [super init]) ) { - request_ = request; - response_ = response; + request_ = [request retain]; + response_ = [response retain]; } return self; } From 3dcd13218094f19ef3db41fa666652c7f89e2e5b Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 24 Jun 2011 00:35:03 +0530 Subject: [PATCH 096/180] BOSH: Remove unused variable -- it was also causing a memory leak. --- Core/Transports/BoshTransport.m | 3 --- 1 file changed, 3 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index f9b15e8..6cc8b43 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -676,7 +676,6 @@ - (void)requestFinished:(ASIHTTPRequest *)request long long rid = [self getRidFromRequest:request]; DDLogRecvPre(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); NSData *responseData = [request responseData]; - NSXMLElement *postBody = [self newXMLElementFromData:[request postBody]]; retryCounter = 0; nextRequestDelay = INITIAL_RETRY_DELAY; @@ -696,8 +695,6 @@ - (void)requestFinished:(ASIHTTPRequest *)request [self processResponses]; [self trySendingStanzas]; - - [postBody release]; } - (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid From c200bf7afdbc4851e07900b1fcffa6a42f82274e Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 24 Jun 2011 00:35:57 +0530 Subject: [PATCH 097/180] BOSH: Fix memory leak -- RequestResponsePair instance was not being released after creation. --- Core/Transports/BoshTransport.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 6cc8b43..381d48a 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -712,6 +712,7 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid RequestResponsePair *pair = [[RequestResponsePair alloc] initWithRequest:body response:nil]; [requestResponsePairs setObject:pair forLongLongKey:rid]; + [pair release]; [pendingHTTPRequests_ addObject:request]; From c97fd66bf784fd287dd48eb6d21baa1f8af6ab36 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 24 Jun 2011 00:36:29 +0530 Subject: [PATCH 098/180] BOSH: Don't copy NSXMLElement unnecessarily while creating payload to send. The real problem was in -[BoshTransport parseXMLString:]. This method would create an NSXMLElement which had an NSXMLDocument as its parent node. --- Core/Transports/BoshTransport.m | 16 ++++++++++------ Core/Transports/XMPPSocketTransport.m | 6 ++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 381d48a..8139982 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -769,7 +769,7 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload { for(NSXMLElement *child in payload) { - [body addChild:[[child copy] autorelease]]; + [body addChild:child]; } } @@ -779,17 +779,21 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload - (NSXMLElement *)parseXMLString:(NSString *)xml { NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:xml - options:0 - error:nil] autorelease]; - return [doc rootElement]; + options:0 + error:NULL] autorelease]; + NSXMLElement *element = [doc rootElement]; + [element detach]; + return element; } - (NSXMLElement *)parseXMLData:(NSData *)xml { NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithData:xml options:0 - error:nil] autorelease]; - return [doc rootElement]; + error:NULL] autorelease]; + NSXMLElement *element = [doc rootElement]; + [element detach]; + return element; } - (NSArray *)newXMLNodeArrayFromDictionary:(NSDictionary *)dict diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index 7891781..4409985 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -99,8 +99,10 @@ - (NSXMLElement *)parseXMLString:(NSString *)xml { NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:xml options:0 - error:nil] autorelease]; - return [doc rootElement]; + error:NULL] autorelease]; + NSXMLElement *element = [doc rootElement]; + [element detach]; + return element; } - (void)addDelegate:(id)delegate From 365267cb73dea18ea3528a8854f3186347626538 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 24 Jun 2011 00:37:19 +0530 Subject: [PATCH 099/180] BOSH: Add an exception instead of logging a logic error. --- Core/Transports/BoshTransport.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 8139982..aa8c383 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -815,7 +815,10 @@ - (NSArray *)newXMLNodeArrayFromDictionary:(NSDictionary *)dict } else { - DDLogError(@"BOSH: Wrong Type Passed to createArrayFrom Dictionary"); + NSException *exception = [NSException exceptionWithName:@"InvalidXMLNodeType" + reason:@"BOSH: Wrong Type Passed to createArrayFrom Dictionary" + userInfo:nil]; + @throw exception; } [array addObject:node]; From 819b41ee8095b72a970c8b3d16c77fd09d49c797 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 24 Jun 2011 00:37:47 +0530 Subject: [PATCH 100/180] BOSH: Whitespace fixes. --- Core/Transports/BoshTransport.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index aa8c383..8e25bfc 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -53,6 +53,7 @@ - (void)removeObjectForLongLongKey:(long long)number NSNumber *nsNumber = [NSNumber numberWithLongLong:number]; [self removeObjectForKey:nsNumber]; } + - (id)objectForLongLongKey:(long long)number { NSNumber *nsNumber = [NSNumber numberWithLongLong:number]; @@ -733,8 +734,8 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload attributes:(NSMutableDictionary *)attributes namespaces:(NSMutableDictionary *)namespaces { - attributes = attributes?attributes:[NSMutableDictionary dictionaryWithCapacity:3]; - namespaces = namespaces?namespaces:[NSMutableDictionary dictionaryWithCapacity:1]; + attributes = attributes ? attributes : [NSMutableDictionary dictionaryWithCapacity:3]; + namespaces = namespaces ? namespaces : [NSMutableDictionary dictionaryWithCapacity:1]; /* Adding ack and sid attribute on every outgoing request after sid is created */ if( self.sid ) From c0cdca3c40f057e1f849442853e43ae0ab674052 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 24 Jun 2011 01:15:21 +0530 Subject: [PATCH 101/180] BOSH: Fix memory leak -- don't copy response child nodes in broadcastStanzas: --- Core/Transports/BoshTransport.m | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 8e25bfc..bcd68a8 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -505,17 +505,15 @@ - (void)trySendingStanzas We should ideally put all the request in the queue and call processRequestQueue with a timeOut. */ -- (void)broadcastStanzas:(NSXMLNode *)node +- (void)broadcastStanzas:(NSXMLNode *)body { - node = [node childAtIndex:0]; - if (!node) - { - return; + while ([body childCount] > 0) { + NSXMLNode *node = [body childAtIndex:0]; + if ([node isKindOfClass:[NSXMLElement class]]) { + [node detach]; + [multicastDelegate transport:self didReceiveStanza:(NSXMLElement *)node]; + } } - do - { - [multicastDelegate transport:self didReceiveStanza:[(NSXMLElement *)node copy]]; - }while ((node = [node nextSibling])); } #pragma mark - From b1ade6a0bf40e6596a26027ff46d59864c5951f3 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 24 Jun 2011 17:47:55 +0530 Subject: [PATCH 102/180] Copy the element that XMPPStream gets from the transport. Prior to this, login was failing in Openfire. Haven't been able to figure out the root cause of this. --- Core/XMPPStream.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index feb8df2..96a8b55 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1353,7 +1353,9 @@ - (void)transport:(id)sender didReceiveStanza:(NSXMLEleme { // We've just read in the stream features // We consider this part of the root element, so we'll add it (replacing any previously sent features) - [rootElement setChildren:[NSArray arrayWithObject:element]]; + NSXMLElement *newElement = [element copy]; + [rootElement setChildren:[NSArray arrayWithObject:newElement]]; + [newElement release]; // Call a method to handle any requirements set forth in the features [self handleStreamFeatures]; From 98cd1fe089ade70ae922ef7eb474f09e41cb63a9 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Fri, 24 Jun 2011 18:57:45 +0530 Subject: [PATCH 103/180] Treating any response without the bosh namespace as a req failure --- Core/Transports/BoshTransport.m | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index bcd68a8..4944447 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -149,7 +149,7 @@ - (void) dealloc static const NSTimeInterval INITIAL_RETRY_DELAY = 1.0; static const NSString *CONTENT_TYPE = @"text/xml; charset=utf-8"; -static const NSString *BODY_NS = @"http://jabber.org/protocol/httpbind"; +static NSString *BODY_NS = @"http://jabber.org/protocol/httpbind"; static const NSString *XMPP_NS = @"urn:xmpp:xbosh"; @interface BoshTransport() @@ -660,6 +660,11 @@ - (void)requestFailed:(ASIHTTPRequest *)request else { DDLogWarn(@"disconnecting due to request failure"); + if (error == nil) { + error = [[[NSError alloc] initWithDomain:BoshTerminateConditionDomain + code:UNDEFINED_CONDITION + userInfo:nil] autorelease]; + } [multicastDelegate transportWillDisconnect:self withError:error]; state = DISCONNECTED; [self handleDisconnection]; @@ -676,15 +681,19 @@ - (void)requestFinished:(ASIHTTPRequest *)request DDLogRecvPre(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); NSData *responseData = [request responseData]; - retryCounter = 0; - nextRequestDelay = INITIAL_RETRY_DELAY; - NSXMLElement *parsedResponse = [self parseXMLData:responseData]; - if ( !parsedResponse ) + + if (!parsedResponse || parsedResponse.kind != DDXMLElementKind || + ![parsedResponse.name isEqualToString:@"body"] || + ![[parsedResponse namespaceStringValueForPrefix:@""] isEqualToString:BODY_NS]) { [self requestFailed:request]; return; } + + retryCounter = 0; + nextRequestDelay = INITIAL_RETRY_DELAY; + RequestResponsePair *requestResponsePair = [requestResponsePairs objectForLongLongKey:rid]; [requestResponsePair setResponse:parsedResponse]; From 5a09b74e6d7b7e9f3fe4dbd5e9eb622c53263797 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Sun, 26 Jun 2011 14:58:54 +0530 Subject: [PATCH 104/180] BOSH: Cancel pending HTTP requests before disconnect, not while de-allocating memory. --- Core/Transports/BoshTransport.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 4944447..f1f409c 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -600,6 +600,11 @@ - (void)handleDisconnection } [pendingXMPPStanzas removeAllObjects]; state = DISCONNECTED; + for (ASIHTTPRequest *request in pendingHTTPRequests_) + { + DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); + [request clearDelegatesAndCancel]; + } [multicastDelegate transportDidDisconnect:self]; } @@ -858,11 +863,6 @@ - (NSNumber *)numberFromString:(NSString *)stringNumber - (void)dealloc { - for (ASIHTTPRequest *request in pendingHTTPRequests_) - { - DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); - [request clearDelegatesAndCancel]; - } [pendingHTTPRequests_ removeAllObjects]; [pendingHTTPRequests_ release]; From 360a2f444a76d4d3ef8442b8cf8d759d34a69d6a Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 29 Jun 2011 17:35:58 +0530 Subject: [PATCH 105/180] vCard: Send 'x-only-base64encodedimage-required' attribute to get BINVAL photos for Talk.to accounts. --- Extensions/XEP-0054/XMPPvCardTemp.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Extensions/XEP-0054/XMPPvCardTemp.m b/Extensions/XEP-0054/XMPPvCardTemp.m index 4003f06..c2110bc 100644 --- a/Extensions/XEP-0054/XMPPvCardTemp.m +++ b/Extensions/XEP-0054/XMPPvCardTemp.m @@ -67,6 +67,7 @@ + (XMPPvCardTemp *)vCardTempFromIQ:(XMPPIQ *)iq { + (XMPPIQ *)iqvCardRequestForJID:(XMPPJID *)jid { XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:[jid bareJID]]; NSXMLElement *vCardElem = [NSXMLElement elementWithName:kXMPPvCardTempElement xmlns:kXMPPNSvCardTemp]; + [vCardElem addAttributeWithName:@"x-only-base64encodedimage-required" stringValue:@"true"]; [iq addChild:vCardElem]; return iq; From 8361b581a6c023b08c763e97796ceaf0aaf9daa9 Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Fri, 29 Jul 2011 14:57:37 +0530 Subject: [PATCH 106/180] Added support for customAuthentication, Added a custom property for token. --- Core/XMPPStream.h | 11 +++++++++++ Core/XMPPStream.m | 26 ++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 56a444a..4ca8186 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -144,6 +144,12 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; **/ @property (nonatomic, readwrite, retain) id tag; +/** + * This is a custom property. + * A token that may be used for custom authentication. +**/ +@property (nonatomic, copy) NSString *token; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark State //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -581,4 +587,9 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; - (void)xmppStream:(XMPPStream *)sender didRegisterModule:(id)module; - (void)xmppStream:(XMPPStream *)sender willUnregisterModule:(id)module; +/** + * This is a custom method to pass on a token to the interested delegates. +**/ +- (void)xmppStream:(XMPPStream *)sender receivedAuthToken:(NSString *)token; + @end diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 96a8b55..75fea94 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -16,6 +16,9 @@ #import #endif +#define supportsCustomAuthenticationSelector @selector(supportsCustomAuthentication) +#define customAuthenticationSelector @selector(customAuthenticationWithPassword:) +#define customHandleAuth1Selector @selector(customHandleAuth1:) NSString *const XMPPStreamErrorDomain = @"XMPPStreamErrorDomain"; @@ -77,6 +80,7 @@ @implementation XMPPStream @synthesize myPresence; @synthesize registeredModules; @synthesize tag = userTag; +@synthesize token = token_; /** * Shared initialization between the various init methods. @@ -89,6 +93,8 @@ - (void)commonInit registeredModules = [[MulticastDelegate alloc] init]; autoDelegateDict = [[NSMutableDictionary alloc] init]; + + token_ = nil; } - (id)initWithTransport:(id)givenTransport @@ -635,7 +641,15 @@ - (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr return NO; } - if ([self supportsDigestMD5Authentication]) + SEL supportsCustomAuthentication = supportsCustomAuthenticationSelector; + SEL customAuthentication = customAuthenticationSelector; + if ([self respondsToSelector:supportsCustomAuthentication] && + [self performSelector:supportsCustomAuthentication]) { + if ([self respondsToSelector:customAuthentication]) { + [self performSelector:customAuthentication withObject:password]; + } + } + else if ([self supportsDigestMD5Authentication]) { NSString *auth = @""; [transport sendStanzaWithString:auth]; @@ -1042,7 +1056,15 @@ - (void)handleRegistration:(NSXMLElement *)response **/ - (void)handleAuth1:(NSXMLElement *)response { - if([self supportsDigestMD5Authentication]) + SEL supportsCustomAuthentication = supportsCustomAuthenticationSelector; + SEL customHandleAuth1 = customHandleAuth1Selector; + if ([self respondsToSelector:supportsCustomAuthentication] && + [self performSelector:supportsCustomAuthentication]) { + if ([self respondsToSelector:customHandleAuth1]) { + [self performSelector:customHandleAuth1 withObject:response]; + } + } + else if([self supportsDigestMD5Authentication]) { // We're expecting a challenge response // If we get anything else we can safely assume it's the equivalent of a failure response From 598956dab3189a20c23b35f9a3f68d116bd4bc79 Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Tue, 2 Aug 2011 14:04:02 +0530 Subject: [PATCH 107/180] Revert "Added support for customAuthentication, Added a custom property for token." This reverts commit 8361b581a6c023b08c763e97796ceaf0aaf9daa9. --- Core/XMPPStream.h | 11 ----------- Core/XMPPStream.m | 26 ++------------------------ 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 4ca8186..56a444a 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -144,12 +144,6 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; **/ @property (nonatomic, readwrite, retain) id tag; -/** - * This is a custom property. - * A token that may be used for custom authentication. -**/ -@property (nonatomic, copy) NSString *token; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark State //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -587,9 +581,4 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; - (void)xmppStream:(XMPPStream *)sender didRegisterModule:(id)module; - (void)xmppStream:(XMPPStream *)sender willUnregisterModule:(id)module; -/** - * This is a custom method to pass on a token to the interested delegates. -**/ -- (void)xmppStream:(XMPPStream *)sender receivedAuthToken:(NSString *)token; - @end diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 75fea94..96a8b55 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -16,9 +16,6 @@ #import #endif -#define supportsCustomAuthenticationSelector @selector(supportsCustomAuthentication) -#define customAuthenticationSelector @selector(customAuthenticationWithPassword:) -#define customHandleAuth1Selector @selector(customHandleAuth1:) NSString *const XMPPStreamErrorDomain = @"XMPPStreamErrorDomain"; @@ -80,7 +77,6 @@ @implementation XMPPStream @synthesize myPresence; @synthesize registeredModules; @synthesize tag = userTag; -@synthesize token = token_; /** * Shared initialization between the various init methods. @@ -93,8 +89,6 @@ - (void)commonInit registeredModules = [[MulticastDelegate alloc] init]; autoDelegateDict = [[NSMutableDictionary alloc] init]; - - token_ = nil; } - (id)initWithTransport:(id)givenTransport @@ -641,15 +635,7 @@ - (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr return NO; } - SEL supportsCustomAuthentication = supportsCustomAuthenticationSelector; - SEL customAuthentication = customAuthenticationSelector; - if ([self respondsToSelector:supportsCustomAuthentication] && - [self performSelector:supportsCustomAuthentication]) { - if ([self respondsToSelector:customAuthentication]) { - [self performSelector:customAuthentication withObject:password]; - } - } - else if ([self supportsDigestMD5Authentication]) + if ([self supportsDigestMD5Authentication]) { NSString *auth = @""; [transport sendStanzaWithString:auth]; @@ -1056,15 +1042,7 @@ - (void)handleRegistration:(NSXMLElement *)response **/ - (void)handleAuth1:(NSXMLElement *)response { - SEL supportsCustomAuthentication = supportsCustomAuthenticationSelector; - SEL customHandleAuth1 = customHandleAuth1Selector; - if ([self respondsToSelector:supportsCustomAuthentication] && - [self performSelector:supportsCustomAuthentication]) { - if ([self respondsToSelector:customHandleAuth1]) { - [self performSelector:customHandleAuth1 withObject:response]; - } - } - else if([self supportsDigestMD5Authentication]) + if([self supportsDigestMD5Authentication]) { // We're expecting a challenge response // If we get anything else we can safely assume it's the equivalent of a failure response From f83dad39d09f7f042c6edda6afd4343a19229503 Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Tue, 2 Aug 2011 15:14:12 +0530 Subject: [PATCH 108/180] Added a state for custom authentication. Call for custom authentication instead of regular authentication method. If no custom auth is provided, regular authentication will be done. --- Core/XMPPStream.h | 19 ++++++++++++++++++ Core/XMPPStream.m | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 56a444a..3848cd0 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -23,6 +23,7 @@ enum { STATE_NEGOTIATING, STATE_STARTTLS, STATE_REGISTERING, + STATE_CUSTOM_AUTH, STATE_AUTH_1, STATE_AUTH_2, STATE_AUTH_3, @@ -65,6 +66,8 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; NSMutableDictionary *autoDelegateDict; id userTag; + + id customAuthTarget; } - (id)initWithTransport:(id)transport; @@ -144,6 +147,12 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; **/ @property (nonatomic, readwrite, retain) id tag; +/** + * The target object for custom authentication methods. + * The selector to be performed on this target is "customAuthSelector" +**/ +@property (nonatomic, retain) id customAuthTarget; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark State //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -292,6 +301,16 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; - (void)handleAuth1:(NSXMLElement *)response; +/** + * Custom Authentication. + * + * To override the priorities of authentication mechanisms, + * and to specify custom mechanisms as well. +**/ +- (BOOL)startCustomAuthenticationWithPassword:(NSString *)password error:(NSError **)errPtr; +- (void)didFinishCustomAuthentication; +- (void)didFailCustomAuthentication:(NSXMLElement *)response; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Server Info //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 96a8b55..854c526 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -78,6 +78,8 @@ @implementation XMPPStream @synthesize registeredModules; @synthesize tag = userTag; +@synthesize customAuthTarget; + /** * Shared initialization between the various init methods. **/ @@ -770,6 +772,31 @@ - (void)setIsAuthenticated:(BOOL)flag flags &= ~kIsAuthenticated; } +/** + * Custom Authentication. +**/ +- (BOOL)startCustomAuthenticationWithPassword:(NSString *)password error:(NSError **)errPtr +{ + if (state != STATE_CONNECTED) + { + if (errPtr) + { + NSString *errMsg = @"Please wait until the stream is connected."; + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; + } + return NO; + } + + if (customAuthTarget == nil) { + return [self authenticateWithPassword:password error:errPtr]; + } + + state = STATE_CUSTOM_AUTH; + return [[customAuthTarget performSelector:@selector(customAuthWithPassword:rootElement:) withObject:password withObject:rootElement] boolValue]; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark General Methods //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1288,6 +1315,25 @@ - (void)handleStartSessionResponse:(NSXMLElement *)response } } +/** + * Handling of custom authentication responses. +**/ +- (void)didFinishCustomAuthentication { + // We are successfully authenticated + [self setIsAuthenticated:YES]; + state = STATE_NEGOTIATING; + + // Now we start our negotiation over again... + [self restartStream]; +} + +- (void)didFailCustomAuthentication:(NSXMLElement *)response { + // Revert back to connected state (from authenticating state) + state = STATE_CONNECTED; + + [multicastDelegate xmppStream:self didNotAuthenticate:response]; +} + ////////////////////////////////////// #pragma mark XMPPTransport Delegate ////////////////////////////////////// @@ -1370,6 +1416,11 @@ - (void)transport:(id)sender didReceiveStanza:(NSXMLEleme // The iq response from our registration request [self handleRegistration:element]; } + else if (state == STATE_CUSTOM_AUTH) + { + //The customAuthTarget must respond to this selector. + [customAuthTarget performSelector:@selector(handleCustomAuth:) withObject:element]; + } else if(state == STATE_AUTH_1) { // The challenge response from our auth message From 4878ac619244a0121c5dc39cf1bbe6f959e8f724 Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Tue, 2 Aug 2011 16:40:24 +0530 Subject: [PATCH 109/180] Must be able to send stanza's when state is custom_auth. --- Core/XMPPStream.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 854c526..4ab8765 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -900,7 +900,7 @@ - (void)sendElement:(NSXMLElement *)element withTag:(long)tag **/ - (void)sendElement:(NSXMLElement *)element { - if (state == STATE_CONNECTED) + if (state == STATE_CONNECTED || state == STATE_CUSTOM_AUTH) { [self sendElement:element withTag:0]; } From aeed029d916bdeac84632412c6b0895cb2fd6587 Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Wed, 3 Aug 2011 19:28:38 +0530 Subject: [PATCH 110/180] Passing in target and selector while calling functions and storing it for later use. --- Core/XMPPStream.h | 9 +++++++-- Core/XMPPStream.m | 26 ++++++++++++-------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 3848cd0..146f82e 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -68,6 +68,8 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; id userTag; id customAuthTarget; + SEL customAuthSelector; + SEL customHandleAuthSelector; } - (id)initWithTransport:(id)transport; @@ -149,9 +151,12 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; /** * The target object for custom authentication methods. - * The selector to be performed on this target is "customAuthSelector" + * The auth selector to be performed on this target. + * The selector for the response of authentication. **/ @property (nonatomic, retain) id customAuthTarget; +@property (nonatomic) SEL customAuthSelector; +@property (nonatomic) SEL customHandleAuthSelector; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark State @@ -307,7 +312,7 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; * To override the priorities of authentication mechanisms, * and to specify custom mechanisms as well. **/ -- (BOOL)startCustomAuthenticationWithPassword:(NSString *)password error:(NSError **)errPtr; +- (BOOL)startCustomAuthenticationWithTarget:(id)target authSelector:(SEL)authSelector handleAuthSelector:(SEL)handleAuthSelector; - (void)didFinishCustomAuthentication; - (void)didFailCustomAuthentication:(NSXMLElement *)response; diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 4ab8765..7c25f74 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -79,6 +79,8 @@ @implementation XMPPStream @synthesize tag = userTag; @synthesize customAuthTarget; +@synthesize customAuthSelector; +@synthesize customHandleAuthSelector; /** * Shared initialization between the various init methods. @@ -775,26 +777,20 @@ - (void)setIsAuthenticated:(BOOL)flag /** * Custom Authentication. **/ -- (BOOL)startCustomAuthenticationWithPassword:(NSString *)password error:(NSError **)errPtr +- (BOOL)startCustomAuthenticationWithTarget:(id)target authSelector:(SEL)authSelector handleAuthSelector:(SEL)handleAuthSelector { if (state != STATE_CONNECTED) { - if (errPtr) - { - NSString *errMsg = @"Please wait until the stream is connected."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; - } return NO; } - if (customAuthTarget == nil) { - return [self authenticateWithPassword:password error:errPtr]; - } - state = STATE_CUSTOM_AUTH; - return [[customAuthTarget performSelector:@selector(customAuthWithPassword:rootElement:) withObject:password withObject:rootElement] boolValue]; + customAuthTarget = [target retain]; + customAuthSelector = authSelector; + customHandleAuthSelector = handleAuthSelector; + + [target performSelector:authSelector]; + return YES; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1325,6 +1321,7 @@ - (void)didFinishCustomAuthentication { // Now we start our negotiation over again... [self restartStream]; + [customAuthTarget release]; } - (void)didFailCustomAuthentication:(NSXMLElement *)response { @@ -1332,6 +1329,7 @@ - (void)didFailCustomAuthentication:(NSXMLElement *)response { state = STATE_CONNECTED; [multicastDelegate xmppStream:self didNotAuthenticate:response]; + [customAuthTarget release]; } ////////////////////////////////////// @@ -1419,7 +1417,7 @@ - (void)transport:(id)sender didReceiveStanza:(NSXMLEleme else if (state == STATE_CUSTOM_AUTH) { //The customAuthTarget must respond to this selector. - [customAuthTarget performSelector:@selector(handleCustomAuth:) withObject:element]; + [customAuthTarget performSelector:customHandleAuthSelector withObject:element]; } else if(state == STATE_AUTH_1) { From 2249e3fe71d467f4bcd1aeb78b842e3e86a3dff8 Mon Sep 17 00:00:00 2001 From: Satyam Shekhar Date: Thu, 18 Aug 2011 18:35:07 +0530 Subject: [PATCH 111/180] Not disconnecting xmppstream if the server messages before establishing the session This however leads to the case of message loss, which should be fixed by the server. --- Core/XMPPStream.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 7c25f74..4a65acf 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1302,6 +1302,10 @@ - (void)handleStartSessionResponse:(NSXMLElement *)response [multicastDelegate xmppStreamDidAuthenticate:self]; } + + //Commenting out this code since pappu doesn't honour rfc right now + //should be changed once pappu starts doing the correct thing. + /* else { // Revert back to connected state (from start session state) @@ -1309,6 +1313,7 @@ - (void)handleStartSessionResponse:(NSXMLElement *)response [multicastDelegate xmppStream:self didNotAuthenticate:response]; } + */ } /** From e377b2776bb7080c19b04465c3efd212dd356887 Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Thu, 25 Aug 2011 17:03:12 +0530 Subject: [PATCH 112/180] Using an attribute routeProtocol to be used inside 'route'. Also using 5222 as default port. --- Core/Transports/BoshTransport.h | 3 +++ Core/Transports/BoshTransport.m | 27 +++++++++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index b557a4f..744d87a 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -107,6 +107,7 @@ typedef enum { @property(assign) unsigned int hold; @property(copy) NSString *lang; @property(copy) NSString *domain; +@property(copy) NSString *routeProtocol; @property(copy) NSString *host; @property(assign) unsigned int port; @property(assign) unsigned int inactivity; @@ -121,6 +122,7 @@ typedef enum { - (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain; - (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain + routeProtocol:(NSString *)routeProtocol host:(NSString *)host port:(unsigned int)port; - (id)initWithUrl:(NSURL *)url @@ -128,6 +130,7 @@ typedef enum { withDelegate:(id)delegate; - (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain + routeProtocol:(NSString *)routeProtocol host:(NSString *)host port:(unsigned int)port withDelegate:(id)delegate; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index f1f409c..d83513b 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -192,6 +192,7 @@ @implementation BoshTransport @synthesize hold = hold_; @synthesize lang = lang_; @synthesize domain = domain_; +@synthesize routeProtocol = routeProtocol_; @synthesize host = host_; @synthesize port = port_; @synthesize myJID = myJID_; @@ -241,11 +242,13 @@ - (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain - (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain + routeProtocol:(NSString *)routeProtocol host:(NSString *)host port:(unsigned int)port { return [self initWithUrl:url forDomain:domain + routeProtocol:routeProtocol host:host port:port withDelegate:nil]; @@ -257,13 +260,15 @@ - (id)initWithUrl:(NSURL *)url { return [self initWithUrl:url forDomain:domain + routeProtocol:routeProtocol_ host:nil - port:0 + port:5222 //Default xmpp port withDelegate:delegate]; } - (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain + routeProtocol:(NSString *)routeProtocol host:(NSString *)host port:(unsigned int)port withDelegate:(id)delegate @@ -288,16 +293,17 @@ - (id)initWithUrl:(NSURL *)url url_ = [url retain]; domain_ = [domain copy]; - host_ = nil; - port_ = 0; - if (host != nil) { - host_ = [host copy]; + routeProtocol_ = nil; + if (routeProtocol != nil) { + routeProtocol_ = [routeProtocol copy]; } - if (port) { - port_ = port; + host_ = nil; + if (host != nil) { + host_ = [host copy]; } + port_ = port; myJID_ = nil; state = DISCONNECTED; @@ -434,9 +440,9 @@ - (BOOL)createSession:(NSError **)error [attr setObject:@"1.0" forKey:@"xmpp:version"]; [attr setObject:[NSString stringWithFormat:@"%u", self.inactivity] forKey:@"inactivity"]; [attr setObject:@"iphone" forKey:@"ua"]; - if (self.host != nil && self.port) { - NSString *route = [NSString stringWithFormat:@"xmpp:%@:%u", self.host, self.port]; - [attr setObject:route forKey:@"route"]; + if (self.host != nil) { + NSString *route = [NSString stringWithFormat:@"%@:%@:%u", self.routeProtocol, self.host, self.port]; + [attr setObject:route forKey:@"route"]; } NSMutableDictionary *ns = [NSMutableDictionary dictionaryWithObjectsAndKeys: XMPP_NS, @"xmpp", nil]; @@ -869,6 +875,7 @@ - (void)dealloc [multicastDelegate release]; [url_ release]; [domain_ release]; + [routeProtocol_ release]; [host_ release]; [myJID_ release]; [authid release]; From fff07a5506665b72b39d1c3085ba07fa8d89c839 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 19 Sep 2011 18:06:17 +0530 Subject: [PATCH 113/180] XMPPMessage: Send 'sid' attribute with message receipt. --- Core/XMPPMessage.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/XMPPMessage.m b/Core/XMPPMessage.m index 4b5cf69..d273f66 100644 --- a/Core/XMPPMessage.m +++ b/Core/XMPPMessage.m @@ -149,6 +149,11 @@ - (XMPPMessage *)generateReceiptResponse:(NSString *)receiptType [received addAttributeWithName:@"id" stringValue:msgid]; } + NSString *sid = [[self attributeForName:@"sid"] stringValue]; + if (sid) { + [received addAttributeWithName:@"sid" stringValue:sid]; + } + [message addChild:received]; return [[self class] messageFromElement:message]; From 8313eafe2e1e84412fde9295a1132677dde06654 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 18 Oct 2011 21:17:14 +0530 Subject: [PATCH 114/180] Add NSCoding protocol to XMPPStream. Also make transport a assignable property of xmppStream --- Core/XMPPStream.h | 4 ++- Core/XMPPStream.m | 79 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 146f82e..f8001a8 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -45,7 +45,7 @@ enum XMPPStreamErrorCode typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; -@interface XMPPStream : NSObject +@interface XMPPStream : NSObject { MulticastDelegate *multicastDelegate; @@ -158,6 +158,8 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; @property (nonatomic) SEL customAuthSelector; @property (nonatomic) SEL customHandleAuthSelector; +@property (nonatomic, assign) id transport; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark State //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 4a65acf..c98b2d5 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -82,6 +82,18 @@ @implementation XMPPStream @synthesize customAuthSelector; @synthesize customHandleAuthSelector; +@dynamic transport; + +- (void)setTransport:(id)givenTransport +{ + transport = givenTransport; + [transport addDelegate:self]; +} + +- (id)transport +{ + return transport; +} /** * Shared initialization between the various init methods. **/ @@ -100,8 +112,7 @@ - (id)initWithTransport:(id)givenTransport if ((self = [super init])) { [self commonInit]; - transport = givenTransport; - [transport addDelegate:self]; + [self setTransport:givenTransport]; } return self; } @@ -144,10 +155,72 @@ - (void)dealloc [autoDelegateDict release]; [userTag release]; - + [super dealloc]; } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark NSCoding Protocol methods +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#define kXSState @"int state" + +#define kXSFlags @"Byte flags" + +#define kXSTempPassword @"NSString *tempPassword" + +#define kXSMyJID @"XMPPJID *myJID" +#define kXSRemoteJID @"XMPPJID *remoteJID" + +#define kXSMYPresence @"XMPPPresence *myPresence" +#define kXSRootelement @"NSXMLElement *rootElement" + +//#define @"id userTag" not being used ryt now in the code, therefore not using +// +//#define @"id customAuthTarget" +//#define @"SEL customAuthSelector" +//#define @"SEL customHandleAuthSelector" + +- (void)encodeWithCoder: (NSCoder *)coder +{ + [coder encodeInt:state forKey:kXSState]; + [coder encodeInt:flags forKey:kXSFlags]; + [coder encodeObject:tempPassword forKey:kXSTempPassword]; + [coder encodeObject:myJID forKey:kXSMyJID]; + [coder encodeObject:remoteJID forKey:kXSRemoteJID]; + + [coder encodeObject:myPresence forKey:kXSMYPresence]; + [coder encodeObject:rootElement forKey:kXSRootelement]; +} + +- (void)commonInitWithCoder:(NSCoder *)coder +{ + state = [coder decodeIntForKey:kXSState]; + flags = (Byte) [coder decodeIntForKey:kXSFlags]; + + tempPassword = [[coder decodeObjectForKey:kXSTempPassword] copy]; + myJID = [[coder decodeObjectForKey:kXSMyJID] copy]; + remoteJID = [[coder decodeObjectForKey:kXSRemoteJID] copy] ; + + myPresence = [[coder decodeObjectForKey:kXSMYPresence] retain]; + rootElement = [[coder decodeObjectForKey:kXSRootelement] retain]; + + multicastDelegate = [[MulticastDelegate alloc] init]; + registeredModules = [[MulticastDelegate alloc] init]; + autoDelegateDict = [[NSMutableDictionary alloc] init]; + +} + +- (id)initWithCoder: (NSCoder *)coder +{ + self = [self init]; + if (self && coder) + { + [self commonInitWithCoder:coder]; + } + return self; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Configuration //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 6c68418757d84a01258ce14c255d900315d7366f Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 18 Oct 2011 21:18:50 +0530 Subject: [PATCH 115/180] Add NSCoding protocol to NSXMLElement. Also add class method 'parseWellFormatedXMLString' to it --- Categories/NSXMLElementAdditions.h | 4 +++- Categories/NSXMLElementAdditions.m | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Categories/NSXMLElementAdditions.h b/Categories/NSXMLElementAdditions.h index ee77510..1d552df 100644 --- a/Categories/NSXMLElementAdditions.h +++ b/Categories/NSXMLElementAdditions.h @@ -5,7 +5,7 @@ #endif -@interface NSXMLElement (XMPPStreamAdditions) +@interface NSXMLElement (XMPPStreamAdditions) + (NSXMLElement *)elementWithName:(NSString *)name xmlns:(NSString *)ns; @@ -43,4 +43,6 @@ - (NSString *)namespaceStringValueForPrefix:(NSString *)prefix; - (NSString *)namespaceStringValueForPrefix:(NSString *)prefix withDefaultValue:(NSString *)defaultValue; ++ (NSXMLElement *)parseWellFormatedXMLString:(NSString *)xml; + @end diff --git a/Categories/NSXMLElementAdditions.m b/Categories/NSXMLElementAdditions.m index 2b4778c..1895152 100644 --- a/Categories/NSXMLElementAdditions.m +++ b/Categories/NSXMLElementAdditions.m @@ -219,4 +219,28 @@ - (NSString *)namespaceStringValueForPrefix:(NSString *)prefix withDefaultValue: return (namespace) ? [namespace stringValue] : defaultValue; } ++ (NSXMLElement *)parseWellFormatedXMLString:(NSString *)xml +{ + NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:xml + options:0 + error:NULL] autorelease]; + NSXMLElement *element = [doc rootElement]; + [element detach]; + return element; +} + +#define kNSXMLElement @"NSXMLElementCompactXMLString" + +- (void)encodeWithCoder: (NSCoder *)coder +{ + [coder encodeObject:[self compactXMLString] forKey:kNSXMLElement]; +} + +- (id)initWithCoder: (NSCoder *)coder +{ + [self release]; + self = [[NSXMLElement parseWellFormatedXMLString:(NSString *)[coder decodeObjectForKey:kNSXMLElement]] retain]; + return self; +} + @end From 8024156e7ff85b79f38ba99bed38c2be837b2c6b Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 18 Oct 2011 21:25:50 +0530 Subject: [PATCH 116/180] BoshTransport.m: fixing ownership of 'lang' in boshTransport. Also make boshVersion #defined value instead of constant (autoreleased) string, so different usage share the value --- Core/Transports/BoshTransport.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index d83513b..47669bd 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -205,6 +205,8 @@ @implementation BoshTransport @synthesize disconnectError = disconnectError_; @synthesize pendingHTTPRequests = pendingHTTPRequests_; +#define BoshVersion @"1.6" + #pragma mark - #pragma mark Private Accessor Method Implementation @@ -276,8 +278,8 @@ - (id)initWithUrl:(NSURL *)url self = [super init]; if(self) { - boshVersion = @"1.6"; - lang_ = @"en"; + boshVersion = BoshVersion; + lang_ = [@"en" retain]; wait_ = 60.0; hold_ = 1; @@ -869,6 +871,8 @@ - (NSNumber *)numberFromString:(NSString *)stringNumber - (void)dealloc { + [lang_ release]; + [pendingHTTPRequests_ removeAllObjects]; [pendingHTTPRequests_ release]; From 94c0b7ef72ac5ebdd69d2ae03556c4457db538c8 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 18 Oct 2011 21:28:32 +0530 Subject: [PATCH 117/180] Add NSCoding protocol to BoshTransport and related internal classes --- Core/Transports/BoshTransport.h | 9 +- Core/Transports/BoshTransport.m | 170 ++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 3 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 744d87a..83b67bf 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -51,7 +51,7 @@ typedef enum { TERMINATING = 4 } BoshTransportState; -@interface RequestResponsePair : NSObject +@interface RequestResponsePair : NSObject @property (nonatomic, retain) NSXMLElement *request; @property (nonatomic, retain) NSXMLElement *response; - (id)initWithRequest:(NSXMLElement *)request response:(NSXMLElement *)response; @@ -63,7 +63,7 @@ typedef enum { /** * Handles the in-order processing of responses. **/ -@interface BoshWindowManager : NSObject { +@interface BoshWindowManager : NSObject { long long maxRidReceived; // all rid value less than equal to maxRidReceived are processed. long long maxRidSent; NSMutableSet *receivedRids; @@ -83,7 +83,7 @@ typedef enum { #pragma mark - -@interface BoshTransport : NSObject { +@interface BoshTransport : NSObject { NSString *boshVersion; long long nextRidToSend; @@ -100,6 +100,9 @@ typedef enum { int retryCounter; NSTimeInterval nextRequestDelay; + + BOOL secure; + unsigned int requests; } @property(retain) XMPPJID *myJID; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 47669bd..c5bea76 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -84,6 +84,29 @@ - (void)dealloc [response_ release]; [super dealloc]; } + +#define kRRPairRequest @"@property (nonatomic, retain) NSXMLElement *request" +#define kRRPairResponse @"@property (nonatomic, retain) NSXMLElement *response" + +- (void)encodeWithCoder: (NSCoder *)coder +{ + [coder encodeObject:self.request forKey:kRRPairRequest]; + [coder encodeObject:self.response forKey:kRRPairResponse]; +} + +- (id)initWithCoder: (NSCoder *)coder +{ + self = [self init]; + if (self && coder) + { + self.request = [coder decodeObjectForKey:kRRPairRequest ]; + self.response = [coder decodeObjectForKey:kRRPairResponse]; + } + + return self; +} + + @end #pragma - @@ -140,6 +163,40 @@ - (void) dealloc [receivedRids release]; [super dealloc]; } + +#define kWMMaxRidReceived @"long long maxRidReceived" +#define kWMMaxRidSent @"long long maxRidSent" +#define kWMReceivedRids @"NSMutableSet *receivedRids" +#define kWMWindowSize @"@property unsigned int windowSize" + +- (void)encodeWithCoder: (NSCoder *)coder +{ + [coder encodeInt64:maxRidReceived forKey:kWMMaxRidReceived]; + [coder encodeInt64:maxRidSent forKey:kWMMaxRidSent]; + [coder encodeObject:receivedRids forKey:kWMReceivedRids]; + [coder encodeInt:self.windowSize forKey:kWMWindowSize]; +} + +- (void)commonInitWithCoder:(NSCoder *)coder +{ + maxRidSent = [coder decodeInt64ForKey:kWMMaxRidSent]; + maxRidReceived = [coder decodeInt64ForKey:kWMMaxRidReceived]; + receivedRids = [[coder decodeObjectForKey:kWMReceivedRids] retain]; + self.windowSize = [coder decodeIntForKey:kWMWindowSize]; +} + +- (id)initWithCoder: (NSCoder *)coder +{ + self = [self init]; + if (self && coder) + { + [self commonInitWithCoder:coder]; + } + + return self; +} + + @end static const int RETRY_COUNT_LIMIT = 25; @@ -886,4 +943,117 @@ - (void)dealloc [sid_ release]; [super dealloc]; } + +#pragma mark Protocol NSCoding Method Implementation + +#define kNextRidToSend @"long long nextRidToSend" +#define kMaxRidProcessed @"long long maxRidProcessed" + +#define kPendingXMPPStanza @"NSMutableArray *pendingXMPPStanzas" +#define kBoshWindowManager @"BoshWindowManager *boshWindowManager" +#define kState @"BoshTransportState state" + +#define kRequestResponsePairs @"NSMutableDictionary *requestResponsePairs" + +#define kDisconnectError_ @"NSError *disconnectError_;" +// +#define kRetryCounter @"int retryCounter;" +#define kNextRequestDelay @"NSTimeInterval nextRequestDelay;" +// +#define kMyJID @"@property(retain) XMPPJID *myJID;" +#define kWait @"@property(assign) unsigned int wait;" +#define kHold @"@property(assign) unsigned int hold;" +#define kLang @"@property(copy) NSString *lang;" +#define kDomain @"@property(copy) NSString *domain;" +#define kRouteProtocol @"@property(copy) NSString *routeProtocol;" +#define kHost @"@property(copy) NSString *host;" +#define kPort @"@property(assign) unsigned int port;" +#define kInactivity @"@property(assign) unsigned int inactivity" +#define kSecure @"@property(readonly)_BOOL_secure" +#define kRequest @"@property(readonly) unsigned int requests" +#define kAuthId @"@property(copy) NSString *authid" +#define kSid @"@property(copy) NSString *sid" +#define kUrl @"@property(copy) NSURL *url" + + +- (void)encodeWithCoder: (NSCoder *)coder +{ + [coder encodeInt64:nextRidToSend forKey:kNextRidToSend]; + [coder encodeInt64:maxRidProcessed forKey:kMaxRidProcessed]; + + [coder encodeObject:pendingXMPPStanzas forKey:kPendingXMPPStanza]; + [coder encodeObject:boshWindowManager forKey:kBoshWindowManager] ; + [coder encodeInt:state forKey:kState]; + + [coder encodeObject:requestResponsePairs forKey:kRequestResponsePairs]; + + [coder encodeObject:disconnectError_ forKey:kDisconnectError_]; + + [coder encodeInt:retryCounter forKey:kRetryCounter]; + [coder encodeDouble:nextRequestDelay forKey:kNextRequestDelay]; + + [coder encodeObject:self.myJID forKey:kMyJID]; + [coder encodeInt:self.wait forKey:kWait]; + [coder encodeInt:self.hold forKey:kHold]; + [coder encodeObject:self.lang forKey:kLang]; + [coder encodeObject:self.domain forKey:kDomain]; + [coder encodeObject:self.routeProtocol forKey:kRouteProtocol]; + [coder encodeObject:self.host forKey:kHost]; + [coder encodeInt:self.port forKey:kPort]; + [coder encodeInt:self.inactivity forKey:kInactivity]; + [coder encodeBool:self.secure forKey:kSecure]; + [coder encodeInt:self.requests forKey:kRequest]; + [coder encodeObject:self.authid forKey:kAuthId]; + [coder encodeObject:self.sid forKey:kSid]; + [coder encodeObject:self.url forKey:kUrl]; +} + +- (void)commonInitWithCoder:(NSCoder *)coder +{ + boshVersion = BoshVersion; + + nextRidToSend = [coder decodeInt64ForKey:kNextRidToSend]; + maxRidProcessed = [coder decodeInt64ForKey:kMaxRidProcessed]; + + pendingXMPPStanzas =[[coder decodeObjectForKey:kPendingXMPPStanza] retain]; + boshWindowManager = [[coder decodeObjectForKey:kBoshWindowManager] retain]; + state = [coder decodeIntForKey:kState]; + + requestResponsePairs = [[coder decodeObjectForKey:kRequestResponsePairs] retain]; + + disconnectError_ = [[coder decodeObjectForKey:kDisconnectError_] retain]; + + retryCounter = [coder decodeIntForKey:kRetryCounter]; + nextRequestDelay= [coder decodeDoubleForKey:kNextRequestDelay]; + + + self.myJID= [coder decodeObjectForKey:kMyJID]; + self.wait= [coder decodeIntForKey:kWait]; + self.hold= [coder decodeIntForKey:kHold]; + self.lang= [coder decodeObjectForKey:kLang]; + self.domain= [coder decodeObjectForKey:kDomain]; + self.routeProtocol= [coder decodeObjectForKey:kRouteProtocol]; + self.host= [coder decodeObjectForKey:kHost]; + self.port= [coder decodeIntForKey:kPort]; + self.inactivity= [coder decodeIntForKey:kInactivity]; + secure = [coder decodeBoolForKey:kSecure]; + requests = [coder decodeIntForKey:kRequest]; + self.authid= [coder decodeObjectForKey:kAuthId]; + self.sid= [coder decodeObjectForKey:kSid]; + self.url= [coder decodeObjectForKey:kUrl]; + + multicastDelegate = [[MulticastDelegate alloc] init]; + +} + +- (id)initWithCoder: (NSCoder *)coder +{ + self = [self init]; + if (self && coder) + { + [self commonInitWithCoder:coder]; + } + return self; +} + @end From 7fc1f89ecdd18ef0a1f365673d7cf4268d433466 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 18 Oct 2011 22:01:46 +0530 Subject: [PATCH 118/180] Add method to resend unacknowledged packets in BoshTransport --- Core/Transports/BoshTransport.h | 1 + Core/Transports/BoshTransport.m | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 83b67bf..fd67a19 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -156,4 +156,5 @@ typedef enum { - (float)serverXmppStreamVersionNumber; - (BOOL)sendStanza:(NSXMLElement *)stanza; - (BOOL)sendStanzaWithString:(NSString *)string; +- (void)resendRemainingRequests; @end diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index c5bea76..49dc562 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -1056,4 +1056,22 @@ - (id)initWithCoder: (NSCoder *)coder return self; } +/* + * This method to be called after the uncompression of the transport object. + * It looks for packets in requestResponsePairs without a response and sends them. + */ +- (void)resendRemainingRequests +{ + for ( NSNumber *ridNumber in [requestResponsePairs allKeys ]) + { + long long rid = [ridNumber longLongValue]; + RequestResponsePair *pair = [requestResponsePairs objectForLongLongKey:rid]; + if ( !pair.response ) + { + [self sendHTTPRequestWithBody:pair.request rid:rid]; + } + } +} + + @end From b615a71ab5d28d40cf7915e01f85663fac3ac83f Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 19 Oct 2011 23:19:24 +0530 Subject: [PATCH 119/180] Update ASIHTTPRequest. --- .../ASIHTTPRequest/ASIAuthenticationDialog.m | 2 +- Vendor/ASIHTTPRequest/ASICacheDelegate.h | 14 +- Vendor/ASIHTTPRequest/ASIDataCompressor.m | 18 +- Vendor/ASIHTTPRequest/ASIDataDecompressor.m | 14 +- Vendor/ASIHTTPRequest/ASIDownloadCache.h | 7 +- Vendor/ASIHTTPRequest/ASIDownloadCache.m | 234 ++-- Vendor/ASIHTTPRequest/ASIFormDataRequest.h | 4 +- Vendor/ASIHTTPRequest/ASIFormDataRequest.m | 69 +- Vendor/ASIHTTPRequest/ASIHTTPRequest.h | 33 +- Vendor/ASIHTTPRequest/ASIHTTPRequest.m | 998 ++++++++++++------ Vendor/ASIHTTPRequest/ASIHTTPRequestConfig.h | 11 + Vendor/ASIHTTPRequest/ASIInputStream.m | 6 +- Vendor/ASIHTTPRequest/Reachability.h | 1 + Vendor/ASIHTTPRequest/version.txt | 1 + 14 files changed, 901 insertions(+), 511 deletions(-) create mode 100644 Vendor/ASIHTTPRequest/version.txt diff --git a/Vendor/ASIHTTPRequest/ASIAuthenticationDialog.m b/Vendor/ASIHTTPRequest/ASIAuthenticationDialog.m index 8d9b17f..47ea120 100644 --- a/Vendor/ASIHTTPRequest/ASIAuthenticationDialog.m +++ b/Vendor/ASIHTTPRequest/ASIAuthenticationDialog.m @@ -133,7 +133,7 @@ - (void)orientationChanged:(NSNotification *)notification { [self showTitle]; - UIDeviceOrientation o = [[UIApplication sharedApplication] statusBarOrientation]; + UIInterfaceOrientation o = (UIInterfaceOrientation)[[UIApplication sharedApplication] statusBarOrientation]; CGFloat angle = 0; switch (o) { case UIDeviceOrientationLandscapeLeft: angle = 90; break; diff --git a/Vendor/ASIHTTPRequest/ASICacheDelegate.h b/Vendor/ASIHTTPRequest/ASICacheDelegate.h index 452765b..060cda5 100644 --- a/Vendor/ASIHTTPRequest/ASICacheDelegate.h +++ b/Vendor/ASIHTTPRequest/ASICacheDelegate.h @@ -53,12 +53,19 @@ typedef enum _ASICacheStoragePolicy { @required -// Should return the cache policy that will be used when requests have their cache policy set to ASIDefaultCachePolicy +// Should return the cache policy that will be used when requests have their cache policy set to ASIUseDefaultCachePolicy - (ASICachePolicy)defaultCachePolicy; +// Returns the date a cached response should expire on. Pass a non-zero max age to specify a custom date. +- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; + +// Updates cached response headers with a new expiry date. Pass a non-zero max age to specify a custom date. +- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; + +// Looks at the request's cache policy and any cached headers to determine if the cache data is still valid - (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request; -// Should Remove cached data for a particular request +// Removes cached data for a particular request - (void)removeCachedDataForRequest:(ASIHTTPRequest *)request; // Should return YES if the cache considers its cached response current for the request @@ -69,6 +76,9 @@ typedef enum _ASICacheStoragePolicy { // When a non-zero maxAge is passed, it should be used as the expiry time for the cached response - (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; +// Removes cached data for a particular url +- (void)removeCachedDataForURL:(NSURL *)url; + // Should return an NSDictionary of cached headers for the passed URL, if it is stored in the cache - (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url; diff --git a/Vendor/ASIHTTPRequest/ASIDataCompressor.m b/Vendor/ASIHTTPRequest/ASIDataCompressor.m index 94d678e..d34687b 100644 --- a/Vendor/ASIHTTPRequest/ASIDataCompressor.m +++ b/Vendor/ASIHTTPRequest/ASIDataCompressor.m @@ -80,16 +80,15 @@ - (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSErro zStream.next_in = bytes; zStream.avail_in = (unsigned int)length; zStream.avail_out = 0; - NSError *theError = nil; - + NSInteger bytesProcessedAlready = zStream.total_out; - while (zStream.avail_in != 0) { + while (zStream.avail_out == 0) { if (zStream.total_out-bytesProcessedAlready >= [outputData length]) { [outputData increaseLengthBy:halfLength]; } - zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; + zStream.next_out = (Bytef*)[outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready)); status = deflate(&zStream, shouldFinish ? Z_FINISH : Z_NO_FLUSH); @@ -103,13 +102,6 @@ - (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSErro } } - if (theError) { - if (err) { - *err = theError; - } - return nil; - } - // Set real length [outputData setLength: zStream.total_out-bytesProcessedAlready]; return outputData; @@ -192,12 +184,12 @@ + (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinati } // Write the deflated data out to the destination file - [outputStream write:[outputData bytes] maxLength:[outputData length]]; + [outputStream write:(const uint8_t *)[outputData bytes] maxLength:[outputData length]]; // Make sure nothing went wrong if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { if (err) { - *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; } [compressor closeStream]; return NO; diff --git a/Vendor/ASIHTTPRequest/ASIDataDecompressor.m b/Vendor/ASIHTTPRequest/ASIDataDecompressor.m index 89797c5..25e7e55 100644 --- a/Vendor/ASIHTTPRequest/ASIDataDecompressor.m +++ b/Vendor/ASIHTTPRequest/ASIDataDecompressor.m @@ -77,7 +77,6 @@ - (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSEr zStream.next_in = bytes; zStream.avail_in = (unsigned int)length; zStream.avail_out = 0; - NSError *theError = nil; NSInteger bytesProcessedAlready = zStream.total_out; while (zStream.avail_in != 0) { @@ -86,7 +85,7 @@ - (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSEr [outputData increaseLengthBy:halfLength]; } - zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; + zStream.next_out = (Bytef*)[outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready)); status = inflate(&zStream, Z_NO_FLUSH); @@ -101,13 +100,6 @@ - (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSEr } } - if (theError) { - if (err) { - *err = theError; - } - return nil; - } - // Set real length [outputData setLength: zStream.total_out-bytesProcessedAlready]; return outputData; @@ -189,12 +181,12 @@ + (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destina } // Write the inflated data out to the destination file - [outputStream write:[outputData bytes] maxLength:[outputData length]]; + [outputStream write:(Bytef*)[outputData bytes] maxLength:[outputData length]]; // Make sure nothing went wrong if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { if (err) { - *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; } [decompressor closeStream]; return NO; diff --git a/Vendor/ASIHTTPRequest/ASIDownloadCache.h b/Vendor/ASIHTTPRequest/ASIDownloadCache.h index f317a48..a2df908 100644 --- a/Vendor/ASIHTTPRequest/ASIDownloadCache.h +++ b/Vendor/ASIHTTPRequest/ASIDownloadCache.h @@ -35,10 +35,9 @@ // A helper function that determines if the server has requested data should not be cached by looking at the request's response headers + (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request; -// A date formatter that can be used to construct an RFC 1123 date -// The returned formatter is safe to use on the calling thread -// Do not use this formatter for parsing dates because the format can vary slightly - use ASIHTTPRequest's dateFromRFC1123String: class method instead -+ (NSDateFormatter *)rfc1123DateFormatter; +// A list of file extensions that we know won't be readable by a webview when accessed locally +// If we're asking for a path to cache a particular url and it has one of these extensions, we change it to '.html' ++ (NSArray *)fileExtensionsToHandleAsHTML; @property (assign, nonatomic) ASICachePolicy defaultCachePolicy; @property (retain, nonatomic) NSString *storagePath; diff --git a/Vendor/ASIHTTPRequest/ASIDownloadCache.m b/Vendor/ASIHTTPRequest/ASIDownloadCache.m index c455094..93da36f 100644 --- a/Vendor/ASIHTTPRequest/ASIDownloadCache.m +++ b/Vendor/ASIHTTPRequest/ASIDownloadCache.m @@ -14,13 +14,24 @@ static NSString *sessionCacheFolder = @"SessionStore"; static NSString *permanentCacheFolder = @"PermanentStore"; +static NSArray *fileExtensionsToHandleAsHTML = nil; @interface ASIDownloadCache () + (NSString *)keyForURL:(NSURL *)url; +- (NSString *)pathToFile:(NSString *)file; @end @implementation ASIDownloadCache ++ (void)initialize +{ + if (self == [ASIDownloadCache class]) { + // Obviously this is not an exhaustive list, but hopefully these are the most commonly used and this will 'just work' for the widest range of people + // I imagine many web developers probably use url rewriting anyway + fileExtensionsToHandleAsHTML = [[NSArray alloc] initWithObjects:@"asp",@"aspx",@"jsp",@"php",@"rb",@"py",@"pl",@"cgi", nil]; + } +} + - (id)init { self = [super init]; @@ -33,9 +44,12 @@ - (id)init + (id)sharedCache { if (!sharedCache) { - sharedCache = [[self alloc] init]; - [sharedCache setStoragePath:[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"ASIHTTPRequestCache"]]; - + @synchronized(self) { + if (!sharedCache) { + sharedCache = [[self alloc] init]; + [sharedCache setStoragePath:[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"ASIHTTPRequestCache"]]; + } + } } return sharedCache; } @@ -84,15 +98,42 @@ - (void)setStoragePath:(NSString *)path [[self accessLock] unlock]; } +- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge +{ + NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request]; + NSMutableDictionary *cachedHeaders = [NSMutableDictionary dictionaryWithContentsOfFile:headerPath]; + if (!cachedHeaders) { + return; + } + NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge]; + if (!expires) { + return; + } + [cachedHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"]; + [cachedHeaders writeToFile:headerPath atomically:NO]; +} + +- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge +{ + return [ASIHTTPRequest expiryDateForRequest:request maxAge:maxAge]; +} + - (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge { [[self accessLock] lock]; - - if ([request error] || ![request responseHeaders] || ([request responseStatusCode] != 200) || ([request cachePolicy] & ASIDoNotWriteToCacheCachePolicy)) { + + if ([request error] || ![request responseHeaders] || ([request cachePolicy] & ASIDoNotWriteToCacheCachePolicy)) { [[self accessLock] unlock]; return; } - + + // We only cache 200/OK or redirect reponses (redirect responses are cached so the cache works better with no internet connection) + int responseCode = [request responseStatusCode]; + if (responseCode != 200 && responseCode != 301 && responseCode != 302 && responseCode != 303 && responseCode != 307) { + [[self accessLock] unlock]; + return; + } + if ([self shouldRespectCacheControlHeaders] && ![[self class] serverAllowsResponseCachingForRequest:request]) { [[self accessLock] unlock]; return; @@ -100,24 +141,42 @@ - (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request]; NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request]; - + NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]]; if ([request isResponseCompressed]) { [responseHeaders removeObjectForKey:@"Content-Encoding"]; } - if (maxAge != 0) { - [responseHeaders removeObjectForKey:@"Expires"]; - [responseHeaders setObject:[NSString stringWithFormat:@"max-age=%i",(int)maxAge] forKey:@"Cache-Control"]; + + // Create a special 'X-ASIHTTPRequest-Expires' header + // This is what we use for deciding if cached data is current, rather than parsing the expires / max-age headers individually each time + // We store this as a timestamp to make reading it easier as NSDateFormatter is quite expensive + + NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge]; + if (expires) { + [responseHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"]; + } + + // Store the response code in a custom header so we can reuse it later + + // We'll change 304/Not Modified to 200/OK because this is likely to be us updating the cached headers with a conditional GET + int statusCode = [request responseStatusCode]; + if (statusCode == 304) { + statusCode = 200; } - // We use this special key to help expire the request when we get a max-age header - [responseHeaders setObject:[[[self class] rfc1123DateFormatter] stringFromDate:[NSDate date]] forKey:@"X-ASIHTTPRequest-Fetch-date"]; + [responseHeaders setObject:[NSNumber numberWithInt:statusCode] forKey:@"X-ASIHTTPRequest-Response-Status-Code"]; + [responseHeaders writeToFile:headerPath atomically:NO]; - + if ([request responseData]) { [[request responseData] writeToFile:dataPath atomically:NO]; - } else if ([request downloadDestinationPath] && ![[request downloadDestinationPath] isEqualToString:dataPath]) { + } else if ([request downloadDestinationPath] && ![[request downloadDestinationPath] isEqualToString:dataPath]) { NSError *error = nil; - [[[[NSFileManager alloc] init] autorelease] copyItemAtPath:[request downloadDestinationPath] toPath:dataPath error:&error]; + NSFileManager* manager = [[NSFileManager alloc] init]; + if ([manager fileExistsAtPath:dataPath]) { + [manager removeItemAtPath:dataPath error:&error]; + } + [manager copyItemAtPath:[request downloadDestinationPath] toPath:dataPath error:&error]; + [manager release]; } [[self accessLock] unlock]; } @@ -142,38 +201,29 @@ - (NSData *)cachedResponseDataForURL:(NSURL *)url - (NSString *)pathToCachedResponseDataForURL:(NSURL *)url { - [[self accessLock] lock]; - if (![self storagePath]) { - [[self accessLock] unlock]; - return nil; - } // Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view NSString *extension = [[url path] pathExtension]; - if (![extension length]) { + + // If the url doesn't have an extension, we'll add one so a webview can read it when locally cached + // If the url has the extension of a common web scripting language, we'll change the extension on the cached path to html for the same reason + if (![extension length] || [[[self class] fileExtensionsToHandleAsHTML] containsObject:[extension lowercaseString]]) { extension = @"html"; } + return [self pathToFile:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]]; +} - NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; - - // Look in the session store - NSString *path = [[self storagePath] stringByAppendingPathComponent:sessionCacheFolder]; - NSString *dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]]; - if ([fileManager fileExistsAtPath:dataPath]) { - [[self accessLock] unlock]; - return dataPath; - } - // Look in the permanent store - path = [[self storagePath] stringByAppendingPathComponent:permanentCacheFolder]; - dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]]; - if ([fileManager fileExistsAtPath:dataPath]) { - [[self accessLock] unlock]; - return dataPath; - } - [[self accessLock] unlock]; - return nil; ++ (NSArray *)fileExtensionsToHandleAsHTML +{ + return fileExtensionsToHandleAsHTML; } + - (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url +{ + return [self pathToFile:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"cachedheaders"]]; +} + +- (NSString *)pathToFile:(NSString *)file { [[self accessLock] lock]; if (![self storagePath]) { @@ -184,15 +234,13 @@ - (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; // Look in the session store - NSString *path = [[self storagePath] stringByAppendingPathComponent:sessionCacheFolder]; - NSString *dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"cachedheaders"]]; + NSString *dataPath = [[[self storagePath] stringByAppendingPathComponent:sessionCacheFolder] stringByAppendingPathComponent:file]; if ([fileManager fileExistsAtPath:dataPath]) { [[self accessLock] unlock]; return dataPath; } // Look in the permanent store - path = [[self storagePath] stringByAppendingPathComponent:permanentCacheFolder]; - dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"cachedheaders"]]; + dataPath = [[[self storagePath] stringByAppendingPathComponent:permanentCacheFolder] stringByAppendingPathComponent:file]; if ([fileManager fileExistsAtPath:dataPath]) { [[self accessLock] unlock]; return dataPath; @@ -201,6 +249,7 @@ - (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url return nil; } + - (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request { [[self accessLock] lock]; @@ -213,7 +262,10 @@ - (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request // Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view NSString *extension = [[[request url] path] pathExtension]; - if (![extension length]) { + + // If the url doesn't have an extension, we'll add one so a webview can read it when locally cached + // If the url has the extension of a common web scripting language, we'll change the extension on the cached path to html for the same reason + if (![extension length] || [[[self class] fileExtensionsToHandleAsHTML] containsObject:[extension lowercaseString]]) { extension = @"html"; } path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:extension]]; @@ -234,32 +286,32 @@ - (NSString *)pathToStoreCachedResponseHeadersForRequest:(ASIHTTPRequest *)reque return path; } - -- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request +- (void)removeCachedDataForURL:(NSURL *)url { [[self accessLock] lock]; if (![self storagePath]) { [[self accessLock] unlock]; return; } + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; - NSString *cachedHeadersPath = [self pathToCachedResponseHeadersForURL:[request url]]; - if (!cachedHeadersPath) { - [[self accessLock] unlock]; - return; - } - NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]]; - if (!dataPath) { - [[self accessLock] unlock]; - return; + NSString *path = [self pathToCachedResponseHeadersForURL:url]; + if (path) { + [fileManager removeItemAtPath:path error:NULL]; } - NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; - [fileManager removeItemAtPath:cachedHeadersPath error:NULL]; - [fileManager removeItemAtPath:dataPath error:NULL]; + path = [self pathToCachedResponseDataForURL:url]; + if (path) { + [fileManager removeItemAtPath:path error:NULL]; + } [[self accessLock] unlock]; } +- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request +{ + [self removeCachedDataForURL:[request url]]; +} + - (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request { [[self accessLock] lock]; @@ -278,14 +330,15 @@ - (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request return NO; } - // If we already have response headers for this request, check to see if the new content is different - if ([request responseHeaders]) { + // New content is not different + if ([request responseStatusCode] == 304) { + [[self accessLock] unlock]; + return YES; + } - // New content is not different - if ([request responseStatusCode] == 304) { - [[self accessLock] unlock]; - return YES; - } + // If we already have response headers for this request, check to see if the new content is different + // We check [request complete] so that we don't end up comparing response headers from a redirection with these + if ([request responseHeaders] && [request complete]) { // If the Etag or Last-Modified date are different from the one we have, we'll have to fetch this resource again NSArray *headersToCompare = [NSArray arrayWithObjects:@"Etag",@"Last-Modified",nil]; @@ -299,33 +352,10 @@ - (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request if ([self shouldRespectCacheControlHeaders]) { - // Look for a max-age header - NSString *cacheControl = [[cachedHeaders objectForKey:@"Cache-Control"] lowercaseString]; - if (cacheControl) { - NSScanner *scanner = [NSScanner scannerWithString:cacheControl]; - [scanner scanUpToString:@"max-age" intoString:NULL]; - if ([scanner scanString:@"max-age" intoString:NULL]) { - [scanner scanString:@"=" intoString:NULL]; - NSTimeInterval maxAge = 0; - [scanner scanDouble:&maxAge]; - - NSDate *fetchDate = [ASIHTTPRequest dateFromRFC1123String:[cachedHeaders objectForKey:@"X-ASIHTTPRequest-Fetch-date"]]; - NSDate *expiryDate = [[[NSDate alloc] initWithTimeInterval:maxAge sinceDate:fetchDate] autorelease]; - - if ([expiryDate timeIntervalSinceNow] >= 0) { - [[self accessLock] unlock]; - return YES; - } - // RFC 2612 says max-age must override any Expires header - [[self accessLock] unlock]; - return NO; - } - } - - // Look for an Expires header to see if the content is out of date - NSString *expires = [cachedHeaders objectForKey:@"Expires"]; + // Look for X-ASIHTTPRequest-Expires header to see if the content is out of date + NSNumber *expires = [cachedHeaders objectForKey:@"X-ASIHTTPRequest-Expires"]; if (expires) { - if ([[ASIHTTPRequest dateFromRFC1123String:expires] timeIntervalSinceNow] >= 0) { + if ([[NSDate dateWithTimeIntervalSince1970:[expires doubleValue]] timeIntervalSinceNow] >= 0) { [[self accessLock] unlock]; return YES; } @@ -411,35 +441,25 @@ + (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request return YES; } -// Borrowed from: http://stackoverflow.com/questions/652300/using-md5-hash-on-a-string-in-cocoa + (NSString *)keyForURL:(NSURL *)url { NSString *urlString = [url absoluteString]; + if ([urlString length] == 0) { + return nil; + } + // Strip trailing slashes so http://allseeing-i.com/ASIHTTPRequest/ is cached the same as http://allseeing-i.com/ASIHTTPRequest if ([[urlString substringFromIndex:[urlString length]-1] isEqualToString:@"/"]) { urlString = [urlString substringToIndex:[urlString length]-1]; } + + // Borrowed from: http://stackoverflow.com/questions/652300/using-md5-hash-on-a-string-in-cocoa const char *cStr = [urlString UTF8String]; unsigned char result[16]; CC_MD5(cStr, (CC_LONG)strlen(cStr), result); return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],result[8], result[9], result[10], result[11],result[12], result[13], result[14], result[15]]; } -+ (NSDateFormatter *)rfc1123DateFormatter -{ - NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary]; - NSDateFormatter *dateFormatter = [threadDict objectForKey:@"ASIDownloadCacheDateFormatter"]; - if (dateFormatter == nil) { - dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; - [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]]; - [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; - [dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss 'GMT'"]; - [threadDict setObject:dateFormatter forKey:@"ASIDownloadCacheDateFormatter"]; - } - return dateFormatter; -} - - - (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request { // Ensure the request is allowed to read from the cache diff --git a/Vendor/ASIHTTPRequest/ASIFormDataRequest.h b/Vendor/ASIHTTPRequest/ASIFormDataRequest.h index e206fd2..670995f 100644 --- a/Vendor/ASIHTTPRequest/ASIFormDataRequest.h +++ b/Vendor/ASIHTTPRequest/ASIFormDataRequest.h @@ -50,13 +50,13 @@ typedef enum _ASIPostFormat { - (void)addFile:(NSString *)filePath forKey:(NSString *)key; // Same as above, but you can specify the content-type and file name -- (void)addFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; +- (void)addFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; // Add the contents of a local file to the request, clearing any others with the same key - (void)setFile:(NSString *)filePath forKey:(NSString *)key; // Same as above, but you can specify the content-type and file name -- (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; +- (void)setFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; // Add the contents of an NSData object to the request - (void)addData:(NSData *)data forKey:(NSString *)key; diff --git a/Vendor/ASIHTTPRequest/ASIFormDataRequest.m b/Vendor/ASIHTTPRequest/ASIFormDataRequest.m index b3ea8ed..2d812a0 100644 --- a/Vendor/ASIHTTPRequest/ASIFormDataRequest.m +++ b/Vendor/ASIHTTPRequest/ASIFormDataRequest.m @@ -30,7 +30,7 @@ @implementation ASIFormDataRequest #pragma mark utilities - (NSString*)encodeURL:(NSString *)string { - NSString *newString = NSMakeCollectable([(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding([self stringEncoding])) autorelease]); + NSString *newString = [NSMakeCollectable(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding([self stringEncoding]))) autorelease]; if (newString) { return newString; } @@ -49,6 +49,7 @@ - (id)initWithURL:(NSURL *)newURL self = [super initWithURL:newURL]; [self setPostFormat:ASIURLEncodedPostFormat]; [self setStringEncoding:NSUTF8StringEncoding]; + [self setRequestMethod:@"POST"]; return self; } @@ -67,10 +68,16 @@ - (void)dealloc - (void)addPostValue:(id )value forKey:(NSString *)key { + if (!key) { + return; + } if (![self postData]) { [self setPostData:[NSMutableArray array]]; } - [[self postData] addObject:[NSDictionary dictionaryWithObjectsAndKeys:[value description],@"value",key,@"key",nil]]; + NSMutableDictionary *keyValuePair = [NSMutableDictionary dictionaryWithCapacity:2]; + [keyValuePair setValue:key forKey:@"key"]; + [keyValuePair setValue:[value description] forKey:@"value"]; + [[self postData] addObject:keyValuePair]; } - (void)setPostValue:(id )value forKey:(NSString *)key @@ -93,36 +100,26 @@ - (void)addFile:(NSString *)filePath forKey:(NSString *)key [self addFile:filePath withFileName:nil andContentType:nil forKey:key]; } -- (void)addFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key +- (void)addFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key { - if (![self fileData]) { - [self setFileData:[NSMutableArray array]]; + BOOL isDirectory = NO; + BOOL fileExists = [[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:filePath isDirectory:&isDirectory]; + if (!fileExists || isDirectory) { + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@",filePath],NSLocalizedDescriptionKey,nil]]]; } - - // If data is a path to a local file - if ([data isKindOfClass:[NSString class]]) { - BOOL isDirectory = NO; - BOOL fileExists = [[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:(NSString *)data isDirectory:&isDirectory]; - if (!fileExists || isDirectory) { - [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@",data],NSLocalizedDescriptionKey,nil]]]; - } - // If the caller didn't specify a custom file name, we'll use the file name of the file we were passed - if (!fileName) { - fileName = [(NSString *)data lastPathComponent]; - } + // If the caller didn't specify a custom file name, we'll use the file name of the file we were passed + if (!fileName) { + fileName = [filePath lastPathComponent]; + } - // If we were given the path to a file, and the user didn't specify a mime type, we can detect it from the file extension - if (!contentType) { - contentType = [ASIHTTPRequest mimeTypeForFileAtPath:data]; - } + // If we were given the path to a file, and the user didn't specify a mime type, we can detect it from the file extension + if (!contentType) { + contentType = [ASIHTTPRequest mimeTypeForFileAtPath:filePath]; } - - NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", key, @"key", nil]; - [[self fileData] addObject:fileInfo]; + [self addData:filePath withFileName:fileName andContentType:contentType forKey:key]; } - - (void)setFile:(NSString *)filePath forKey:(NSString *)key { [self setFile:filePath withFileName:nil andContentType:nil forKey:key]; @@ -155,8 +152,13 @@ - (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSStr if (!contentType) { contentType = @"application/octet-stream"; } - - NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", key, @"key", nil]; + + NSMutableDictionary *fileInfo = [NSMutableDictionary dictionaryWithCapacity:4]; + [fileInfo setValue:key forKey:@"key"]; + [fileInfo setValue:fileName forKey:@"fileName"]; + [fileInfo setValue:contentType forKey:@"contentType"]; + [fileInfo setValue:data forKey:@"data"]; + [[self fileData] addObject:fileInfo]; } @@ -206,7 +208,7 @@ - (void)buildPostBody [super buildPostBody]; #if DEBUG_FORM_DATA_REQUEST - NSLog(@"%@",[self debugBodyString]); + ASI_DEBUG_LOG(@"%@",[self debugBodyString]); [self setDebugBodyString:nil]; #endif } @@ -220,8 +222,11 @@ - (void)buildMultipartFormDataPostBody NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding])); - // Set your own boundary string only if really obsessive. We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does. - NSString *stringBoundary = @"0xKhTmLbOuNdArY"; + // We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does. + CFUUIDRef uuid = CFUUIDCreate(nil); + NSString *uuidString = [(NSString*)CFUUIDCreateString(nil, uuid) autorelease]; + CFRelease(uuid); + NSString *stringBoundary = [NSString stringWithFormat:@"0xKhTmLbOuNdArY-%@",uuidString]; [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]]; @@ -328,7 +333,9 @@ - (void)appendPostDataFromFile:(NSString *)file - (void)addToDebugBody:(NSString *)string { - [self setDebugBodyString:[[self debugBodyString] stringByAppendingString:string]]; + if (string) { + [self setDebugBodyString:[[self debugBodyString] stringByAppendingString:string]]; + } } #endif diff --git a/Vendor/ASIHTTPRequest/ASIHTTPRequest.h b/Vendor/ASIHTTPRequest/ASIHTTPRequest.h index 0745c20..ecfe05c 100644 --- a/Vendor/ASIHTTPRequest/ASIHTTPRequest.h +++ b/Vendor/ASIHTTPRequest/ASIHTTPRequest.h @@ -2,7 +2,7 @@ // ASIHTTPRequest.h // // Created by Ben Copsey on 04/10/2007. -// Copyright 2007-2010 All-Seeing Interactive. All rights reserved. +// Copyright 2007-2011 All-Seeing Interactive. All rights reserved. // // A guide to the main features is available at: // http://allseeing-i.com/ASIHTTPRequest @@ -92,7 +92,7 @@ typedef void (^ASIDataBlock)(NSData *data); // Temporarily stores the url we are about to redirect to. Will be nil again when we do redirect NSURL *redirectURL; - // The delegate, you need to manage setting and talking to your delegate in your subclasses + // The delegate - will be notified of various changes in state via the ASIHTTPRequestDelegate protocol id delegate; // Another delegate that is also notified of request status changes and progress updates @@ -100,7 +100,7 @@ typedef void (^ASIDataBlock)(NSData *data); // NOTE: WILL BE RETAINED BY THE REQUEST id queue; - // HTTP method to use (GET / POST / PUT / DELETE / HEAD). Defaults to GET + // HTTP method to use (eg: GET / POST / PUT / DELETE / HEAD etc). Defaults to GET NSString *requestMethod; // Request body - only used when the whole body is stored in memory (shouldStreamPostDataFromDisk is false) @@ -192,6 +192,9 @@ typedef void (^ASIDataBlock)(NSData *data); NSString *username; NSString *password; + // User-Agent for this request + NSString *userAgent; + // Domain used for NTLM authentication NSString *domain; @@ -226,6 +229,7 @@ typedef void (^ASIDataBlock)(NSData *data); int authenticationRetryCount; // Authentication scheme (Basic, Digest, NTLM) + // If you are using Basic authentication and want to force ASIHTTPRequest to send an authorization header without waiting for a 401, you must set this to (NSString *)kCFHTTPAuthenticationSchemeBasic NSString *authenticationScheme; // Realm for authentication when credentials are required @@ -291,7 +295,7 @@ typedef void (^ASIDataBlock)(NSData *data); SEL didReceiveResponseHeadersSelector; // Called on the delegate (if implemented) when the request receives a Location header and shouldRedirect is YES - // The delegate can then change the url if needed, and can restart the request by calling [request resume], or simply cancel it + // The delegate can then change the url if needed, and can restart the request by calling [request redirectToURL:], or simply cancel it SEL willRedirectSelector; // Called on the delegate (if implemented) when the request completes successfully. Default is requestFinished: @@ -343,8 +347,9 @@ typedef void (^ASIDataBlock)(NSData *data); // Tells ASIHTTPRequest not to delete partial downloads, and allows it to use an existing file to resume a download. Defaults to NO. BOOL allowResumeForFileDownloads; - // Custom user information associated with the request + // Custom user information associated with the request (not sent to the server) NSDictionary *userInfo; + NSInteger tag; // Use HTTP 1.0 rather than 1.1 (defaults to false) BOOL useHTTPVersionOne; @@ -384,6 +389,7 @@ typedef void (^ASIDataBlock)(NSData *data); // Set to NO to only present credentials when explicitly asked for them // This only affects credentials stored in the session cache when useSessionPersistence is YES. Credentials from the keychain are never presented unless the server asks for them // Default is YES + // For requests using Basic authentication, set authenticationScheme to (NSString *)kCFHTTPAuthenticationSchemeBasic, and credentials can be sent on the very first request when shouldPresentCredentialsBeforeChallenge is YES BOOL shouldPresentCredentialsBeforeChallenge; // YES when the request hasn't finished yet. Will still be YES even if the request isn't doing anything (eg it's waiting for delegate authentication). READ-ONLY @@ -399,7 +405,10 @@ typedef void (^ASIDataBlock)(NSData *data); // The number of times this request has retried (when numberOfTimesToRetryOnTimeout > 0) int retryCount; - + + // Temporarily set to YES when a closed connection forces a retry (internally, this stops ASIHTTPRequest cleaning up a temporary post body) + BOOL willRetryRequest; + // When YES, requests will keep the connection to the server alive for a while to allow subsequent requests to re-use it for a substantial speed-boost // Persistent connections will not be used if the server explicitly closes the connection // Default is YES @@ -439,7 +448,6 @@ typedef void (^ASIDataBlock)(NSData *data); // This timer checks up on the request every 0.25 seconds, and updates progress NSTimer *statusTimer; - // The download cache that will be used for this request (use [ASIHTTPRequest setDefaultCache:cache] to configure a default cache id downloadCache; @@ -460,7 +468,6 @@ typedef void (^ASIDataBlock)(NSData *data); BOOL shouldContinueWhenAppEntersBackground; UIBackgroundTaskIdentifier backgroundTask; #endif - // When downloading a gzipped response, the request will use this helper object to inflate the response ASIDataDecompressor *dataDecompressor; @@ -794,6 +801,7 @@ typedef void (^ASIDataBlock)(NSData *data); // Will be used as a user agent if requests do not specify a custom user agent // Is only used when you have specified a Bundle Display Name (CFDisplayBundleName) or Bundle Name (CFBundleName) in your plist + (NSString *)defaultUserAgentString; ++ (void)setDefaultUserAgentString:(NSString *)agent; #pragma mark mime-type detection @@ -865,6 +873,11 @@ typedef void (^ASIDataBlock)(NSData *data); // And also by ASIS3Request + (NSString *)base64forData:(NSData *)theData; +// Returns the expiration date for the request. +// Calculated from the Expires response header property, unless maxAge is non-zero or +// there exists a non-zero max-age property in the Cache-Control response header. ++ (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge; + // Returns a date from a string in RFC1123 format + (NSDate *)dateFromRFC1123String:(NSString *)string; @@ -890,6 +903,7 @@ typedef void (^ASIDataBlock)(NSData *data); @property (retain) NSString *username; @property (retain) NSString *password; +@property (retain) NSString *userAgent; @property (retain) NSString *domain; @property (retain) NSString *proxyUsername; @@ -932,7 +946,7 @@ typedef void (^ASIDataBlock)(NSData *data); @property (retain,readonly) NSString *responseStatusMessage; @property (retain) NSMutableData *rawResponseData; @property (assign) NSTimeInterval timeOutSeconds; -@property (retain) NSString *requestMethod; +@property (retain, nonatomic) NSString *requestMethod; @property (retain) NSMutableData *postBody; @property (assign) unsigned long long contentLength; @property (assign) unsigned long long postLength; @@ -947,6 +961,7 @@ typedef void (^ASIDataBlock)(NSData *data); @property (assign) BOOL allowCompressedResponse; @property (assign) BOOL allowResumeForFileDownloads; @property (retain) NSDictionary *userInfo; +@property (assign) NSInteger tag; @property (retain) NSString *postBodyFilePath; @property (assign) BOOL shouldStreamPostDataFromDisk; @property (assign) BOOL didCreateTemporaryPostDataFile; diff --git a/Vendor/ASIHTTPRequest/ASIHTTPRequest.m b/Vendor/ASIHTTPRequest/ASIHTTPRequest.m index deff3a8..8b239ed 100644 --- a/Vendor/ASIHTTPRequest/ASIHTTPRequest.m +++ b/Vendor/ASIHTTPRequest/ASIHTTPRequest.m @@ -2,7 +2,7 @@ // ASIHTTPRequest.m // // Created by Ben Copsey on 04/10/2007. -// Copyright 2007-2010 All-Seeing Interactive. All rights reserved. +// Copyright 2007-2011 All-Seeing Interactive. All rights reserved. // // A guide to the main features is available at: // http://allseeing-i.com/ASIHTTPRequest @@ -24,7 +24,9 @@ #import "ASIDataCompressor.h" // Automatically set on build -NSString *ASIHTTPRequestVersion = @"v1.8-56 2011-02-06"; +NSString *ASIHTTPRequestVersion = @"v1.8.1-61 2011-09-19"; + +static NSString *defaultUserAgent = nil; NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; @@ -64,7 +66,6 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy static NSMutableArray *bandwidthUsageTracker = nil; static unsigned long averageBandwidthUsedPerSecond = 0; - // These are used for queuing persistent connections on the same connection // Incremented every time we specify we want a new connection @@ -104,7 +105,7 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy // When YES, bandwidth will be automatically throttled when using WWAN (3G/Edge/GPRS) // Wifi will not be throttled -static BOOL shouldThrottleBandwithForWWANOnly = NO; +static BOOL shouldThrottleBandwidthForWWANOnly = NO; #endif // Mediates access to the session cookies so requests @@ -122,19 +123,14 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy static id defaultCache = nil; - // Used for tracking when requests are using the network static unsigned int runningRequestCount = 0; - // You can use [ASIHTTPRequest setShouldUpdateNetworkActivityIndicator:NO] if you want to manage it yourself // Alternatively, override showNetworkActivityIndicator / hideNetworkActivityIndicator // By default this does nothing on Mac OS X, but again override the above methods for a different behaviour static BOOL shouldUpdateNetworkActivityIndicator = YES; - -//**Queue stuff**/ - // The thread all requests will run on // Hangs around forever, but will be blocked unless there are requests underway static NSThread *networkThread = nil; @@ -167,6 +163,8 @@ - (void)reportFinished; - (void)markAsFinished; - (void)performRedirect; - (BOOL)shouldTimeOut; +- (BOOL)willRedirect; +- (BOOL)willAskDelegateToConfirmRedirect; + (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease; + (void)hideNetworkActivityIndicatorAfterDelay; @@ -229,6 +227,7 @@ - (void)callBlock:(ASIBasicBlock)block; @property (retain) NSString *responseStatusMessage; @property (assign) BOOL inProgress; @property (assign) int retryCount; +@property (assign) BOOL willRetryRequest; @property (assign) BOOL connectionCanBeReused; @property (retain, nonatomic) NSMutableDictionary *connectionInfo; @property (retain, nonatomic) NSInputStream *readStream; @@ -393,6 +392,7 @@ - (void)dealloc [connectionInfo release]; [requestID release]; [dataDecompressor release]; + [userAgent release]; #if NS_BLOCKS_AVAILABLE [self releaseBlocksOnMainThread]; @@ -601,6 +601,27 @@ - (void)appendPostDataFromFile:(NSString *)file [stream close]; } +- (NSString *)requestMethod +{ + [[self cancelledLock] lock]; + NSString *m = requestMethod; + [[self cancelledLock] unlock]; + return m; +} + +- (void)setRequestMethod:(NSString *)newRequestMethod +{ + [[self cancelledLock] lock]; + if (requestMethod != newRequestMethod) { + [requestMethod release]; + requestMethod = [newRequestMethod retain]; + if ([requestMethod isEqualToString:@"POST"] || [requestMethod isEqualToString:@"PUT"] || [postBody length] || postBodyFilePath) { + [self setShouldAttemptPersistentConnection:NO]; + } + } + [[self cancelledLock] unlock]; +} + - (NSURL *)url { [[self cancelledLock] lock]; @@ -675,7 +696,7 @@ - (void)setQueue:(id)newQueue - (void)cancelOnRequestThread { #if DEBUG_REQUEST_STATUS - NSLog(@"Request cancelled: %@",self); + ASI_DEBUG_LOG(@"[STATUS] Request cancelled: %@",self); #endif [[self cancelledLock] lock]; @@ -765,7 +786,7 @@ - (NSData *)responseData - (void)startSynchronous { #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING - NSLog(@"Starting synchronous request %@",self); + ASI_DEBUG_LOG(@"[STATUS] Starting synchronous request %@",self); #endif [self setSynchronous:YES]; [self setRunLoopMode:ASIHTTPRequestRunLoopMode]; @@ -790,7 +811,7 @@ - (void)start - (void)startAsynchronous { #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING - NSLog(@"Starting asynchronous request %@",self); + ASI_DEBUG_LOG(@"[STATUS] Starting asynchronous request %@",self); #endif [sharedQueue addOperation:self]; } @@ -940,43 +961,65 @@ - (void)applyAuthorizationHeader { // Do we want to send credentials before we are asked for them? if (![self shouldPresentCredentialsBeforeChallenge]) { + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will not send credentials to the server until it asks for them",self); + #endif return; } - - // First, see if we have any credentials we can use in the session store + NSDictionary *credentials = nil; - if ([self useSessionPersistence]) { - credentials = [self findSessionAuthenticationCredentials]; - } - - - // Are any credentials set on this request that might be used for basic authentication? - if ([self username] && [self password] && ![self domain]) { - - // If we know this request should use Basic auth, we'll add an Authorization header with basic credentials - if ([[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) { + + // Do we already have an auth header? + if (![[self requestHeaders] objectForKey:@"Authorization"]) { + + // If we have basic authentication explicitly set and a username and password set on the request, add a basic auth header + if ([self username] && [self password] && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) { [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]]; - } - } - - if (credentials && ![[self requestHeaders] objectForKey:@"Authorization"]) { - - // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them - // (credentials for Digest and NTLM will always be stored like this) - if ([credentials objectForKey:@"Authentication"]) { - - // If we've already talked to this server and have valid credentials, let's apply them to the request - if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) { - [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; - } - - // If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication - // When this happens, we'll need to create the Authorization header ourselves + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ has a username and password set, and was manually configured to use BASIC. Will send credentials without waiting for an authentication challenge",self); + #endif + } else { - NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"]; - [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]]; + + // See if we have any cached credentials we can use in the session store + if ([self useSessionPersistence]) { + credentials = [self findSessionAuthenticationCredentials]; + + if (credentials) { + + // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them + // (credentials for Digest and NTLM will always be stored like this) + if ([credentials objectForKey:@"Authentication"]) { + + // If we've already talked to this server and have valid credentials, let's apply them to the request + if (CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) { + [self setAuthenticationScheme:[credentials objectForKey:@"AuthenticationScheme"]]; + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ found cached credentials (%@), will reuse without waiting for an authentication challenge",self,[credentials objectForKey:@"AuthenticationScheme"]); + #endif + } else { + [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Failed to apply cached credentials to request %@. These will be removed from the session store, and this request will wait for an authentication challenge",self); + #endif + } + + // If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication + // When this happens, we'll need to create the Authorization header ourselves + } else { + NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"]; + [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]]; + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ found cached BASIC credentials from a previous request. Will send credentials without waiting for an authentication challenge",self); + #endif + } + } + } } } + + // Apply proxy authentication credentials if ([self useSessionPersistence]) { credentials = [self findSessionProxyAuthenticationCredentials]; if (credentials) { @@ -1038,7 +1081,10 @@ - (void)buildRequestHeaders // Build and set the user agent string if the request does not already have a custom user agent specified if (![[self requestHeaders] objectForKey:@"User-Agent"]) { - NSString *userAgentString = [ASIHTTPRequest defaultUserAgentString]; + NSString *userAgentString = [self userAgent]; + if (!userAgentString) { + userAgentString = [ASIHTTPRequest defaultUserAgentString]; + } if (userAgentString) { [self addRequestHeader:@"User-Agent" value:userAgentString]; } @@ -1123,7 +1169,7 @@ - (void)startRequest } else { [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]]; } - [self setReadStream:[(NSInputStream *)CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]) autorelease]]; + [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]]; } else { // If we have a request body, we'll stream it from memory using our custom stream, so that we can measure bandwidth use and it can be bandwidth-throttled if necessary @@ -1133,10 +1179,10 @@ - (void)startRequest } else if ([self postBody]) { [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody] request:self]]; } - [self setReadStream:[(NSInputStream *)CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]) autorelease]]; + [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]]; } else { - [self setReadStream:[(NSInputStream *)CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request) autorelease]]; + [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request)) autorelease]]; } } @@ -1152,18 +1198,28 @@ - (void)startRequest // Handle SSL certificate settings // - if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) { - - NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1]; - + if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) { + // Tell CFNetwork not to validate SSL certificates if (![self validatesSecureCertificate]) { - [sslProperties setObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]; - } - + // see: http://iphonedevelopment.blogspot.com/2010/05/nsstream-tcp-and-ssl.html + + NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys: + [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates, + [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot, + [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain, + kCFNull,kCFStreamSSLPeerName, + nil]; + + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], + kCFStreamPropertySSLSettings, + (CFTypeRef)sslProperties); + } + // Tell CFNetwork to use a client certificate if (clientCertificateIdentity) { - + NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1]; + NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1]; // The first object in the array is our SecIdentityRef @@ -1173,10 +1229,12 @@ - (void)startRequest for (id cert in clientCertificates) { [certificates addObject:cert]; } + [sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates]; + + CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties); } - - CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties); + } // @@ -1238,15 +1296,22 @@ - (void)startRequest if (![[[self connectionInfo] objectForKey:@"host"] isEqualToString:[[self url] host]] || ![[[self connectionInfo] objectForKey:@"scheme"] isEqualToString:[[self url] scheme]] || [(NSNumber *)[[self connectionInfo] objectForKey:@"port"] intValue] != [[[self url] port] intValue]) { [self setConnectionInfo:nil]; - + // Check if we should have expired this connection } else if ([[[self connectionInfo] objectForKey:@"expires"] timeIntervalSinceNow] < 0) { #if DEBUG_PERSISTENT_CONNECTIONS - NSLog(@"Not re-using connection #%i because it has expired",[[[self connectionInfo] objectForKey:@"id"] intValue]); + ASI_DEBUG_LOG(@"[CONNECTION] Not re-using connection #%i because it has expired",[[[self connectionInfo] objectForKey:@"id"] intValue]); #endif [persistentConnectionsPool removeObject:[self connectionInfo]]; [self setConnectionInfo:nil]; - } + + } else if ([[self connectionInfo] objectForKey:@"request"] != nil) { + //Some other request reused this connection already - we'll have to create a new one + #if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"%@ - Not re-using connection #%i for request #%i because it is already used by request #%i",self,[[[self connectionInfo] objectForKey:@"id"] intValue],[[self requestID] intValue],[[[self connectionInfo] objectForKey:@"request"] intValue]); + #endif + [self setConnectionInfo:nil]; + } } @@ -1287,7 +1352,7 @@ - (void)startRequest CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue); #if DEBUG_PERSISTENT_CONNECTIONS - NSLog(@"Request #%@ will use connection #%i",[self requestID],[[[self connectionInfo] objectForKey:@"id"] intValue]); + ASI_DEBUG_LOG(@"[CONNECTION] Request #%@ will use connection #%i",[self requestID],[[[self connectionInfo] objectForKey:@"id"] intValue]); #endif @@ -1296,6 +1361,10 @@ - (void)startRequest CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]); + } else { + #if DEBUG_PERSISTENT_CONNECTIONS + ASI_DEBUG_LOG(@"[CONNECTION] Request %@ will not use a persistent connection",self); + #endif } [connectionsLock unlock]; @@ -1462,7 +1531,7 @@ - (void)checkRequestStatus [self setLastBytesSent:totalBytesSent]; // Find out how much data we've uploaded so far - [self setTotalBytesSent:[NSMakeCollectable([(NSNumber *)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease]) unsignedLongLongValue]]; + [self setTotalBytesSent:[[NSMakeCollectable(CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount)) autorelease] unsignedLongLongValue]]; if (totalBytesSent > lastBytesSent) { // We've uploaded more data, reset the timeout @@ -1471,7 +1540,7 @@ - (void)checkRequestStatus #if DEBUG_REQUEST_STATUS if ([self totalBytesSent] == [self postLength]) { - NSLog(@"Request %@ finished uploading data",self); + ASI_DEBUG_LOG(@"[STATUS] Request %@ finished uploading data",self); } #endif } @@ -1506,8 +1575,9 @@ - (void)cancelLoad [self setPostBodyReadStream:nil]; if ([self rawResponseData]) { - [self setRawResponseData:nil]; - + if (![self complete]) { + [self setRawResponseData:nil]; + } // If we were downloading to a file } else if ([self temporaryFileDownloadPath]) { [[self fileDownloadOutputStream] close]; @@ -1517,20 +1587,20 @@ - (void)cancelLoad [self setInflatedFileDownloadOutputStream:nil]; // If we haven't said we might want to resume, let's remove the temporary file too - if (![self allowResumeForFileDownloads]) { - [self removeTemporaryDownloadFile]; + if (![self complete]) { + if (![self allowResumeForFileDownloads]) { + [self removeTemporaryDownloadFile]; + } + [self removeTemporaryUncompressedDownloadFile]; } - [self removeTemporaryUncompressedDownloadFile]; } // Clean up any temporary file used to store request body for streaming - if (![self authenticationNeeded] && [self didCreateTemporaryPostDataFile]) { + if (![self authenticationNeeded] && ![self willRetryRequest] && [self didCreateTemporaryPostDataFile]) { [self removeTemporaryUploadFile]; [self removeTemporaryCompressedUploadFile]; [self setDidCreateTemporaryPostDataFile:NO]; } - - [self setResponseHeaders:nil]; } #pragma mark HEAD request @@ -1857,14 +1927,14 @@ - (void)requestStarted if (delegate && [delegate respondsToSelector:didStartSelector]) { [delegate performSelector:didStartSelector withObject:self]; } - if (queue && [queue respondsToSelector:@selector(requestStarted:)]) { - [queue performSelector:@selector(requestStarted:) withObject:self]; - } #if NS_BLOCKS_AVAILABLE if(startedBlock){ startedBlock(); } #endif + if (queue && [queue respondsToSelector:@selector(requestStarted:)]) { + [queue performSelector:@selector(requestStarted:) withObject:self]; + } } /* ALWAYS CALLED ON MAIN THREAD! */ @@ -1877,6 +1947,7 @@ - (void)requestRedirected if([[self delegate] respondsToSelector:@selector(requestRedirected:)]){ [[self delegate] performSelector:@selector(requestRedirected:) withObject:self]; } + #if NS_BLOCKS_AVAILABLE if(requestRedirectedBlock){ requestRedirectedBlock(); @@ -1895,15 +1966,16 @@ - (void)requestReceivedResponseHeaders:(NSMutableDictionary *)newResponseHeaders if (delegate && [delegate respondsToSelector:didReceiveResponseHeadersSelector]) { [delegate performSelector:didReceiveResponseHeadersSelector withObject:self withObject:newResponseHeaders]; } - if (queue && [queue respondsToSelector:@selector(request:didReceiveResponseHeaders:)]) { - [queue performSelector:@selector(request:didReceiveResponseHeaders:) withObject:self withObject:newResponseHeaders]; - } - + #if NS_BLOCKS_AVAILABLE if(headersReceivedBlock){ headersReceivedBlock(newResponseHeaders); } #endif + + if (queue && [queue respondsToSelector:@selector(request:didReceiveResponseHeaders:)]) { + [queue performSelector:@selector(request:didReceiveResponseHeaders:) withObject:self withObject:newResponseHeaders]; + } } /* ALWAYS CALLED ON MAIN THREAD! */ @@ -1925,7 +1997,7 @@ - (void)requestWillRedirectToURL:(NSURL *)newURL - (void)requestFinished { #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING - NSLog(@"Request finished: %@",self); + ASI_DEBUG_LOG(@"[STATUS] Request finished: %@",self); #endif if ([self error] || [self mainRequest]) { return; @@ -1943,14 +2015,16 @@ - (void)reportFinished if (delegate && [delegate respondsToSelector:didFinishSelector]) { [delegate performSelector:didFinishSelector withObject:self]; } - if (queue && [queue respondsToSelector:@selector(requestFinished:)]) { - [queue performSelector:@selector(requestFinished:) withObject:self]; - } -#if NS_BLOCKS_AVAILABLE + + #if NS_BLOCKS_AVAILABLE if(completionBlock){ completionBlock(); } -#endif + #endif + + if (queue && [queue respondsToSelector:@selector(requestFinished:)]) { + [queue performSelector:@selector(requestFinished:) withObject:self]; + } } /* ALWAYS CALLED ON MAIN THREAD! */ @@ -1959,14 +2033,16 @@ - (void)reportFailure if (delegate && [delegate respondsToSelector:didFailSelector]) { [delegate performSelector:didFailSelector withObject:self]; } - if (queue && [queue respondsToSelector:@selector(requestFailed:)]) { - [queue performSelector:@selector(requestFailed:) withObject:self]; - } + #if NS_BLOCKS_AVAILABLE if(failureBlock){ failureBlock(); } #endif + + if (queue && [queue respondsToSelector:@selector(requestFailed:)]) { + [queue performSelector:@selector(requestFailed:) withObject:self]; + } } /* ALWAYS CALLED ON MAIN THREAD! */ @@ -1988,7 +2064,7 @@ - (void)passOnReceivedData:(NSData *)data - (void)failWithError:(NSError *)theError { #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING - NSLog(@"Request %@: %@",self,(theError == ASIRequestCancelledError ? @"Cancelled" : @"Failed")); + ASI_DEBUG_LOG(@"[STATUS] Request %@: %@",self,(theError == ASIRequestCancelledError ? @"Cancelled" : @"Failed")); #endif [self setComplete:YES]; @@ -1996,7 +2072,7 @@ - (void)failWithError:(NSError *)theError if (theError && [theError code] != ASIAuthenticationErrorType && [theError code] != ASITooMuchRedirectionErrorType) { [connectionsLock lock]; #if DEBUG_PERSISTENT_CONNECTIONS - NSLog(@"Request #%@ failed and will invalidate connection #%@",[self requestID],[[self connectionInfo] objectForKey:@"id"]); + ASI_DEBUG_LOG(@"[CONNECTION] Request #%@ failed and will invalidate connection #%@",[self requestID],[[self connectionInfo] objectForKey:@"id"]); #endif [[self connectionInfo] removeObjectForKey:@"request"]; [persistentConnectionsPool removeObject:[self connectionInfo]]; @@ -2064,23 +2140,22 @@ - (void)readResponseHeaders #if DEBUG_REQUEST_STATUS if ([self totalBytesSent] == [self postLength]) { - NSLog(@"Request %@ received response headers",self); + ASI_DEBUG_LOG(@"[STATUS] Request %@ received response headers",self); } #endif - CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(message); - [self setResponseHeaders:(NSDictionary *)headerFields]; - - CFRelease(headerFields); - + [self setResponseHeaders:[NSMakeCollectable(CFHTTPMessageCopyAllHeaderFields(message)) autorelease]]; [self setResponseStatusCode:(int)CFHTTPMessageGetResponseStatusCode(message)]; - [self setResponseStatusMessage:[(NSString *)CFHTTPMessageCopyResponseStatusLine(message) autorelease]]; - + [self setResponseStatusMessage:[NSMakeCollectable(CFHTTPMessageCopyResponseStatusLine(message)) autorelease]]; + if ([self downloadCache] && ([[self downloadCache] canUseCachedDataForRequest:self])) { + + // Update the expiry date + [[self downloadCache] updateExpiryForRequest:self maxAge:[self secondsToCache]]; + // Read the response from the cache [self useDataFromCache]; - // Update the response headers (this will usually move the expiry date into the future) - [[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]]; + CFRelease(message); return; } @@ -2090,13 +2165,23 @@ - (void)readResponseHeaders [self setAuthenticationNeeded:ASIHTTPAuthenticationNeeded]; } else if ([self responseStatusCode] == 407) { [self setAuthenticationNeeded:ASIProxyAuthenticationNeeded]; + } else { + #if DEBUG_HTTP_AUTHENTICATION + if ([self authenticationScheme]) { + ASI_DEBUG_LOG(@"[AUTH] Request %@ has passed %@ authentication",self,[self authenticationScheme]); + } + #endif } // Authentication succeeded, or no authentication was required if (![self authenticationNeeded]) { // Did we get here without an authentication challenge? (which can happen when shouldPresentCredentialsBeforeChallenge is YES and basic auth was successful) - if (!requestAuthentication && [self username] && [self password] && [self useSessionPersistence]) { + if (!requestAuthentication && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && [self username] && [self password] && [self useSessionPersistence]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ passed BASIC authentication, and will save credentials in the session store for future use",self); + #endif NSMutableDictionary *newCredentials = [NSMutableDictionary dictionaryWithCapacity:2]; [newCredentials setObject:[self username] forKey:(NSString *)kCFHTTPAuthenticationUsername]; @@ -2131,57 +2216,7 @@ - (void)readResponseHeaders } // Do we need to redirect? - // Note that ASIHTTPRequest does not currently support 305 Use Proxy - if ([self shouldRedirect] && [responseHeaders valueForKey:@"Location"]) { - if (([self responseStatusCode] > 300 && [self responseStatusCode] < 304) || [self responseStatusCode] == 307) { - - [self performSelectorOnMainThread:@selector(requestRedirected) withObject:nil waitUntilDone:[NSThread isMainThread]]; - - // By default, we redirect 301 and 302 response codes as GET requests - // According to RFC 2616 this is wrong, but this is what most browsers do, so it's probably what you're expecting to happen - // See also: - // http://allseeing-i.lighthouseapp.com/projects/27881/tickets/27-302-redirection-issue - - if ([self responseStatusCode] != 307 && (![self shouldUseRFC2616RedirectBehaviour] || [self responseStatusCode] == 303)) { - [self setRequestMethod:@"GET"]; - [self setPostBody:nil]; - [self setPostLength:0]; - - // Perhaps there are other headers we should be preserving, but it's hard to know what we need to keep and what to throw away. - NSString *userAgentHeader = [[self requestHeaders] objectForKey:@"User-Agent"]; - NSString *acceptHeader = [[self requestHeaders] objectForKey:@"Accept"]; - [self setRequestHeaders:nil]; - if (userAgentHeader) { - [self addRequestHeader:@"User-Agent" value:userAgentHeader]; - } - if (acceptHeader) { - [self addRequestHeader:@"Accept" value:acceptHeader]; - } - [self setHaveBuiltRequestHeaders:NO]; - } else { - - // Force rebuild the cookie header incase we got some new cookies from this request - // All other request headers will remain as they are for 301 / 302 redirects - [self applyCookieHeader]; - } - - // Force the redirected request to rebuild the request headers (if not a 303, it will re-use old ones, and add any new ones) - [self setRedirectURL:[[NSURL URLWithString:[responseHeaders valueForKey:@"Location"] relativeToURL:[self url]] absoluteURL]]; - [self setNeedsRedirect:YES]; - - // Clear the request cookies - // This means manually added cookies will not be added to the redirect request - only those stored in the global persistent store - // But, this is probably the safest option - we might be redirecting to a different domain - [self setRequestCookies:[NSMutableArray array]]; - - #if DEBUG_REQUEST_STATUS - NSLog(@"Request will redirect (code: %i): %@",[self responseStatusCode],self); - #endif - - } - } - - if (![self needsRedirect]) { + if (![self willRedirect]) { // See if we got a Content-length header NSString *cLength = [responseHeaders valueForKey:@"Content-Length"]; ASIHTTPRequest *theRequest = self; @@ -2214,7 +2249,8 @@ - (void)readResponseHeaders if ([self shouldAttemptPersistentConnection]) { NSString *connectionHeader = [[[self responseHeaders] objectForKey:@"Connection"] lowercaseString]; - NSString *httpVersion = NSMakeCollectable([(NSString *)CFHTTPMessageCopyVersion(message) autorelease]); + + NSString *httpVersion = [NSMakeCollectable(CFHTTPMessageCopyVersion(message)) autorelease]; // Don't re-use the connection if the server is HTTP 1.0 and didn't send Connection: Keep-Alive if (![httpVersion isEqualToString:(NSString *)kCFHTTPVersion1_0] || [connectionHeader isEqualToString:@"keep-alive"]) { @@ -2238,7 +2274,7 @@ - (void)readResponseHeaders [self setConnectionCanBeReused:YES]; [self setPersistentConnectionTimeoutSeconds:timeout]; #if DEBUG_PERSISTENT_CONNECTIONS - NSLog(@"Got a keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]); + ASI_DEBUG_LOG(@"[CONNECTION] Got a keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]); #endif } @@ -2246,7 +2282,7 @@ - (void)readResponseHeaders } else { [self setConnectionCanBeReused:YES]; #if DEBUG_PERSISTENT_CONNECTIONS - NSLog(@"Got no keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]); + ASI_DEBUG_LOG(@"[CONNECTION] Got no keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]); #endif } } @@ -2257,6 +2293,65 @@ - (void)readResponseHeaders [self performSelectorOnMainThread:@selector(requestReceivedResponseHeaders:) withObject:[[[self responseHeaders] copy] autorelease] waitUntilDone:[NSThread isMainThread]]; } +- (BOOL)willRedirect +{ + // Do we need to redirect? + if (![self shouldRedirect] || ![responseHeaders valueForKey:@"Location"]) { + return NO; + } + + // Note that ASIHTTPRequest does not currently support 305 Use Proxy + int responseCode = [self responseStatusCode]; + if (responseCode != 301 && responseCode != 302 && responseCode != 303 && responseCode != 307) { + return NO; + } + + [self performSelectorOnMainThread:@selector(requestRedirected) withObject:nil waitUntilDone:[NSThread isMainThread]]; + + // By default, we redirect 301 and 302 response codes as GET requests + // According to RFC 2616 this is wrong, but this is what most browsers do, so it's probably what you're expecting to happen + // See also: + // http://allseeing-i.lighthouseapp.com/projects/27881/tickets/27-302-redirection-issue + + if (responseCode != 307 && (![self shouldUseRFC2616RedirectBehaviour] || responseCode == 303)) { + [self setRequestMethod:@"GET"]; + [self setPostBody:nil]; + [self setPostLength:0]; + + // Perhaps there are other headers we should be preserving, but it's hard to know what we need to keep and what to throw away. + NSString *userAgentHeader = [[self requestHeaders] objectForKey:@"User-Agent"]; + NSString *acceptHeader = [[self requestHeaders] objectForKey:@"Accept"]; + [self setRequestHeaders:nil]; + if (userAgentHeader) { + [self addRequestHeader:@"User-Agent" value:userAgentHeader]; + } + if (acceptHeader) { + [self addRequestHeader:@"Accept" value:acceptHeader]; + } + [self setHaveBuiltRequestHeaders:NO]; + + } else { + // Force rebuild the cookie header incase we got some new cookies from this request + // All other request headers will remain as they are for 301 / 302 redirects + [self applyCookieHeader]; + } + + // Force the redirected request to rebuild the request headers (if not a 303, it will re-use old ones, and add any new ones) + [self setRedirectURL:[[NSURL URLWithString:[responseHeaders valueForKey:@"Location"] relativeToURL:[self url]] absoluteURL]]; + [self setNeedsRedirect:YES]; + + // Clear the request cookies + // This means manually added cookies will not be added to the redirect request - only those stored in the global persistent store + // But, this is probably the safest option - we might be redirecting to a different domain + [self setRequestCookies:[NSMutableArray array]]; + + #if DEBUG_REQUEST_STATUS + ASI_DEBUG_LOG(@"[STATUS] Request will redirect (code: %i): %@",responseCode,self); + #endif + + return YES; +} + - (void)parseStringEncodingFromHeaders { // Handle response text encoding @@ -2362,17 +2457,24 @@ - (NSMutableDictionary *)findProxyCredentials NSString *user = nil; NSString *pass = nil; - + ASIHTTPRequest *theRequest = [self mainRequest]; // If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request - if ([self mainRequest] && [[self mainRequest] proxyUsername] && [[self mainRequest] proxyPassword]) { - user = [[self mainRequest] proxyUsername]; - pass = [[self mainRequest] proxyPassword]; + if ([theRequest proxyUsername] && [theRequest proxyPassword]) { + user = [theRequest proxyUsername]; + pass = [theRequest proxyPassword]; - // Let's try to use the ones set in this object + // Let's try to use the ones set in this object } else if ([self proxyUsername] && [self proxyPassword]) { user = [self proxyUsername]; pass = [self proxyPassword]; - } + } + + // When we connect to a website using NTLM via a proxy, we will use the main credentials + if ((!user || !pass) && [self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM) { + user = [self username]; + pass = [self password]; + } + // Ok, that didn't work, let's try the keychain @@ -2391,13 +2493,21 @@ - (NSMutableDictionary *)findProxyCredentials NSString *ntlmDomain = [self proxyDomain]; - // If we have no domain yet, let's try to extract it from the username + // If we have no domain yet if (!ntlmDomain || [ntlmDomain length] == 0) { - ntlmDomain = @""; + + // Let's try to extract it from the username NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"]; if ([ntlmComponents count] == 2) { ntlmDomain = [ntlmComponents objectAtIndex:0]; user = [ntlmComponents objectAtIndex:1]; + + // If we are connecting to a website using NTLM, but we are connecting via a proxy, the string we need may be in the domain property + } else { + ntlmDomain = [self domain]; + } + if (!ntlmDomain) { + ntlmDomain = @""; } } [newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain]; @@ -2417,26 +2527,37 @@ - (NSMutableDictionary *)findProxyCredentials - (NSMutableDictionary *)findCredentials { NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease]; - - + // First, let's look at the url to see if the username and password were included NSString *user = [[self url] user]; NSString *pass = [[self url] password]; - - // If the username and password weren't in the url - if (!user || !pass) { + + if (user && pass) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will use credentials set on its url",self); + #endif + + } else { // If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request if ([self mainRequest] && [[self mainRequest] username] && [[self mainRequest] password]) { user = [[self mainRequest] username]; pass = [[self mainRequest] password]; - + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will use credentials from its parent request",self); + #endif + // Let's try to use the ones set in this object } else if ([self username] && [self password]) { user = [self username]; pass = [self password]; + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will use username and password properties as credentials",self); + #endif } - } // Ok, that didn't work, let's try the keychain @@ -2445,8 +2566,12 @@ - (NSMutableDictionary *)findCredentials if (authenticationCredentials) { user = [authenticationCredentials user]; pass = [authenticationCredentials password]; + #if DEBUG_HTTP_AUTHENTICATION + if (user && pass) { + ASI_DEBUG_LOG(@"[AUTH] Request %@ will use credentials from the keychain",self); + } + #endif } - } // Handle NTLM, which requires a domain to be set too @@ -2478,6 +2603,9 @@ - (NSMutableDictionary *)findCredentials // Called by delegate or authentication dialog to resume loading once authentication info has been populated - (void)retryUsingSuppliedCredentials { + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ received credentials from its delegate or an ASIAuthenticationDialog, will retry",self); + #endif //If the url was changed by the delegate, our CFHTTPMessageRef will be NULL and we'll go back to the start if (!request) { [self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; @@ -2489,6 +2617,9 @@ - (void)retryUsingSuppliedCredentials // Called by delegate or authentication dialog to cancel authentication - (void)cancelAuthentication { + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ had authentication cancelled by its delegate or an ASIAuthenticationDialog",self); + #endif [self performSelector:@selector(failAuthentication) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; } @@ -2499,21 +2630,29 @@ - (void)failAuthentication - (BOOL)showProxyAuthenticationDialog { -// Mac authentication dialog coming soon! -#if TARGET_OS_IPHONE + if ([self isSynchronous]) { + return NO; + } + + // Mac authentication dialog coming soon! + #if TARGET_OS_IPHONE if ([self shouldPresentProxyAuthenticationDialog]) { [ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]]; return YES; } return NO; -#else + #else return NO; -#endif + #endif } - (BOOL)willAskDelegateForProxyCredentials { + if ([self isSynchronous]) { + return NO; + } + // If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:. // Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate id authenticationDelegate = [self delegate]; @@ -2561,6 +2700,10 @@ - (void)askDelegateForProxyCredentials - (BOOL)willAskDelegateForCredentials { + if ([self isSynchronous]) { + return NO; + } + // If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:. // Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate id authenticationDelegate = [self delegate]; @@ -2618,7 +2761,7 @@ - (void)attemptToApplyProxyCredentialsAndResume CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty((CFReadStreamRef)[self readStream],kCFStreamPropertyHTTPResponseHeader); proxyAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader); CFRelease(responseHeader); - [self setProxyAuthenticationScheme:[(NSString *)CFHTTPAuthenticationCopyMethod(proxyAuthentication) autorelease]]; + [self setProxyAuthenticationScheme:[NSMakeCollectable(CFHTTPAuthenticationCopyMethod(proxyAuthentication)) autorelease]]; } // If we haven't got a CFHTTPAuthenticationRef by now, something is badly wrong, so we'll have to give up @@ -2631,7 +2774,7 @@ - (void)attemptToApplyProxyCredentialsAndResume // Get the authentication realm [self setProxyAuthenticationRealm:nil]; if (!CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) { - [self setProxyAuthenticationRealm:[(NSString *)CFHTTPAuthenticationCopyRealm(proxyAuthentication) autorelease]]; + [self setProxyAuthenticationRealm:[NSMakeCollectable(CFHTTPAuthenticationCopyRealm(proxyAuthentication)) autorelease]]; } // See if authentication is valid @@ -2763,26 +2906,28 @@ - (void)attemptToApplyProxyCredentialsAndResume - (BOOL)showAuthenticationDialog { -// Mac authentication dialog coming soon! -#if TARGET_OS_IPHONE + if ([self isSynchronous]) { + return NO; + } + // Mac authentication dialog coming soon! + #if TARGET_OS_IPHONE if ([self shouldPresentAuthenticationDialog]) { [ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]]; return YES; } return NO; -#else + #else return NO; -#endif + #endif } - - - (void)attemptToApplyCredentialsAndResume { if ([self error] || [self isCancelled]) { return; } + // Do we actually need to authenticate with a proxy? if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) { [self attemptToApplyProxyCredentialsAndResume]; return; @@ -2793,10 +2938,14 @@ - (void)attemptToApplyCredentialsAndResume CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty((CFReadStreamRef)[self readStream],kCFStreamPropertyHTTPResponseHeader); requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader); CFRelease(responseHeader); - [self setAuthenticationScheme:[(NSString *)CFHTTPAuthenticationCopyMethod(requestAuthentication) autorelease]]; + [self setAuthenticationScheme:[NSMakeCollectable(CFHTTPAuthenticationCopyMethod(requestAuthentication)) autorelease]]; } if (!requestAuthentication) { + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ failed to read authentication information from response headers",self); + #endif + [self cancelLoad]; [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]]; return; @@ -2805,9 +2954,23 @@ - (void)attemptToApplyCredentialsAndResume // Get the authentication realm [self setAuthenticationRealm:nil]; if (!CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) { - [self setAuthenticationRealm:[(NSString *)CFHTTPAuthenticationCopyRealm(requestAuthentication) autorelease]]; + [self setAuthenticationRealm:[NSMakeCollectable(CFHTTPAuthenticationCopyRealm(requestAuthentication)) autorelease]]; } + #if DEBUG_HTTP_AUTHENTICATION + NSString *realm = [self authenticationRealm]; + if (realm) { + realm = [NSString stringWithFormat:@" (Realm: %@)",realm]; + } else { + realm = @""; + } + if ([self authenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM || [self authenticationRetryCount] == 0) { + ASI_DEBUG_LOG(@"[AUTH] Request %@ received 401 challenge and must authenticate using %@%@",self,[self authenticationScheme],realm); + } else { + ASI_DEBUG_LOG(@"[AUTH] Request %@ NTLM handshake step %i",self,[self authenticationRetryCount]+1); + } + #endif + // See if authentication is valid CFStreamError err; if (!CFHTTPAuthenticationIsValid(requestAuthentication, &err)) { @@ -2817,7 +2980,11 @@ - (void)attemptToApplyCredentialsAndResume // check for bad credentials, so we can give the delegate a chance to replace them if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) { - + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ had bad credentials, will remove them from the session store if they are cached",self); + #endif + // Prevent more than one request from asking for credentials at once [delegateAuthenticationLock lock]; @@ -2827,34 +2994,57 @@ - (void)attemptToApplyCredentialsAndResume // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us if ([self error] || [self isCancelled]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ failed or was cancelled while waiting to access credentials",self); + #endif + [delegateAuthenticationLock unlock]; return; } - + // Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request if ([self useSessionPersistence]) { NSDictionary *credentials = [self findSessionAuthenticationCredentials]; if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will reuse cached credentials from the session (%@)",self,[credentials objectForKey:@"AuthenticationScheme"]); + #endif + [delegateAuthenticationLock unlock]; [self startRequest]; return; } } - - [self setLastActivityTime:nil]; if ([self willAskDelegateForCredentials]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will ask its delegate for credentials to use",self); + #endif + [delegateAuthenticationLock unlock]; return; } if ([self showAuthenticationDialog]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will ask ASIAuthenticationDialog for credentials",self); + #endif + [delegateAuthenticationLock unlock]; return; } [delegateAuthenticationLock unlock]; } + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ has no credentials to present and must give up",self); + #endif + [self cancelLoad]; [self failWithError:ASIAuthenticationError]; return; @@ -2869,9 +3059,18 @@ - (void)attemptToApplyCredentialsAndResume // We've failed NTLM authentication twice, we should assume our credentials are wrong } else if ([self authenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self authenticationRetryCount ] == 2) { + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ has failed NTLM authentication",self); + #endif + [self failWithError:ASIAuthenticationError]; } else { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ had credentials and they were not marked as bad, but we got a 401 all the same.",self); + #endif + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]]; } @@ -2883,6 +3082,11 @@ - (void)attemptToApplyCredentialsAndResume // If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us if ([self error] || [self isCancelled]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ failed or was cancelled while waiting to access credentials",self); + #endif + [delegateAuthenticationLock unlock]; return; } @@ -2891,6 +3095,11 @@ - (void)attemptToApplyCredentialsAndResume if ([self useSessionPersistence]) { NSDictionary *credentials = [self findSessionAuthenticationCredentials]; if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will reuse cached credentials from the session (%@)",self,[credentials objectForKey:@"AuthenticationScheme"]); + #endif + [delegateAuthenticationLock unlock]; [self startRequest]; return; @@ -2907,24 +3116,38 @@ - (void)attemptToApplyCredentialsAndResume [delegateAuthenticationLock unlock]; [self startRequest]; } else { + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ failed to apply credentials",self); + #endif [delegateAuthenticationLock unlock]; [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]]; } return; } if ([self willAskDelegateForCredentials]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will ask its delegate for credentials to use",self); + #endif + [delegateAuthenticationLock unlock]; return; } - if ([self showAuthenticationDialog]) { + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ will ask ASIAuthenticationDialog for credentials",self); + #endif + [delegateAuthenticationLock unlock]; return; } + + #if DEBUG_HTTP_AUTHENTICATION + ASI_DEBUG_LOG(@"[AUTH] Request %@ has no credentials to present and must give up",self); + #endif [delegateAuthenticationLock unlock]; - [self failWithError:ASIAuthenticationError]; - return; } @@ -2933,6 +3156,8 @@ - (void)attemptToApplyCredentialsAndResume - (void)addBasicAuthenticationHeaderWithUsername:(NSString *)theUsername andPassword:(NSString *)thePassword { [self addRequestHeader:@"Authorization" value:[NSString stringWithFormat:@"Basic %@",[ASIHTTPRequest base64forData:[[NSString stringWithFormat:@"%@:%@",theUsername,thePassword] dataUsingEncoding:NSUTF8StringEncoding]]]]; + [self setAuthenticationScheme:(NSString *)kCFHTTPAuthenticationSchemeBasic]; + } @@ -2946,7 +3171,7 @@ - (void)handleNetworkEvent:(CFStreamEventType)type if ([self complete] || [self isCancelled]) { [[self cancelledLock] unlock]; - [pool release]; + [pool drain]; return; } @@ -2975,22 +3200,7 @@ - (void)handleNetworkEvent:(CFStreamEventType)type [[self cancelledLock] unlock]; if ([self downloadComplete] && [self needsRedirect]) { - - // We must lock again to ensure delegate / queue aren't changed while we check them - [[self cancelledLock] lock]; - // Here we perform an initial check to see if either the delegate or the queue wants to be asked about the redirect, because if not we should redirect straight away - // We will check again on the main thread later - BOOL needToAskDelegateAboutRedirect = (([self delegate] && [[self delegate] respondsToSelector:[self willRedirectSelector]]) || ([self queue] && [[self queue] respondsToSelector:@selector(request:willRedirectToURL:)])); - [[self cancelledLock] unlock]; - - // Either the delegate or the queue's delegate is interested in being told when we are about to redirect - if (needToAskDelegateAboutRedirect) { - NSURL *newURL = [[[self redirectURL] copy] autorelease]; - [self setRedirectURL:nil]; - [self performSelectorOnMainThread:@selector(requestWillRedirectToURL:) withObject:newURL waitUntilDone:[NSThread isMainThread]]; - - // If neither the delegate nor the queue's delegate implement request:willRedirectToURL:, we will redirect automatically - } else { + if (![self willAskDelegateToConfirmRedirect]) { [self performRedirect]; } } else if ([self downloadComplete] && [self authenticationNeeded]) { @@ -2998,7 +3208,28 @@ - (void)handleNetworkEvent:(CFStreamEventType)type } CFRelease(self); - [pool release]; + [pool drain]; +} + +- (BOOL)willAskDelegateToConfirmRedirect +{ + // We must lock to ensure delegate / queue aren't changed while we check them + [[self cancelledLock] lock]; + + // Here we perform an initial check to see if either the delegate or the queue wants to be asked about the redirect, because if not we should redirect straight away + // We will check again on the main thread later + BOOL needToAskDelegateAboutRedirect = (([self delegate] && [[self delegate] respondsToSelector:[self willRedirectSelector]]) || ([self queue] && [[self queue] respondsToSelector:@selector(request:willRedirectToURL:)])); + + [[self cancelledLock] unlock]; + + // Either the delegate or the queue's delegate is interested in being told when we are about to redirect + if (needToAskDelegateAboutRedirect) { + NSURL *newURL = [[[self redirectURL] copy] autorelease]; + [self setRedirectURL:nil]; + [self performSelectorOnMainThread:@selector(requestWillRedirectToURL:) withObject:newURL waitUntilDone:[NSThread isMainThread]]; + return true; + } + return false; } - (void)handleBytesAvailable @@ -3154,7 +3385,7 @@ - (void)handleStreamComplete { #if DEBUG_REQUEST_STATUS - NSLog(@"Request %@ finished downloading data (%qu bytes)",self, [self totalBytesRead]); + ASI_DEBUG_LOG(@"[STATUS] Request %@ finished downloading data (%qu bytes)",self, [self totalBytesRead]); #endif [self setStatusTimer:nil]; [self setDownloadComplete:YES]; @@ -3166,7 +3397,7 @@ - (void)handleStreamComplete [progressLock lock]; // Find out how much data we've uploaded so far [self setLastBytesSent:totalBytesSent]; - [self setTotalBytesSent:[NSMakeCollectable([(NSNumber *)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease]) unsignedLongLongValue]]; + [self setTotalBytesSent:[[NSMakeCollectable(CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount)) autorelease] unsignedLongLongValue]]; [self setComplete:YES]; if (![self contentLength]) { [self setContentLength:[self totalBytesRead]]; @@ -3251,7 +3482,9 @@ - (void)handleStreamComplete [self unscheduleReadStream]; } #if DEBUG_PERSISTENT_CONNECTIONS - NSLog(@"Request #%@ finished using connection #%@",[self requestID], [[self connectionInfo] objectForKey:@"id"]); + if ([self requestID]) { + ASI_DEBUG_LOG(@"[CONNECTION] Request #%@ finished using connection #%@",[self requestID], [[self connectionInfo] objectForKey:@"id"]); + } #endif [[self connectionInfo] removeObjectForKey:@"request"]; [[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:[self persistentConnectionTimeoutSeconds]] forKey:@"expires"]; @@ -3286,13 +3519,16 @@ - (void)markAsFinished // dealloc won't be called when running with GC, so we'll clean these up now if (request) { - CFMakeCollectable(request); + CFRelease(request); + request = nil; } if (requestAuthentication) { - CFMakeCollectable(requestAuthentication); + CFRelease(requestAuthentication); + requestAuthentication = nil; } if (proxyAuthentication) { - CFMakeCollectable(proxyAuthentication); + CFRelease(proxyAuthentication); + proxyAuthentication = nil; } BOOL wasInProgress = inProgress; @@ -3338,13 +3574,10 @@ - (void)useDataFromCache if (headers && dataPath) { - // only 200 responses are stored in the cache, so let the client know - // this was a successful response - [self setResponseStatusCode:200]; - + [self setResponseStatusCode:[[headers objectForKey:@"X-ASIHTTPRequest-Response-Status-Code"] intValue]]; [self setDidUseCachedResponse:YES]; - [theRequest setResponseHeaders:headers]; + if ([theRequest downloadDestinationPath]) { [theRequest setDownloadDestinationPath:dataPath]; } else { @@ -3356,10 +3589,19 @@ - (void)useDataFromCache [theRequest parseStringEncodingFromHeaders]; [theRequest setResponseCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:[self url]]]; + + // See if we need to redirect + if ([self willRedirect]) { + if (![self willAskDelegateToConfirmRedirect]) { + [self performRedirect]; + } + return; + } } + [theRequest setComplete:YES]; [theRequest setDownloadComplete:YES]; - + // If we're pulling data from the cache without contacting the server at all, we won't have set originalURL yet if ([self redirectCount] == 0) { [theRequest setOriginalURL:[theRequest url]]; @@ -3376,8 +3618,13 @@ - (void)useDataFromCache - (BOOL)retryUsingNewConnection { if ([self retryCount] == 0) { + + [self setWillRetryRequest:YES]; + [self cancelLoad]; + [self setWillRetryRequest:NO]; + #if DEBUG_PERSISTENT_CONNECTIONS - NSLog(@"Request attempted to use connection #%@, but it has been closed - will retry with a new connection", [[self connectionInfo] objectForKey:@"id"]); + ASI_DEBUG_LOG(@"[CONNECTION] Request attempted to use connection #%@, but it has been closed - will retry with a new connection", [[self connectionInfo] objectForKey:@"id"]); #endif [connectionsLock lock]; [[self connectionInfo] removeObjectForKey:@"request"]; @@ -3389,7 +3636,7 @@ - (BOOL)retryUsingNewConnection return YES; } #if DEBUG_PERSISTENT_CONNECTIONS - NSLog(@"Request attempted to use connection #%@, but it has been closed - we have already retried with a new connection, so we must give up", [[self connectionInfo] objectForKey:@"id"]); + ASI_DEBUG_LOG(@"[CONNECTION] Request attempted to use connection #%@, but it has been closed - we have already retried with a new connection, so we must give up", [[self connectionInfo] objectForKey:@"id"]); #endif return NO; } @@ -3397,10 +3644,8 @@ - (BOOL)retryUsingNewConnection - (void)handleStreamError { - NSError *underlyingError = NSMakeCollectable([(NSError *)CFReadStreamCopyError((CFReadStreamRef)[self readStream]) autorelease]); + NSError *underlyingError = [NSMakeCollectable(CFReadStreamCopyError((CFReadStreamRef)[self readStream])) autorelease]; - [self cancelLoad]; - if (![self error]) { // We may already have handled this error // First, check for a 'socket not connected', 'broken pipe' or 'connection lost' error @@ -3422,11 +3667,13 @@ - (void)handleStreamError // Also, iPhone seems to handle errors differently from Mac OS X - a self-signed certificate returns a different error code on each platform, so we'll just provide a general error if ([[underlyingError domain] isEqualToString:NSOSStatusErrorDomain]) { if ([underlyingError code] <= -9800 && [underlyingError code] >= -9818) { - reason = [NSString stringWithFormat:@"%@: SSL problem (possibly a bad/expired/self-signed certificate)",reason]; + reason = [NSString stringWithFormat:@"%@: SSL problem (Possible causes may include a bad/expired/self-signed certificate, clock set to wrong date)",reason]; } } - + [self cancelLoad]; [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIConnectionFailureErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:reason,NSLocalizedDescriptionKey,underlyingError,NSUnderlyingErrorKey,nil]]]; + } else { + [self cancelLoad]; } [self checkRequestStatus]; } @@ -3575,12 +3822,12 @@ - (BOOL)configureProxies } else { #if TARGET_OS_IPHONE - NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)CFNetworkCopySystemProxySettings() autorelease]); + NSDictionary *proxySettings = [NSMakeCollectable(CFNetworkCopySystemProxySettings()) autorelease]; #else - NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)SCDynamicStoreCopyProxies(NULL) autorelease]); + NSDictionary *proxySettings = [NSMakeCollectable(SCDynamicStoreCopyProxies(NULL)) autorelease]; #endif - proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForURL((CFURLRef)[self url], (CFDictionaryRef)proxySettings) autorelease]); + proxies = [NSMakeCollectable(CFNetworkCopyProxiesForURL((CFURLRef)[self url], (CFDictionaryRef)proxySettings)) autorelease]; // Now check to see if the proxy settings contained a PAC url, we need to run the script to get the real list of proxies if so NSDictionary *settings = [proxies objectAtIndex:0]; @@ -3732,7 +3979,7 @@ - (void)runPACScript:(NSString *)script // Obtain the list of proxies by running the autoconfiguration script CFErrorRef err = NULL; - NSArray *proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForAutoConfigurationScript((CFStringRef)script,(CFURLRef)[self url], &err) autorelease]); + NSArray *proxies = [NSMakeCollectable(CFNetworkCopyProxiesForAutoConfigurationScript((CFStringRef)script,(CFURLRef)[self url], &err)) autorelease]; if (!err && [proxies count] > 0) { NSDictionary *settings = [proxies objectAtIndex:0]; [self setProxyHost:[settings objectForKey:(NSString *)kCFProxyHostNameKey]]; @@ -3777,7 +4024,7 @@ + (void)expirePersistentConnections NSDictionary *existingConnection = [persistentConnectionsPool objectAtIndex:i]; if (![existingConnection objectForKey:@"request"] && [[existingConnection objectForKey:@"expires"] timeIntervalSinceNow] <= 0) { #if DEBUG_PERSISTENT_CONNECTIONS - NSLog(@"Closing connection #%i because it has expired",[[existingConnection objectForKey:@"id"] intValue]); + ASI_DEBUG_LOG(@"[CONNECTION] Closing connection #%i because it has expired",[[existingConnection objectForKey:@"id"] intValue]); #endif NSInputStream *stream = [existingConnection objectForKey:@"stream"]; if (stream) { @@ -3791,7 +4038,6 @@ + (void)expirePersistentConnections } #pragma mark NSCopying - - (id)copyWithZone:(NSZone *)zone { // Don't forget - this will return a retained copy! @@ -3834,6 +4080,7 @@ - (id)copyWithZone:(NSZone *)zone [newRequest setDefaultResponseEncoding:[self defaultResponseEncoding]]; [newRequest setAllowResumeForFileDownloads:[self allowResumeForFileDownloads]]; [newRequest setUserInfo:[[[self userInfo] copyWithZone:zone] autorelease]]; + [newRequest setTag:[self tag]]; [newRequest setUseHTTPVersionOne:[self useHTTPVersionOne]]; [newRequest setShouldRedirect:[self shouldRedirect]]; [newRequest setValidatesSecureCertificate:[self validatesSecureCertificate]]; @@ -3845,6 +4092,7 @@ - (id)copyWithZone:(NSZone *)zone [newRequest setShouldUseRFC2616RedirectBehaviour:[self shouldUseRFC2616RedirectBehaviour]]; [newRequest setShouldAttemptPersistentConnection:[self shouldAttemptPersistentConnection]]; [newRequest setPersistentConnectionTimeoutSeconds:[self persistentConnectionTimeoutSeconds]]; + [newRequest setAuthenticationScheme:[self authenticationScheme]]; return newRequest; } @@ -3965,31 +4213,59 @@ - (NSDictionary *)findSessionAuthenticationCredentials { [sessionCredentialsLock lock]; NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore]; - // Find an exact match (same url) - for (NSDictionary *theCredentials in sessionCredentialsList) { - if ([(NSURL*)[theCredentials objectForKey:@"URL"] isEqual:[self url]]) { - // /Just a sanity check to ensure we never choose credentials from a different realm. Can't really do more than that, as either this request or the stored credentials may not have a realm when the other does - if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) { - [sessionCredentialsLock unlock]; - return theCredentials; - } - } - } - // Find a rough match (same host, port, scheme) NSURL *requestURL = [self url]; + + BOOL haveFoundExactMatch; + NSDictionary *closeMatch = nil; + + // Loop through all the cached credentials we have, looking for the best match for this request for (NSDictionary *theCredentials in sessionCredentialsList) { - NSURL *theURL = [theCredentials objectForKey:@"URL"]; - // Port can be nil! - if ([[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]]) { - if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) { - [sessionCredentialsLock unlock]; - return theCredentials; + haveFoundExactMatch = NO; + NSURL *cachedCredentialsURL = [theCredentials objectForKey:@"URL"]; + + // Find an exact match (same url) + if ([cachedCredentialsURL isEqual:[self url]]) { + haveFoundExactMatch = YES; + + // This is not an exact match for the url, and we already have a close match we can use + } else if (closeMatch) { + continue; + + // Find a close match (same host, scheme and port) + } else if ([[cachedCredentialsURL host] isEqualToString:[requestURL host]] && ([cachedCredentialsURL port] == [requestURL port] || ([requestURL port] && [[cachedCredentialsURL port] isEqualToNumber:[requestURL port]])) && [[cachedCredentialsURL scheme] isEqualToString:[requestURL scheme]]) { + } else { + continue; + } + + // Just a sanity check to ensure we never choose credentials from a different realm. Can't really do more than that, as either this request or the stored credentials may not have a realm when the other does + if ([self authenticationRealm] && ([theCredentials objectForKey:@"AuthenticationRealm"] && ![[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) { + continue; + } + + // If we have a username and password set on the request, check that they are the same as the cached ones + if ([self username] && [self password]) { + NSDictionary *usernameAndPassword = [theCredentials objectForKey:@"Credentials"]; + NSString *storedUsername = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername]; + NSString *storedPassword = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]; + if (![storedUsername isEqualToString:[self username]] || ![storedPassword isEqualToString:[self password]]) { + continue; } } + + // If we have an exact match for the url, use those credentials + if (haveFoundExactMatch) { + [sessionCredentialsLock unlock]; + return theCredentials; + } + + // We have no exact match, let's remember that we have a good match for this server, and we'll use it at the end if we don't find an exact match + closeMatch = theCredentials; } [sessionCredentialsLock unlock]; - return nil; + + // Return credentials that matched on host, port and scheme, or nil if we didn't find any + return closeMatch; } #pragma mark keychain storage @@ -4036,13 +4312,15 @@ + (void)removeCredentialsForProxy:(NSString *)host port:(int)port realm:(NSStrin } } - + (NSMutableArray *)sessionCookies { + [sessionCookiesLock lock]; if (!sessionCookies) { - [ASIHTTPRequest setSessionCookies:[[[NSMutableArray alloc] init] autorelease]]; + [ASIHTTPRequest setSessionCookies:[NSMutableArray array]]; } - return sessionCookies; + NSMutableArray *cookies = [[sessionCookies retain] autorelease]; + [sessionCookiesLock unlock]; + return cookies; } + (void)setSessionCookies:(NSMutableArray *)newSessionCookies @@ -4088,64 +4366,87 @@ + (void)clearSession + (NSString *)defaultUserAgentString { - NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + @synchronized (self) { - // Attempt to find a name for this application - NSString *appName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; - if (!appName) { - appName = [bundle objectForInfoDictionaryKey:@"CFBundleName"]; - } - // If we couldn't find one, we'll give up (and ASIHTTPRequest will use the standard CFNetwork user agent) - if (!appName) { - return nil; + if (!defaultUserAgent) { + + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + + // Attempt to find a name for this application + NSString *appName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + if (!appName) { + appName = [bundle objectForInfoDictionaryKey:@"CFBundleName"]; + } + + NSData *latin1Data = [appName dataUsingEncoding:NSUTF8StringEncoding]; + appName = [[[NSString alloc] initWithData:latin1Data encoding:NSISOLatin1StringEncoding] autorelease]; + + // If we couldn't find one, we'll give up (and ASIHTTPRequest will use the standard CFNetwork user agent) + if (!appName) { + return nil; + } + + NSString *appVersion = nil; + NSString *marketingVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + NSString *developmentVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]; + if (marketingVersionNumber && developmentVersionNumber) { + if ([marketingVersionNumber isEqualToString:developmentVersionNumber]) { + appVersion = marketingVersionNumber; + } else { + appVersion = [NSString stringWithFormat:@"%@ rv:%@",marketingVersionNumber,developmentVersionNumber]; + } + } else { + appVersion = (marketingVersionNumber ? marketingVersionNumber : developmentVersionNumber); + } + + NSString *deviceName; + NSString *OSName; + NSString *OSVersion; + NSString *locale = [[NSLocale currentLocale] localeIdentifier]; + + #if TARGET_OS_IPHONE + UIDevice *device = [UIDevice currentDevice]; + deviceName = [device model]; + OSName = [device systemName]; + OSVersion = [device systemVersion]; + + #else + deviceName = @"Macintosh"; + OSName = @"Mac OS X"; + + // From http://www.cocoadev.com/index.pl?DeterminingOSVersion + // We won't bother to check for systems prior to 10.4, since ASIHTTPRequest only works on 10.5+ + OSErr err; + SInt32 versionMajor, versionMinor, versionBugFix; + err = Gestalt(gestaltSystemVersionMajor, &versionMajor); + if (err != noErr) return nil; + err = Gestalt(gestaltSystemVersionMinor, &versionMinor); + if (err != noErr) return nil; + err = Gestalt(gestaltSystemVersionBugFix, &versionBugFix); + if (err != noErr) return nil; + OSVersion = [NSString stringWithFormat:@"%u.%u.%u", versionMajor, versionMinor, versionBugFix]; + #endif + + // Takes the form "My Application 1.0 (Macintosh; Mac OS X 10.5.7; en_GB)" + [self setDefaultUserAgentString:[NSString stringWithFormat:@"%@ %@ (%@; %@ %@; %@)", appName, appVersion, deviceName, OSName, OSVersion, locale]]; + } + return [[defaultUserAgent retain] autorelease]; } - NSString *appVersion = nil; - NSString *marketingVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - NSString *developmentVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]; - if (marketingVersionNumber && developmentVersionNumber) { - if ([marketingVersionNumber isEqualToString:developmentVersionNumber]) { - appVersion = marketingVersionNumber; - } else { - appVersion = [NSString stringWithFormat:@"%@ rv:%@",marketingVersionNumber,developmentVersionNumber]; + return nil; +} + ++ (void)setDefaultUserAgentString:(NSString *)agent +{ + @synchronized (self) { + if (defaultUserAgent == agent) { + return; } - } else { - appVersion = (marketingVersionNumber ? marketingVersionNumber : developmentVersionNumber); + [defaultUserAgent release]; + defaultUserAgent = [agent copy]; } - - - NSString *deviceName; - NSString *OSName; - NSString *OSVersion; - - NSString *locale = [[NSLocale currentLocale] localeIdentifier]; - -#if TARGET_OS_IPHONE - UIDevice *device = [UIDevice currentDevice]; - deviceName = [device model]; - OSName = [device systemName]; - OSVersion = [device systemVersion]; - -#else - deviceName = @"Macintosh"; - OSName = @"Mac OS X"; - - // From http://www.cocoadev.com/index.pl?DeterminingOSVersion - // We won't bother to check for systems prior to 10.4, since ASIHTTPRequest only works on 10.5+ - OSErr err; - SInt32 versionMajor, versionMinor, versionBugFix; - err = Gestalt(gestaltSystemVersionMajor, &versionMajor); - if (err != noErr) return nil; - err = Gestalt(gestaltSystemVersionMinor, &versionMinor); - if (err != noErr) return nil; - err = Gestalt(gestaltSystemVersionBugFix, &versionBugFix); - if (err != noErr) return nil; - OSVersion = [NSString stringWithFormat:@"%u.%u.%u", versionMajor, versionMinor, versionBugFix]; - -#endif - // Takes the form "My Application 1.0 (Macintosh; Mac OS X 10.5.7; en_GB)" - return [NSString stringWithFormat:@"%@ %@ (%@; %@ %@; %@)", appName, appVersion, deviceName, OSName, OSVersion, locale]; } + #pragma mark mime-type detection + (NSString *)mimeTypeForFileAtPath:(NSString *)path @@ -4160,7 +4461,7 @@ + (NSString *)mimeTypeForFileAtPath:(NSString *)path if (!MIMEType) { return @"application/octet-stream"; } - return NSMakeCollectable([(NSString *)MIMEType autorelease]); + return [NSMakeCollectable(MIMEType) autorelease]; } #pragma mark bandwidth measurement / throttling @@ -4179,14 +4480,14 @@ - (void)performThrottling if ([self readStreamIsScheduled]) { [self unscheduleReadStream]; #if DEBUG_THROTTLING - NSLog(@"Sleeping request %@ until after %@",self,throttleWakeUpTime); + ASI_DEBUG_LOG(@"[THROTTLING] Sleeping request %@ until after %@",self,throttleWakeUpTime); #endif } } else { if (![self readStreamIsScheduled]) { [self scheduleReadStream]; #if DEBUG_THROTTLING - NSLog(@"Waking up request %@",self); + ASI_DEBUG_LOG(@"[THROTTLING] Waking up request %@",self); #endif } } @@ -4204,12 +4505,12 @@ + (BOOL)isBandwidthThrottled #if TARGET_OS_IPHONE [bandwidthThrottlingLock lock]; - BOOL throttle = isBandwidthThrottled || (!shouldThrottleBandwithForWWANOnly && (maxBandwidthPerSecond)); + BOOL throttle = isBandwidthThrottled || (!shouldThrottleBandwidthForWWANOnly && (maxBandwidthPerSecond > 0)); [bandwidthThrottlingLock unlock]; return throttle; #else [bandwidthThrottlingLock lock]; - BOOL throttle = (maxBandwidthPerSecond); + BOOL throttle = (maxBandwidthPerSecond > 0); [bandwidthThrottlingLock unlock]; return throttle; #endif @@ -4249,7 +4550,7 @@ + (void)recordBandwidthUsage } } #if DEBUG_THROTTLING - NSLog(@"===Used: %u bytes of bandwidth in last measurement period===",bandwidthUsedInLastSecond); + ASI_DEBUG_LOG(@"[THROTTLING] ===Used: %u bytes of bandwidth in last measurement period===",bandwidthUsedInLastSecond); #endif [bandwidthUsageTracker addObject:[NSNumber numberWithUnsignedLong:bandwidthUsedInLastSecond]]; [bandwidthMeasurementDate release]; @@ -4284,7 +4585,7 @@ + (void)measureBandwidthUsage // Are we performing bandwidth throttling? if ( #if TARGET_OS_IPHONE - isBandwidthThrottled || (!shouldThrottleBandwithForWWANOnly && (maxBandwidthPerSecond)) + isBandwidthThrottled || (!shouldThrottleBandwidthForWWANOnly && (maxBandwidthPerSecond)) #else maxBandwidthPerSecond #endif @@ -4306,7 +4607,6 @@ + (void)measureBandwidthUsage + (unsigned long)maxUploadReadLength { - [bandwidthThrottlingLock lock]; // We'll split our bandwidth allowance into 4 (which is the default for an ASINetworkQueue's max concurrent operations count) to give all running requests a fighting chance of reading data this cycle @@ -4337,7 +4637,7 @@ + (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle [ASIHTTPRequest setMaxBandwidthPerSecond:0]; [bandwidthThrottlingLock lock]; isBandwidthThrottled = NO; - shouldThrottleBandwithForWWANOnly = NO; + shouldThrottleBandwidthForWWANOnly = NO; [bandwidthThrottlingLock unlock]; } } @@ -4345,7 +4645,7 @@ + (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle + (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit { [bandwidthThrottlingLock lock]; - shouldThrottleBandwithForWWANOnly = YES; + shouldThrottleBandwidthForWWANOnly = YES; maxBandwidthPerSecond = limit; [ASIHTTPRequest registerForNetworkReachabilityNotifications]; [bandwidthThrottlingLock unlock]; @@ -4391,13 +4691,19 @@ + (NSOperationQueue *)sharedQueue + (void)setDefaultCache:(id )cache { - [defaultCache release]; - defaultCache = [cache retain]; + @synchronized (self) { + [cache retain]; + [defaultCache release]; + defaultCache = cache; + } } + (id )defaultCache { - return defaultCache; + @synchronized(self) { + return [[defaultCache retain] autorelease]; + } + return nil; } @@ -4460,9 +4766,13 @@ + (void)hideNetworkActivityIndicatorIfNeeeded // If you have multiple requests sharing the thread or you want to re-use the thread, you'll need to restart the runloop + (NSThread *)threadForRequest:(ASIHTTPRequest *)request { - if (!networkThread) { - networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequests) object:nil]; - [networkThread start]; + if (networkThread == nil) { + @synchronized(self) { + if (networkThread == nil) { + networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequests) object:nil]; + [networkThread start]; + } + } } return networkThread; } @@ -4478,7 +4788,7 @@ + (void)runRequests while (runAlways) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; CFRunLoopRun(); - [pool release]; + [pool drain]; } // Should never be called, but anyway @@ -4511,15 +4821,13 @@ + (NSString*)base64forData:(NSData*)theData { NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; uint8_t* output = (uint8_t*)data.mutableBytes; - NSInteger i; + NSInteger i,i2; for (i=0; i < length; i += 3) { NSInteger value = 0; - NSInteger j; - for (j = i; j < (i + 3); j++) { + for (i2=0; i2<3; i2++) { value <<= 8; - - if (j < length) { - value |= (0xFF & input[j]); + if (i+i2 < length) { + value |= (0xFF & input[i+i2]); } } @@ -4533,6 +4841,35 @@ + (NSString*)base64forData:(NSData*)theData { return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease]; } ++ (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge +{ + NSDictionary *responseHeaders = [request responseHeaders]; + + // If we weren't given a custom max-age, lets look for one in the response headers + if (!maxAge) { + NSString *cacheControl = [[responseHeaders objectForKey:@"Cache-Control"] lowercaseString]; + if (cacheControl) { + NSScanner *scanner = [NSScanner scannerWithString:cacheControl]; + [scanner scanUpToString:@"max-age" intoString:NULL]; + if ([scanner scanString:@"max-age" intoString:NULL]) { + [scanner scanString:@"=" intoString:NULL]; + [scanner scanDouble:&maxAge]; + } + } + } + + // RFC 2612 says max-age must override any Expires header + if (maxAge) { + return [[NSDate date] addTimeInterval:maxAge]; + } else { + NSString *expires = [responseHeaders objectForKey:@"Expires"]; + if (expires) { + return [ASIHTTPRequest dateFromRFC1123String:expires]; + } + } + return nil; +} + // Based on hints from http://stackoverflow.com/questions/1850824/parsing-a-rfc-822-date-with-nsdateformatter + (NSDate *)dateFromRFC1123String:(NSString *)string { @@ -4656,6 +4993,7 @@ - (void)setRequestRedirectedBlock:(ASIBasicBlock)aRedirectBlock @synthesize username; @synthesize password; +@synthesize userAgent; @synthesize domain; @synthesize proxyUsername; @synthesize proxyPassword; @@ -4709,6 +5047,7 @@ - (void)setRequestRedirectedBlock:(ASIBasicBlock)aRedirectBlock @synthesize allowCompressedResponse; @synthesize allowResumeForFileDownloads; @synthesize userInfo; +@synthesize tag; @synthesize postBodyFilePath; @synthesize compressedPostBodyFilePath; @synthesize postBodyWriteStream; @@ -4746,6 +5085,7 @@ - (void)setRequestRedirectedBlock:(ASIBasicBlock)aRedirectBlock @synthesize inProgress; @synthesize numberOfTimesToRetryOnTimeout; @synthesize retryCount; +@synthesize willRetryRequest; @synthesize shouldAttemptPersistentConnection; @synthesize persistentConnectionTimeoutSeconds; @synthesize connectionCanBeReused; diff --git a/Vendor/ASIHTTPRequest/ASIHTTPRequestConfig.h b/Vendor/ASIHTTPRequest/ASIHTTPRequestConfig.h index 52cbfcb..3f6c587 100644 --- a/Vendor/ASIHTTPRequest/ASIHTTPRequestConfig.h +++ b/Vendor/ASIHTTPRequest/ASIHTTPRequestConfig.h @@ -11,6 +11,12 @@ // Debug output configuration options // ====== +// If defined will use the specified function for debug logging +// Otherwise use NSLog +#ifndef ASI_DEBUG_LOG + #define ASI_DEBUG_LOG NSLog +#endif + // When set to 1 ASIHTTPRequests will print information about what a request is doing #ifndef DEBUG_REQUEST_STATUS #define DEBUG_REQUEST_STATUS 0 @@ -30,3 +36,8 @@ #ifndef DEBUG_PERSISTENT_CONNECTIONS #define DEBUG_PERSISTENT_CONNECTIONS 0 #endif + +// When set to 1, ASIHTTPRequests will print information about HTTP authentication (Basic, Digest or NTLM) to the console +#ifndef DEBUG_HTTP_AUTHENTICATION + #define DEBUG_HTTP_AUTHENTICATION 0 +#endif diff --git a/Vendor/ASIHTTPRequest/ASIInputStream.m b/Vendor/ASIHTTPRequest/ASIInputStream.m index 5753510..d2b8428 100644 --- a/Vendor/ASIHTTPRequest/ASIInputStream.m +++ b/Vendor/ASIHTTPRequest/ASIInputStream.m @@ -58,9 +58,11 @@ - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len } [request performThrottling]; } - [ASIHTTPRequest incrementBandwidthUsedInLastSecond:toRead]; [readLock unlock]; - return [stream read:buffer maxLength:toRead]; + NSInteger rv = [stream read:buffer maxLength:toRead]; + if (rv > 0) + [ASIHTTPRequest incrementBandwidthUsedInLastSecond:rv]; + return rv; } /* diff --git a/Vendor/ASIHTTPRequest/Reachability.h b/Vendor/ASIHTTPRequest/Reachability.h index b49b797..af52444 100644 --- a/Vendor/ASIHTTPRequest/Reachability.h +++ b/Vendor/ASIHTTPRequest/Reachability.h @@ -105,6 +105,7 @@ #import #import +#import #define USE_DDG_EXTENSIONS 1 // Use DDG's Extensions to test network criteria. // Since NSAssert and NSCAssert are used in this code, diff --git a/Vendor/ASIHTTPRequest/version.txt b/Vendor/ASIHTTPRequest/version.txt new file mode 100644 index 0000000..0f5ce84 --- /dev/null +++ b/Vendor/ASIHTTPRequest/version.txt @@ -0,0 +1 @@ +4282568eec0b487a98e312ce49b523350ffa4a6b From c911ea1ae3e6cf0ffaf8f65945fa1e8fe7a61722 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Thu, 20 Oct 2011 17:14:32 +0530 Subject: [PATCH 120/180] Fix indentation and naming conventions in recent changed files --- Categories/NSXMLElementAdditions.m | 20 +-- Core/Transports/BoshTransport.h | 2 +- Core/Transports/BoshTransport.m | 269 ++++++++++++++--------------- Core/XMPPStream.h | 2 +- Core/XMPPStream.m | 72 ++++---- 5 files changed, 182 insertions(+), 183 deletions(-) diff --git a/Categories/NSXMLElementAdditions.m b/Categories/NSXMLElementAdditions.m index 1895152..3959a2d 100644 --- a/Categories/NSXMLElementAdditions.m +++ b/Categories/NSXMLElementAdditions.m @@ -221,26 +221,26 @@ - (NSString *)namespaceStringValueForPrefix:(NSString *)prefix withDefaultValue: + (NSXMLElement *)parseWellFormatedXMLString:(NSString *)xml { - NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:xml - options:0 - error:NULL] autorelease]; - NSXMLElement *element = [doc rootElement]; - [element detach]; - return element; + NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithXMLString:xml + options:0 + error:NULL] autorelease]; + NSXMLElement *element = [doc rootElement]; + [element detach]; + return element; } #define kNSXMLElement @"NSXMLElementCompactXMLString" - (void)encodeWithCoder: (NSCoder *)coder { - [coder encodeObject:[self compactXMLString] forKey:kNSXMLElement]; + [coder encodeObject:[self compactXMLString] forKey:kNSXMLElement]; } - (id)initWithCoder: (NSCoder *)coder { - [self release]; - self = [[NSXMLElement parseWellFormatedXMLString:(NSString *)[coder decodeObjectForKey:kNSXMLElement]] retain]; - return self; + [self release]; + self = [[NSXMLElement parseWellFormatedXMLString:(NSString *)[coder decodeObjectForKey:kNSXMLElement]] retain]; + return self; } @end diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index fd67a19..eb1885d 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -83,7 +83,7 @@ typedef enum { #pragma mark - -@interface BoshTransport : NSObject { +@interface BoshTransport : NSObject { NSString *boshVersion; long long nextRidToSend; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 49dc562..c7f1aa5 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -85,25 +85,25 @@ - (void)dealloc [super dealloc]; } -#define kRRPairRequest @"@property (nonatomic, retain) NSXMLElement *request" -#define kRRPairResponse @"@property (nonatomic, retain) NSXMLElement *response" +#define kPairRequest @"request" +#define kPairResponse @"response" - (void)encodeWithCoder: (NSCoder *)coder { - [coder encodeObject:self.request forKey:kRRPairRequest]; - [coder encodeObject:self.response forKey:kRRPairResponse]; + [coder encodeObject:self.request forKey:kPairRequest]; + [coder encodeObject:self.response forKey:kPairResponse]; } - (id)initWithCoder: (NSCoder *)coder { - self = [self init]; - if (self && coder) - { - self.request = [coder decodeObjectForKey:kRRPairRequest ]; - self.response = [coder decodeObjectForKey:kRRPairResponse]; - } - - return self; + self = [self init]; + if (self && coder) + { + self.request = [coder decodeObjectForKey:kPairRequest ]; + self.response = [coder decodeObjectForKey:kPairResponse]; + } + + return self; } @@ -164,36 +164,36 @@ - (void) dealloc [super dealloc]; } -#define kWMMaxRidReceived @"long long maxRidReceived" -#define kWMMaxRidSent @"long long maxRidSent" -#define kWMReceivedRids @"NSMutableSet *receivedRids" -#define kWMWindowSize @"@property unsigned int windowSize" +#define kMaxRidReceived @"maxRidReceived" +#define kMaxRidSent @"maxRidSent" +#define kReceivedRids @"receivedRids" +#define kWindowSize @"windowSize" - (void)encodeWithCoder: (NSCoder *)coder { - [coder encodeInt64:maxRidReceived forKey:kWMMaxRidReceived]; - [coder encodeInt64:maxRidSent forKey:kWMMaxRidSent]; - [coder encodeObject:receivedRids forKey:kWMReceivedRids]; - [coder encodeInt:self.windowSize forKey:kWMWindowSize]; + [coder encodeInt64:maxRidReceived forKey:kMaxRidReceived]; + [coder encodeInt64:maxRidSent forKey:kMaxRidSent]; + [coder encodeObject:receivedRids forKey:kReceivedRids]; + [coder encodeInt:self.windowSize forKey:kWindowSize]; } - (void)commonInitWithCoder:(NSCoder *)coder { - maxRidSent = [coder decodeInt64ForKey:kWMMaxRidSent]; - maxRidReceived = [coder decodeInt64ForKey:kWMMaxRidReceived]; - receivedRids = [[coder decodeObjectForKey:kWMReceivedRids] retain]; - self.windowSize = [coder decodeIntForKey:kWMWindowSize]; + maxRidSent = [coder decodeInt64ForKey:kMaxRidSent]; + maxRidReceived = [coder decodeInt64ForKey:kMaxRidReceived]; + receivedRids = [[coder decodeObjectForKey:kReceivedRids] retain]; + self.windowSize = [coder decodeIntForKey:kWindowSize]; } - (id)initWithCoder: (NSCoder *)coder { - self = [self init]; - if (self && coder) - { - [self commonInitWithCoder:coder]; - } - - return self; + self = [self init]; + if (self && coder) + { + [self commonInitWithCoder:coder]; + } + + return self; } @@ -336,7 +336,7 @@ - (id)initWithUrl:(NSURL *)url if(self) { boshVersion = BoshVersion; - lang_ = [@"en" retain]; + lang_ = @"en"; wait_ = 60.0; hold_ = 1; @@ -946,114 +946,114 @@ - (void)dealloc #pragma mark Protocol NSCoding Method Implementation -#define kNextRidToSend @"long long nextRidToSend" -#define kMaxRidProcessed @"long long maxRidProcessed" +#define kNextRidToSend @"nextRidToSend" +#define kMaxRidProcessed @"maxRidProcessed" -#define kPendingXMPPStanza @"NSMutableArray *pendingXMPPStanzas" -#define kBoshWindowManager @"BoshWindowManager *boshWindowManager" -#define kState @"BoshTransportState state" +#define kPendingXMPPStanza @"pendingXMPPStanzas" +#define kBoshWindowManager @"boshWindowManager" +#define kState @"state" -#define kRequestResponsePairs @"NSMutableDictionary *requestResponsePairs" +#define kRequestResponsePairs @"requestResponsePairs" -#define kDisconnectError_ @"NSError *disconnectError_;" -// -#define kRetryCounter @"int retryCounter;" -#define kNextRequestDelay @"NSTimeInterval nextRequestDelay;" -// -#define kMyJID @"@property(retain) XMPPJID *myJID;" -#define kWait @"@property(assign) unsigned int wait;" -#define kHold @"@property(assign) unsigned int hold;" -#define kLang @"@property(copy) NSString *lang;" -#define kDomain @"@property(copy) NSString *domain;" -#define kRouteProtocol @"@property(copy) NSString *routeProtocol;" -#define kHost @"@property(copy) NSString *host;" -#define kPort @"@property(assign) unsigned int port;" -#define kInactivity @"@property(assign) unsigned int inactivity" -#define kSecure @"@property(readonly)_BOOL_secure" -#define kRequest @"@property(readonly) unsigned int requests" -#define kAuthId @"@property(copy) NSString *authid" -#define kSid @"@property(copy) NSString *sid" -#define kUrl @"@property(copy) NSURL *url" +#define kDisconnectError_ @"disconnectError_" + +#define kRetryCounter @"retryCounter" +#define kNextRequestDelay @"nextRequestDelay" + +#define kMyJID @"myJID" +#define kWait @"wait" +#define kHold @"hold" +#define kLang @"lang" +#define kDomain @"domain" +#define kRouteProtocol @"routeProtocol" +#define kHost @"host" +#define kPort @"port" +#define kInactivity @"inactivity" +#define kSecure @"secure" +#define kRequest @"requests" +#define kAuthId @"authid" +#define kSid @"sid" +#define kUrl @"url" - (void)encodeWithCoder: (NSCoder *)coder { - [coder encodeInt64:nextRidToSend forKey:kNextRidToSend]; - [coder encodeInt64:maxRidProcessed forKey:kMaxRidProcessed]; - - [coder encodeObject:pendingXMPPStanzas forKey:kPendingXMPPStanza]; - [coder encodeObject:boshWindowManager forKey:kBoshWindowManager] ; - [coder encodeInt:state forKey:kState]; - - [coder encodeObject:requestResponsePairs forKey:kRequestResponsePairs]; - - [coder encodeObject:disconnectError_ forKey:kDisconnectError_]; - - [coder encodeInt:retryCounter forKey:kRetryCounter]; - [coder encodeDouble:nextRequestDelay forKey:kNextRequestDelay]; - - [coder encodeObject:self.myJID forKey:kMyJID]; - [coder encodeInt:self.wait forKey:kWait]; - [coder encodeInt:self.hold forKey:kHold]; - [coder encodeObject:self.lang forKey:kLang]; - [coder encodeObject:self.domain forKey:kDomain]; - [coder encodeObject:self.routeProtocol forKey:kRouteProtocol]; - [coder encodeObject:self.host forKey:kHost]; - [coder encodeInt:self.port forKey:kPort]; - [coder encodeInt:self.inactivity forKey:kInactivity]; - [coder encodeBool:self.secure forKey:kSecure]; - [coder encodeInt:self.requests forKey:kRequest]; - [coder encodeObject:self.authid forKey:kAuthId]; - [coder encodeObject:self.sid forKey:kSid]; - [coder encodeObject:self.url forKey:kUrl]; + [coder encodeInt64:nextRidToSend forKey:kNextRidToSend]; + [coder encodeInt64:maxRidProcessed forKey:kMaxRidProcessed]; + + [coder encodeObject:pendingXMPPStanzas forKey:kPendingXMPPStanza]; + [coder encodeObject:boshWindowManager forKey:kBoshWindowManager] ; + [coder encodeInt:state forKey:kState]; + + [coder encodeObject:requestResponsePairs forKey:kRequestResponsePairs]; + + [coder encodeObject:disconnectError_ forKey:kDisconnectError_]; + + [coder encodeInt:retryCounter forKey:kRetryCounter]; + [coder encodeDouble:nextRequestDelay forKey:kNextRequestDelay]; + + [coder encodeObject:self.myJID forKey:kMyJID]; + [coder encodeInt:self.wait forKey:kWait]; + [coder encodeInt:self.hold forKey:kHold]; + [coder encodeObject:self.lang forKey:kLang]; + [coder encodeObject:self.domain forKey:kDomain]; + [coder encodeObject:self.routeProtocol forKey:kRouteProtocol]; + [coder encodeObject:self.host forKey:kHost]; + [coder encodeInt:self.port forKey:kPort]; + [coder encodeInt:self.inactivity forKey:kInactivity]; + [coder encodeBool:self.secure forKey:kSecure]; + [coder encodeInt:self.requests forKey:kRequest]; + [coder encodeObject:self.authid forKey:kAuthId]; + [coder encodeObject:self.sid forKey:kSid]; + [coder encodeObject:self.url forKey:kUrl]; } - (void)commonInitWithCoder:(NSCoder *)coder { - boshVersion = BoshVersion; - - nextRidToSend = [coder decodeInt64ForKey:kNextRidToSend]; - maxRidProcessed = [coder decodeInt64ForKey:kMaxRidProcessed]; - - pendingXMPPStanzas =[[coder decodeObjectForKey:kPendingXMPPStanza] retain]; - boshWindowManager = [[coder decodeObjectForKey:kBoshWindowManager] retain]; - state = [coder decodeIntForKey:kState]; - - requestResponsePairs = [[coder decodeObjectForKey:kRequestResponsePairs] retain]; - - disconnectError_ = [[coder decodeObjectForKey:kDisconnectError_] retain]; - - retryCounter = [coder decodeIntForKey:kRetryCounter]; - nextRequestDelay= [coder decodeDoubleForKey:kNextRequestDelay]; - - - self.myJID= [coder decodeObjectForKey:kMyJID]; - self.wait= [coder decodeIntForKey:kWait]; - self.hold= [coder decodeIntForKey:kHold]; - self.lang= [coder decodeObjectForKey:kLang]; - self.domain= [coder decodeObjectForKey:kDomain]; - self.routeProtocol= [coder decodeObjectForKey:kRouteProtocol]; - self.host= [coder decodeObjectForKey:kHost]; - self.port= [coder decodeIntForKey:kPort]; - self.inactivity= [coder decodeIntForKey:kInactivity]; - secure = [coder decodeBoolForKey:kSecure]; - requests = [coder decodeIntForKey:kRequest]; - self.authid= [coder decodeObjectForKey:kAuthId]; - self.sid= [coder decodeObjectForKey:kSid]; - self.url= [coder decodeObjectForKey:kUrl]; - - multicastDelegate = [[MulticastDelegate alloc] init]; - + boshVersion = BoshVersion; + + nextRidToSend = [coder decodeInt64ForKey:kNextRidToSend]; + maxRidProcessed = [coder decodeInt64ForKey:kMaxRidProcessed]; + + pendingXMPPStanzas =[[coder decodeObjectForKey:kPendingXMPPStanza] retain]; + boshWindowManager = [[coder decodeObjectForKey:kBoshWindowManager] retain]; + state = [coder decodeIntForKey:kState]; + + requestResponsePairs = [[coder decodeObjectForKey:kRequestResponsePairs] retain]; + + disconnectError_ = [[coder decodeObjectForKey:kDisconnectError_] retain]; + + retryCounter = [coder decodeIntForKey:kRetryCounter]; + nextRequestDelay= [coder decodeDoubleForKey:kNextRequestDelay]; + + + self.myJID= [coder decodeObjectForKey:kMyJID]; + self.wait= [coder decodeIntForKey:kWait]; + self.hold= [coder decodeIntForKey:kHold]; + self.lang= [coder decodeObjectForKey:kLang]; + self.domain= [coder decodeObjectForKey:kDomain]; + self.routeProtocol= [coder decodeObjectForKey:kRouteProtocol]; + self.host= [coder decodeObjectForKey:kHost]; + self.port= [coder decodeIntForKey:kPort]; + self.inactivity= [coder decodeIntForKey:kInactivity]; + secure = [coder decodeBoolForKey:kSecure]; + requests = [coder decodeIntForKey:kRequest]; + self.authid= [coder decodeObjectForKey:kAuthId]; + self.sid= [coder decodeObjectForKey:kSid]; + self.url= [coder decodeObjectForKey:kUrl]; + + multicastDelegate = [[MulticastDelegate alloc] init]; + } - (id)initWithCoder: (NSCoder *)coder { - self = [self init]; - if (self && coder) - { - [self commonInitWithCoder:coder]; - } - return self; + self = [self init]; + if (self && coder) + { + [self commonInitWithCoder:coder]; + } + return self; } /* @@ -1062,16 +1062,15 @@ - (id)initWithCoder: (NSCoder *)coder */ - (void)resendRemainingRequests { - for ( NSNumber *ridNumber in [requestResponsePairs allKeys ]) - { - long long rid = [ridNumber longLongValue]; - RequestResponsePair *pair = [requestResponsePairs objectForLongLongKey:rid]; - if ( !pair.response ) - { - [self sendHTTPRequestWithBody:pair.request rid:rid]; - } - } + for ( NSNumber *ridNumber in [requestResponsePairs allKeys ]) + { + long long rid = [ridNumber longLongValue]; + RequestResponsePair *pair = [requestResponsePairs objectForLongLongKey:rid]; + if ( !pair.response ) + { + [self sendHTTPRequestWithBody:pair.request rid:rid]; + } + } } - @end diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index f8001a8..cbcda30 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -45,7 +45,7 @@ enum XMPPStreamErrorCode typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; -@interface XMPPStream : NSObject +@interface XMPPStream : NSObject { MulticastDelegate *multicastDelegate; diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index c98b2d5..53dbcbe 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -86,13 +86,13 @@ @implementation XMPPStream - (void)setTransport:(id)givenTransport { - transport = givenTransport; - [transport addDelegate:self]; + transport = givenTransport; + [transport addDelegate:self]; } - (id)transport { - return transport; + return transport; } /** * Shared initialization between the various init methods. @@ -163,17 +163,17 @@ - (void)dealloc #pragma mark NSCoding Protocol methods //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#define kXSState @"int state" +#define kState @"state" -#define kXSFlags @"Byte flags" +#define kFlags @"flags" -#define kXSTempPassword @"NSString *tempPassword" +#define kTempPassword @"tempPassword" -#define kXSMyJID @"XMPPJID *myJID" -#define kXSRemoteJID @"XMPPJID *remoteJID" +#define kMyJID @"myJID" +#define kRemoteJID @"remoteJID" -#define kXSMYPresence @"XMPPPresence *myPresence" -#define kXSRootelement @"NSXMLElement *rootElement" +#define kMYPresence @"myPresence" +#define kRootelement @"rootElement" //#define @"id userTag" not being used ryt now in the code, therefore not using // @@ -183,42 +183,42 @@ - (void)dealloc - (void)encodeWithCoder: (NSCoder *)coder { - [coder encodeInt:state forKey:kXSState]; - [coder encodeInt:flags forKey:kXSFlags]; - [coder encodeObject:tempPassword forKey:kXSTempPassword]; - [coder encodeObject:myJID forKey:kXSMyJID]; - [coder encodeObject:remoteJID forKey:kXSRemoteJID]; - - [coder encodeObject:myPresence forKey:kXSMYPresence]; - [coder encodeObject:rootElement forKey:kXSRootelement]; + [coder encodeInt:state forKey:kState]; + [coder encodeInt:flags forKey:kFlags]; + [coder encodeObject:tempPassword forKey:kTempPassword]; + [coder encodeObject:myJID forKey:kMyJID]; + [coder encodeObject:remoteJID forKey:kRemoteJID]; + + [coder encodeObject:myPresence forKey:kMYPresence]; + [coder encodeObject:rootElement forKey:kRootelement]; } - (void)commonInitWithCoder:(NSCoder *)coder { - state = [coder decodeIntForKey:kXSState]; - flags = (Byte) [coder decodeIntForKey:kXSFlags]; - - tempPassword = [[coder decodeObjectForKey:kXSTempPassword] copy]; - myJID = [[coder decodeObjectForKey:kXSMyJID] copy]; - remoteJID = [[coder decodeObjectForKey:kXSRemoteJID] copy] ; - - myPresence = [[coder decodeObjectForKey:kXSMYPresence] retain]; - rootElement = [[coder decodeObjectForKey:kXSRootelement] retain]; - - multicastDelegate = [[MulticastDelegate alloc] init]; + state = [coder decodeIntForKey:kState]; + flags = (Byte) [coder decodeIntForKey:kFlags]; + + tempPassword = [[coder decodeObjectForKey:kTempPassword] copy]; + myJID = [[coder decodeObjectForKey:kMyJID] copy]; + remoteJID = [[coder decodeObjectForKey:kRemoteJID] copy] ; + + myPresence = [[coder decodeObjectForKey:kMYPresence] retain]; + rootElement = [[coder decodeObjectForKey:kRootelement] retain]; + + multicastDelegate = [[MulticastDelegate alloc] init]; registeredModules = [[MulticastDelegate alloc] init]; autoDelegateDict = [[NSMutableDictionary alloc] init]; - + } - (id)initWithCoder: (NSCoder *)coder { - self = [self init]; - if (self && coder) - { - [self commonInitWithCoder:coder]; - } - return self; + self = [self init]; + if (self && coder) + { + [self commonInitWithCoder:coder]; + } + return self; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From e6075385f5454abbdc9a28241d2d4a3f599637ac Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Thu, 20 Oct 2011 17:18:18 +0530 Subject: [PATCH 121/180] Retain transport in XmppStream --- Core/XMPPStream.h | 2 +- Core/XMPPStream.m | 3 ++- Core/XMPPTransportProtocol.h | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index cbcda30..7528deb 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -158,7 +158,7 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; @property (nonatomic) SEL customAuthSelector; @property (nonatomic) SEL customHandleAuthSelector; -@property (nonatomic, assign) id transport; +@property (nonatomic, retain) id transport; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark State diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 53dbcbe..4b02664 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -86,7 +86,7 @@ @implementation XMPPStream - (void)setTransport:(id)givenTransport { - transport = givenTransport; + transport = [givenTransport retain]; [transport addDelegate:self]; } @@ -141,6 +141,7 @@ - (NSXMLElement *)newRootElement **/ - (void)dealloc { + [transport release]; [multicastDelegate release]; [tempPassword release]; diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 14d03e8..b3d7df5 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -31,7 +31,7 @@ @class XMPPJID; -@protocol XMPPTransportProtocol +@protocol XMPPTransportProtocol - (void)addDelegate:(id)delegate; - (void)removeDelegate:(id)delegate; From c78cc5863c3526ffc9d4d8eaac9258cc57a8f49b Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Thu, 20 Oct 2011 17:24:25 +0530 Subject: [PATCH 122/180] Ensure resendRemainingRequests method in BoshTransport sends atleast a packet --- Core/Transports/BoshTransport.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index c7f1aa5..b676013 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -1062,15 +1062,23 @@ - (id)initWithCoder: (NSCoder *)coder */ - (void)resendRemainingRequests { + BOOL didSentSomething = false; for ( NSNumber *ridNumber in [requestResponsePairs allKeys ]) { long long rid = [ridNumber longLongValue]; RequestResponsePair *pair = [requestResponsePairs objectForLongLongKey:rid]; if ( !pair.response ) { + didSentSomething = true; [self sendHTTPRequestWithBody:pair.request rid:rid]; } } + + if ( !didSentSomething ) + { + // if we havent sent anything, send an empty packet. + [self trySendingStanzas]; + } } @end From 86939f46e3984e5a0fe21a3981eb8adb1ed55b36 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Thu, 20 Oct 2011 17:28:45 +0530 Subject: [PATCH 123/180] Archive transport with archiving xmppStream --- Core/XMPPStream.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 4b02664..57a7532 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -176,6 +176,8 @@ - (void)dealloc #define kMYPresence @"myPresence" #define kRootelement @"rootElement" +#define kTransport @"transport" + //#define @"id userTag" not being used ryt now in the code, therefore not using // //#define @"id customAuthTarget" @@ -192,6 +194,7 @@ - (void)encodeWithCoder: (NSCoder *)coder [coder encodeObject:myPresence forKey:kMYPresence]; [coder encodeObject:rootElement forKey:kRootelement]; + [coder encodeObject:transport forKey:kTransport]; } - (void)commonInitWithCoder:(NSCoder *)coder @@ -206,10 +209,11 @@ - (void)commonInitWithCoder:(NSCoder *)coder myPresence = [[coder decodeObjectForKey:kMYPresence] retain]; rootElement = [[coder decodeObjectForKey:kRootelement] retain]; + transport = [[coder decodeObjectForKey:kTransport] retain]; + multicastDelegate = [[MulticastDelegate alloc] init]; registeredModules = [[MulticastDelegate alloc] init]; autoDelegateDict = [[NSMutableDictionary alloc] init]; - } - (id)initWithCoder: (NSCoder *)coder From 306ee54f41321ebe69fb969387662af1635dc966 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Thu, 20 Oct 2011 17:51:59 +0530 Subject: [PATCH 124/180] Bugfix typo by calling [self init] insteed of [super init] in recent changed files (BoshTransport.m/XMPPStream.m) --- Core/Transports/BoshTransport.m | 6 +++--- Core/XMPPStream.m | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index b676013..06708ff 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -96,7 +96,7 @@ - (void)encodeWithCoder: (NSCoder *)coder - (id)initWithCoder: (NSCoder *)coder { - self = [self init]; + self = [super init]; if (self && coder) { self.request = [coder decodeObjectForKey:kPairRequest ]; @@ -187,7 +187,7 @@ - (void)commonInitWithCoder:(NSCoder *)coder - (id)initWithCoder: (NSCoder *)coder { - self = [self init]; + self = [super init]; if (self && coder) { [self commonInitWithCoder:coder]; @@ -1048,7 +1048,7 @@ - (void)commonInitWithCoder:(NSCoder *)coder - (id)initWithCoder: (NSCoder *)coder { - self = [self init]; + self = [super init]; if (self && coder) { [self commonInitWithCoder:coder]; diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 57a7532..9d962f9 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -218,7 +218,7 @@ - (void)commonInitWithCoder:(NSCoder *)coder - (id)initWithCoder: (NSCoder *)coder { - self = [self init]; + self = [super init]; if (self && coder) { [self commonInitWithCoder:coder]; From 9dfb93b42e47278c49ce7e317f9b1a5233c892ed Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Fri, 21 Oct 2011 14:29:17 +0530 Subject: [PATCH 125/180] Memory Leak fix- avoid retaining new root element in xmppStream --- Core/XMPPStream.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 9d962f9..185f25b 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1426,7 +1426,7 @@ - (void)transportDidConnect:(id)sender // Digest Access authentication requires us to know the ID attribute from the element. [rootElement release]; - rootElement = [[self newRootElement] retain]; + rootElement = [self newRootElement] ; if ([self isP2P] && [self isP2PRecipient]) { self.remoteJID = [transport remoteJID]; From 561ca61c13f500b23660823cd93d82720a4755f5 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 1 Nov 2011 12:49:14 +0530 Subject: [PATCH 126/180] Add XMPPTransportProtocol method to notify receiving an empty Packet in BoshTransport --- Core/Transports/BoshTransport.m | 6 ++++++ Core/XMPPTransportProtocol.h | 1 + 2 files changed, 7 insertions(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 06708ff..fa4cab0 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -572,6 +572,7 @@ - (void)trySendingStanzas */ - (void)broadcastStanzas:(NSXMLNode *)body { + if ([body childCount] > 0) { while ([body childCount] > 0) { NSXMLNode *node = [body childAtIndex:0]; if ([node isKindOfClass:[NSXMLElement class]]) { @@ -579,6 +580,11 @@ - (void)broadcastStanzas:(NSXMLNode *)body [multicastDelegate transport:self didReceiveStanza:(NSXMLElement *)node]; } } + } + else + { + [multicastDelegate transportDidReceiveEmptyPacket:self]; + } } #pragma mark - diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index b3d7df5..52450c9 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -69,6 +69,7 @@ - (void)transport:(id )transport willSendStanza:(NSXMLElement *)stanza; - (void)transport:(id )transport didSendStanza:(NSXMLElement *)stanza; - (void)transport:(id )transport didReceiveStanza:(NSXMLElement *)stanza; +- (void)transportDidReceiveEmptyPacket:(id )transport; - (void)transport:(id )transport didReceiveError:(id)error; - (void)transportDidSecure:(id )transport; From 35daf62501efaf950dbe3ff3980626ca0e6a013b Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Fri, 4 Nov 2011 13:00:16 +0530 Subject: [PATCH 127/180] Leak fix- releasing some members of boshTransport --- Core/Transports/BoshTransport.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index d83513b..dcd520f 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -880,6 +880,9 @@ - (void)dealloc [myJID_ release]; [authid release]; [sid_ release]; + [boshWindowManager release]; + [pendingXMPPStanzas release]; + [requestResponsePairs release]; [super dealloc]; } @end From 2d91517c104a6e541eb71b150b0a7870fb5bb6b8 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Fri, 4 Nov 2011 13:00:53 +0530 Subject: [PATCH 128/180] Leak fix- rootElement in XmppStream --- Core/XMPPStream.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 4a65acf..a1c6e70 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1348,7 +1348,7 @@ - (void)transportDidConnect:(id)sender // Digest Access authentication requires us to know the ID attribute from the element. [rootElement release]; - rootElement = [[self newRootElement] retain]; + rootElement = [self newRootElement]; if ([self isP2P] && [self isP2PRecipient]) { self.remoteJID = [transport remoteJID]; From 43bc61ddf35006188ffd5ca4094d5259f5b5fa6a Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 29 Nov 2011 10:58:19 +0530 Subject: [PATCH 129/180] Avoid assign transport directly in XmppStream. Use property transport for same --- Core/XMPPStream.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 185f25b..c8cdc5f 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -82,8 +82,6 @@ @implementation XMPPStream @synthesize customAuthSelector; @synthesize customHandleAuthSelector; -@dynamic transport; - - (void)setTransport:(id)givenTransport { transport = [givenTransport retain]; @@ -209,7 +207,7 @@ - (void)commonInitWithCoder:(NSCoder *)coder myPresence = [[coder decodeObjectForKey:kMYPresence] retain]; rootElement = [[coder decodeObjectForKey:kRootelement] retain]; - transport = [[coder decodeObjectForKey:kTransport] retain]; + self.transport = [coder decodeObjectForKey:kTransport]; multicastDelegate = [[MulticastDelegate alloc] init]; registeredModules = [[MulticastDelegate alloc] init]; From 8fd211fef50ff1e7e542167dd098d169c73e5bf0 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 29 Nov 2011 16:40:34 +0530 Subject: [PATCH 130/180] Add archiving NSHTTPCookieStorage's persisted cookies with bosh transport --- Core/Transports/BoshTransport.m | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 7321b62..b31a8e9 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -983,6 +983,7 @@ - (void)dealloc #define kAuthId @"authid" #define kSid @"sid" #define kUrl @"url" +#define kPersistedCookies @"persistedCookies" - (void)encodeWithCoder: (NSCoder *)coder @@ -1015,6 +1016,9 @@ - (void)encodeWithCoder: (NSCoder *)coder [coder encodeObject:self.authid forKey:kAuthId]; [coder encodeObject:self.sid forKey:kSid]; [coder encodeObject:self.url forKey:kUrl]; + + DDLogRecvPre(@"BOSH: saving all Cookies = %@", [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]); + [coder encodeObject:[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] forKey:kPersistedCookies]; } - (void)commonInitWithCoder:(NSCoder *)coder @@ -1051,6 +1055,13 @@ - (void)commonInitWithCoder:(NSCoder *)coder self.sid= [coder decodeObjectForKey:kSid]; self.url= [coder decodeObjectForKey:kUrl]; + DDLogRecvPre(@"BOSH: restoring sessionCookies = %@", [coder decodeObjectForKey:kPersistedCookies]); + + for ( NSHTTPCookie *cookie in [coder decodeObjectForKey:kPersistedCookies] ) + { + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; + } + multicastDelegate = [[MulticastDelegate alloc] init]; } From ff0e09cef70667f7ed5ee644138786fb2912b7c1 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Wed, 30 Nov 2011 11:13:49 +0530 Subject: [PATCH 131/180] Add pause/resume methods in xmppStream and Bosh Transport. --- Core/Transports/BoshTransport.h | 7 ++- Core/Transports/BoshTransport.m | 36 ++++++++++++++ Core/XMPPStream.h | 9 ++++ Core/XMPPStream.m | 88 ++++++++++++++++++++++++++++++++- Core/XMPPTransportProtocol.h | 3 ++ 5 files changed, 140 insertions(+), 3 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index eb1885d..712aeeb 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -121,6 +121,8 @@ typedef enum { @property(copy) NSURL *url; @property(readonly) NSError *disconnectError; +@property(nonatomic, getter = isPaused) BOOL paused; + /* init Methods */ - (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain; - (id)initWithUrl:(NSURL *)url @@ -156,5 +158,8 @@ typedef enum { - (float)serverXmppStreamVersionNumber; - (BOOL)sendStanza:(NSXMLElement *)stanza; - (BOOL)sendStanzaWithString:(NSString *)string; -- (void)resendRemainingRequests; + +- (void)pause; +- (void)resume; + @end diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index b31a8e9..6f62fb4 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -261,6 +261,7 @@ @implementation BoshTransport @synthesize requests; @synthesize disconnectError = disconnectError_; @synthesize pendingHTTPRequests = pendingHTTPRequests_; +@synthesize paused; #define BoshVersion @"1.6" @@ -392,6 +393,11 @@ - (float)serverXmppStreamVersionNumber - (BOOL)connect:(NSError **)error { + if (self.isPaused) + { + DDLogError(@"BOSH: Need to be unpaused to connect the stream."); + return FALSE; + } DDLogInfo(@"BOSH: Connecting to %@ with jid = %@", self.domain, [self.myJID bare]); if(![self canConnect]) return NO; @@ -402,6 +408,11 @@ - (BOOL)connect:(NSError **)error - (void)restartStream { + if (self.isPaused) + { + DDLogError(@"BOSH: Need to be unpaused to restart the stream."); + return; + } if(![self isConnected]) { DDLogError(@"BOSH: Need to be connected to restart the stream."); @@ -428,6 +439,11 @@ - (void)disconnect - (BOOL)sendStanza:(NSXMLElement *)stanza { + if (self.isPaused) + { + DDLogError(@"BOSH: Need to unpaused to be able to send stanza"); + return NO; + } if (![self isConnected]) { DDLogError(@"BOSH: Need to be connected to be able to send stanza"); @@ -1101,4 +1117,24 @@ - (void)resendRemainingRequests } } +- (void)pause +{ + self.paused = true; + + for (ASIHTTPRequest *request in pendingHTTPRequests_) + { + NSLog(@"---------- \npending request cookies: \n\t requestCookies: %@, \n responseCookies: %@, \n sessionCookies: %@ ",request.requestCookies, request.responseCookies, [ASIHTTPRequest sessionCookies]); + DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); + [request clearDelegatesAndCancel]; + } +} + +- (void)resume +{ + self.paused = false; + + [self resendRemainingRequests]; +} + + @end diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 7528deb..2208088 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -158,6 +158,8 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; @property (nonatomic) SEL customAuthSelector; @property (nonatomic) SEL customHandleAuthSelector; +@property (nonatomic, getter = isPaused) BOOL paused; + @property (nonatomic, retain) id transport; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -417,6 +419,13 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; - (void)autoAddDelegate:(id)delegate toModulesOfClass:(Class)aClass; - (void)removeAutoDelegate:(id)delegate fromModulesOfClass:(Class)aClass; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Pause-Resume XmppStream +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)pause; +- (void)resume; + @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index c8cdc5f..5033e9b 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -82,6 +82,8 @@ @implementation XMPPStream @synthesize customAuthSelector; @synthesize customHandleAuthSelector; +@synthesize paused; + - (void)setTransport:(id)givenTransport { transport = [givenTransport retain]; @@ -284,6 +286,17 @@ - (BOOL)isConnected - (BOOL)connect:(NSError **)errPtr { + if (self.isPaused) + { + if (errPtr) + { + NSString *errMsg = @"Attempting to connect while xmppStream paused."; + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; + } + return NO; + } if (state > STATE_DISCONNECTED) { if (errPtr) @@ -404,6 +417,18 @@ - (void)sendStartTLSRequest - (BOOL)secureConnection:(NSError **)errPtr { + if (self.isPaused) + { + if (errPtr) + { + NSString *errMsg = @"Attempting to secure connection while xmppStream is paused."; + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; + } + return NO; + } + if (state != STATE_CONNECTED) { if (errPtr) @@ -471,6 +496,18 @@ - (BOOL)supportsInBandRegistration **/ - (BOOL)registerWithPassword:(NSString *)password error:(NSError **)errPtr { + if (self.isPaused) + { + if (errPtr) + { + NSString *errMsg = @"Attempting to register while xmppStream paused."; + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; + } + return NO; + } + if (state != STATE_CONNECTED) { if (errPtr) @@ -691,6 +728,18 @@ - (BOOL)supportsDeprecatedDigestAuthentication **/ - (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr { + if (self.isPaused) + { + if (errPtr) + { + NSString *errMsg = @"Attempting to authenticate while xmppStream paused."; + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; + } + return NO; + } + if (state != STATE_CONNECTED) { if (errPtr) @@ -802,6 +851,18 @@ - (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr **/ - (BOOL)authenticateAnonymously:(NSError **)errPtr { + if (self.isPaused) + { + if (errPtr) + { + NSString *errMsg = @"Attempting to connect while xmppStream paused."; + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + *errPtr = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; + } + return NO; + } + if (state != STATE_CONNECTED) { if (errPtr) @@ -855,6 +916,11 @@ - (void)setIsAuthenticated:(BOOL)flag **/ - (BOOL)startCustomAuthenticationWithTarget:(id)target authSelector:(SEL)authSelector handleAuthSelector:(SEL)handleAuthSelector { + if (self.isPaused) + { + return NO; + } + if (state != STATE_CONNECTED) { return NO; @@ -972,7 +1038,7 @@ - (void)sendElement:(NSXMLElement *)element withTag:(long)tag **/ - (void)sendElement:(NSXMLElement *)element { - if (state == STATE_CONNECTED || state == STATE_CUSTOM_AUTH) + if ((state == STATE_CONNECTED || state == STATE_CUSTOM_AUTH) && !self.isPaused ) { [self sendElement:element withTag:0]; } @@ -987,7 +1053,7 @@ - (void)sendElement:(NSXMLElement *)element **/ - (void)sendElement:(NSXMLElement *)element andNotifyMe:(UInt16)tag { - if (state == STATE_CONNECTED) + if (state == STATE_CONNECTED && !self.isPaused) { [self sendElement:element withTag:tag]; } @@ -1777,6 +1843,24 @@ - (void)removeAutoDelegate:(id)delegate fromModulesOfClass:(Class)aClass } } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Pause-Resume XmppStream +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)pause +{ + self.paused = true; + [transport removeDelegate:self]; + [transport pause]; +} + +- (void)resume +{ + [transport addDelegate:self]; + [transport resume]; + self.paused = false; +} + @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 52450c9..e9c2acf 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -53,6 +53,9 @@ - (void)setRemoteJID:(XMPPJID *)jid; - (BOOL)isP2PRecipient; +- (void)pause; +- (void)resume; + @end From ff5c9f97560395af2047942e5cdc6fce1f49d824 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Wed, 30 Nov 2011 11:53:41 +0530 Subject: [PATCH 132/180] Remove all requests on pausing Bosh Transport --- Core/Transports/BoshTransport.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 6f62fb4..39851da 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -1127,6 +1127,7 @@ - (void)pause DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); [request clearDelegatesAndCancel]; } + [pendingHTTPRequests_ removeAllObjects]; } - (void)resume From e7bdc1d6a1c8350d4e897db3f457ba526c4b92cf Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Mon, 5 Dec 2011 11:36:22 +0530 Subject: [PATCH 133/180] Remove transportDidReceiveEmptyPacket from BoshTransport --- Core/Transports/BoshTransport.m | 6 ------ Core/XMPPTransportProtocol.h | 1 - 2 files changed, 7 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 39851da..e097797 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -588,7 +588,6 @@ - (void)trySendingStanzas */ - (void)broadcastStanzas:(NSXMLNode *)body { - if ([body childCount] > 0) { while ([body childCount] > 0) { NSXMLNode *node = [body childAtIndex:0]; if ([node isKindOfClass:[NSXMLElement class]]) { @@ -596,11 +595,6 @@ - (void)broadcastStanzas:(NSXMLNode *)body [multicastDelegate transport:self didReceiveStanza:(NSXMLElement *)node]; } } - } - else - { - [multicastDelegate transportDidReceiveEmptyPacket:self]; - } } #pragma mark - diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index e9c2acf..2055e44 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -72,7 +72,6 @@ - (void)transport:(id )transport willSendStanza:(NSXMLElement *)stanza; - (void)transport:(id )transport didSendStanza:(NSXMLElement *)stanza; - (void)transport:(id )transport didReceiveStanza:(NSXMLElement *)stanza; -- (void)transportDidReceiveEmptyPacket:(id )transport; - (void)transport:(id )transport didReceiveError:(id)error; - (void)transportDidSecure:(id )transport; From af981a2fc8a95439d31c278ec9bd198ec0e37f23 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Thu, 8 Dec 2011 13:04:39 +0530 Subject: [PATCH 134/180] BoshTransport: Remove and cancel requests in pendingHTTPRequests_ at same program point. --- Core/Transports/BoshTransport.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index dcd520f..383d58e 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -611,6 +611,7 @@ - (void)handleDisconnection DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); [request clearDelegatesAndCancel]; } + [pendingHTTPRequests_ removeAllObjects]; [multicastDelegate transportDidDisconnect:self]; } @@ -869,6 +870,11 @@ - (NSNumber *)numberFromString:(NSString *)stringNumber - (void)dealloc { + for (ASIHTTPRequest *request in pendingHTTPRequests_) + { + DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); + [request clearDelegatesAndCancel]; + } [pendingHTTPRequests_ removeAllObjects]; [pendingHTTPRequests_ release]; From 3e4231074e91001c2420020a0096cb5727365ace Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Fri, 9 Dec 2011 18:15:58 +0530 Subject: [PATCH 135/180] BoshTransport: Allow disconnection while in state CONNECTING. --- Core/Transports/BoshTransport.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 383d58e..b43fd89 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -356,7 +356,7 @@ - (void)restartStream - (void)disconnect { - if(![self isConnected]) + if(state != CONNECTED && state != CONNECTING ) { DDLogError(@"BOSH: Need to be connected to disconnect"); return; @@ -591,9 +591,11 @@ - (void)createSessionResponseHandler:(NSXMLElement *)parsedResponse /* Not doing anything with namespaces right now - because chirkut doesn't send it */ //NSArray *responseNamespaces = [rootElement namespaces]; + if ( state == CONNECTING ) { state = CONNECTED; [multicastDelegate transportDidConnect:self]; [multicastDelegate transportDidStartNegotiation:self]; + } } - (void)handleDisconnection From b17d27a232c27f9bc13416c614d0dfd9cd85412f Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Tue, 27 Dec 2011 12:31:51 +0530 Subject: [PATCH 136/180] Add |supportsPause| for transport and xmppStream. Also rename the |paused| property of both transport and xmppStream to isPaused. --- Core/Transports/BoshTransport.h | 2 +- Core/Transports/BoshTransport.m | 10 +++++++--- Core/Transports/XMPPSocketTransport.m | 4 ++++ Core/XMPPStream.h | 3 ++- Core/XMPPStream.m | 22 +++++++++++++++------- Core/XMPPTransportProtocol.h | 2 ++ 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 712aeeb..32746a2 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -121,7 +121,7 @@ typedef enum { @property(copy) NSURL *url; @property(readonly) NSError *disconnectError; -@property(nonatomic, getter = isPaused) BOOL paused; +@property(readonly) BOOL isPaused; /* init Methods */ - (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index e097797..bad21cf 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -261,7 +261,7 @@ @implementation BoshTransport @synthesize requests; @synthesize disconnectError = disconnectError_; @synthesize pendingHTTPRequests = pendingHTTPRequests_; -@synthesize paused; +@synthesize isPaused; #define BoshVersion @"1.6" @@ -1111,9 +1111,13 @@ - (void)resendRemainingRequests } } +- (BOOL)supportsPause { + return YES; +} + - (void)pause { - self.paused = true; + isPaused = true; for (ASIHTTPRequest *request in pendingHTTPRequests_) { @@ -1126,7 +1130,7 @@ - (void)pause - (void)resume { - self.paused = false; + isPaused = false; [self resendRemainingRequests]; } diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index 4409985..bf8b8dc 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -514,4 +514,8 @@ - (void)srvResolver:(RFSRVResolver *)sender didNotResolveSRVWithError:(NSError * [self tryNextSrvResult]; } +- (BOOL)supportsPause { + return NO; +} + @end diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 2208088..b9243f2 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -158,7 +158,7 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; @property (nonatomic) SEL customAuthSelector; @property (nonatomic) SEL customHandleAuthSelector; -@property (nonatomic, getter = isPaused) BOOL paused; +@property (nonatomic, readonly) BOOL isPaused; @property (nonatomic, retain) id transport; @@ -423,6 +423,7 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode; #pragma mark Pause-Resume XmppStream //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +- (BOOL)supportsPause; - (void)pause; - (void)resume; diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 5033e9b..8639a7d 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -82,7 +82,7 @@ @implementation XMPPStream @synthesize customAuthSelector; @synthesize customHandleAuthSelector; -@synthesize paused; +@synthesize isPaused; - (void)setTransport:(id)givenTransport { @@ -1847,18 +1847,26 @@ - (void)removeAutoDelegate:(id)delegate fromModulesOfClass:(Class)aClass #pragma mark Pause-Resume XmppStream //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +- (BOOL)supportsPause { + return [transport supportsPause]; +} + - (void)pause { - self.paused = true; - [transport removeDelegate:self]; - [transport pause]; + if ([transport supportsPause]) { + isPaused = true; + [transport removeDelegate:self]; + [transport pause]; + } } - (void)resume { - [transport addDelegate:self]; - [transport resume]; - self.paused = false; + if ([transport supportsPause]) { + [transport addDelegate:self]; + [transport resume]; + isPaused = false; + } } @end diff --git a/Core/XMPPTransportProtocol.h b/Core/XMPPTransportProtocol.h index 2055e44..4d5dbcc 100644 --- a/Core/XMPPTransportProtocol.h +++ b/Core/XMPPTransportProtocol.h @@ -43,6 +43,7 @@ - (float)serverXmppStreamVersionNumber; - (BOOL)sendStanza:(NSXMLElement *)stanza; - (BOOL)sendStanzaWithString:(NSString *)string; +- (BOOL)supportsPause; @optional - (void)secure; @@ -53,6 +54,7 @@ - (void)setRemoteJID:(XMPPJID *)jid; - (BOOL)isP2PRecipient; +- (BOOL)isPaused; - (void)pause; - (void)resume; From 415e45df7ab8388af5dd26b107678ddca35dc874 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Tue, 27 Dec 2011 17:17:18 +0530 Subject: [PATCH 137/180] Fix whitespace. --- Core/XMPPStream.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 8639a7d..03c3b3e 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -184,7 +184,7 @@ - (void)dealloc //#define @"SEL customAuthSelector" //#define @"SEL customHandleAuthSelector" -- (void)encodeWithCoder: (NSCoder *)coder +- (void)encodeWithCoder:(NSCoder *)coder { [coder encodeInt:state forKey:kState]; [coder encodeInt:flags forKey:kFlags]; @@ -216,7 +216,7 @@ - (void)commonInitWithCoder:(NSCoder *)coder autoDelegateDict = [[NSMutableDictionary alloc] init]; } -- (id)initWithCoder: (NSCoder *)coder +- (id)initWithCoder:(NSCoder *)coder { self = [super init]; if (self && coder) From af2ee9c766f6d1310e767732e2742f74356e82c6 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Tue, 27 Dec 2011 17:18:11 +0530 Subject: [PATCH 138/180] XMPPStream: Should not add or remove self as a transport delegate during pause/resume. --- Core/XMPPStream.m | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 03c3b3e..8bf18e6 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1854,8 +1854,7 @@ - (BOOL)supportsPause { - (void)pause { if ([transport supportsPause]) { - isPaused = true; - [transport removeDelegate:self]; + isPaused = YES; [transport pause]; } } @@ -1863,9 +1862,8 @@ - (void)pause - (void)resume { if ([transport supportsPause]) { - [transport addDelegate:self]; [transport resume]; - isPaused = false; + isPaused = NO; } } From 7206f0b2d16db3e1d83d87c645f1c0231f6eaa9d Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Tue, 27 Dec 2011 17:18:27 +0530 Subject: [PATCH 139/180] Remove frivolous NSLog()s. --- Core/Transports/BoshTransport.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index bad21cf..aba59fb 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -1027,8 +1027,7 @@ - (void)encodeWithCoder: (NSCoder *)coder [coder encodeObject:self.sid forKey:kSid]; [coder encodeObject:self.url forKey:kUrl]; - DDLogRecvPre(@"BOSH: saving all Cookies = %@", [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]); - [coder encodeObject:[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] forKey:kPersistedCookies]; + [coder encodeObject:[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] forKey:kPersistedCookies]; } - (void)commonInitWithCoder:(NSCoder *)coder @@ -1121,7 +1120,6 @@ - (void)pause for (ASIHTTPRequest *request in pendingHTTPRequests_) { - NSLog(@"---------- \npending request cookies: \n\t requestCookies: %@, \n responseCookies: %@, \n sessionCookies: %@ ",request.requestCookies, request.responseCookies, [ASIHTTPRequest sessionCookies]); DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); [request clearDelegatesAndCancel]; } From 486e9f59e35949a7615f662679e5bdf322d69d3c Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Thu, 29 Dec 2011 11:56:06 +0530 Subject: [PATCH 140/180] BoshTransport: enable sending pending xmpp stanzas while transport is in state Disconeecting --- Core/Transports/BoshTransport.m | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index b43fd89..62ff473 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -476,15 +476,16 @@ - (void)trySendingStanzas { if( state != DISCONNECTED && ![boshWindowManager isWindowFull] ) { - if (state == CONNECTED) { - if ( [pendingXMPPStanzas count] > 0 ) - { - [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas - attributes:nil - namespaces:nil]; - [pendingXMPPStanzas removeAllObjects]; - } - else if ([boshWindowManager isWindowEmpty]) + if ( [pendingXMPPStanzas count] > 0 && ( state == CONNECTED || state == DISCONNECTING )) + { + [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas + attributes:nil + namespaces:nil]; + [pendingXMPPStanzas removeAllObjects]; + + } + else if (state == CONNECTED) { + if ([boshWindowManager isWindowEmpty]) { [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:nil From 4e148317c9f897b3b540769501412aa31bdd9707 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Thu, 29 Dec 2011 12:48:27 +0530 Subject: [PATCH 141/180] BoshTransport: Send 'terminate' with pending xmpp stanzas in same request --- Core/Transports/BoshTransport.m | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 62ff473..ebb7391 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -466,26 +466,25 @@ - (void)makeBodyAndSendHTTPRequestWithPayload:(NSArray *)bodyPayload [requestPayload release]; } -- (void)sendTerminateRequest +- (void)sendTerminateRequestWithPayload:(NSArray *)bodyPayload { NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"terminate", @"type", nil]; - [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:attr namespaces:nil]; + [self makeBodyAndSendHTTPRequestWithPayload:bodyPayload attributes:attr namespaces:nil]; } - (void)trySendingStanzas { if( state != DISCONNECTED && ![boshWindowManager isWindowFull] ) { - if ( [pendingXMPPStanzas count] > 0 && ( state == CONNECTED || state == DISCONNECTING )) - { - [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas - attributes:nil - namespaces:nil]; - [pendingXMPPStanzas removeAllObjects]; - - } - else if (state == CONNECTED) { - if ([boshWindowManager isWindowEmpty]) + if (state == CONNECTED) { + if ( [pendingXMPPStanzas count] > 0 ) + { + [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas + attributes:nil + namespaces:nil]; + [pendingXMPPStanzas removeAllObjects]; + } + else if ([boshWindowManager isWindowEmpty]) { [self makeBodyAndSendHTTPRequestWithPayload:nil attributes:nil @@ -494,7 +493,8 @@ - (void)trySendingStanzas } else if(state == DISCONNECTING) { - [self sendTerminateRequest]; + [self sendTerminateRequestWithPayload:pendingXMPPStanzas]; + [pendingXMPPStanzas removeAllObjects]; state = TERMINATING; } else if ([boshWindowManager isWindowEmpty] && state == TERMINATING) From 336f3e9dda5a5958b7fd4eaa0279fc0bcf17b43a Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Thu, 29 Dec 2011 20:50:35 +0530 Subject: [PATCH 142/180] BoshTransport: YES or NO should be assigned to a BOOL variable. --- Core/Transports/BoshTransport.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 8dca47c..e5a9737 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -1098,19 +1098,19 @@ - (id)initWithCoder: (NSCoder *)coder */ - (void)resendRemainingRequests { - BOOL didSentSomething = false; + BOOL didSendSomething = NO; for ( NSNumber *ridNumber in [requestResponsePairs allKeys ]) { long long rid = [ridNumber longLongValue]; RequestResponsePair *pair = [requestResponsePairs objectForLongLongKey:rid]; if ( !pair.response ) { - didSentSomething = true; + didSendSomething = YES; [self sendHTTPRequestWithBody:pair.request rid:rid]; } } - if ( !didSentSomething ) + if ( !didSendSomething ) { // if we havent sent anything, send an empty packet. [self trySendingStanzas]; From 6eaccf24446ce846808d22c72f3f644530aef6ec Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Thu, 29 Dec 2011 22:48:30 +0530 Subject: [PATCH 143/180] Init pendingHTTPRequests_ while unarchiving BOSH transport. Fixes iPwm #508. --- Core/Transports/BoshTransport.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index e5a9737..6e3d49c 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -1070,6 +1070,8 @@ - (void)commonInitWithCoder:(NSCoder *)coder self.authid= [coder decodeObjectForKey:kAuthId]; self.sid= [coder decodeObjectForKey:kSid]; self.url= [coder decodeObjectForKey:kUrl]; + + pendingHTTPRequests_ = [[NSMutableSet alloc] initWithCapacity:2]; DDLogRecvPre(@"BOSH: restoring sessionCookies = %@", [coder decodeObjectForKey:kPersistedCookies]); From 684ab94cd29203b38482e579261cdf4885c70557 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Thu, 29 Dec 2011 22:53:32 +0530 Subject: [PATCH 144/180] Fix indentation. --- Core/Transports/BoshTransport.m | 40 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 6e3d49c..979ac5b 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -1070,16 +1070,14 @@ - (void)commonInitWithCoder:(NSCoder *)coder self.authid= [coder decodeObjectForKey:kAuthId]; self.sid= [coder decodeObjectForKey:kSid]; self.url= [coder decodeObjectForKey:kUrl]; - - pendingHTTPRequests_ = [[NSMutableSet alloc] initWithCapacity:2]; - DDLogRecvPre(@"BOSH: restoring sessionCookies = %@", [coder decodeObjectForKey:kPersistedCookies]); - - for ( NSHTTPCookie *cookie in [coder decodeObjectForKey:kPersistedCookies] ) - { - [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; - } - + pendingHTTPRequests_ = [[NSMutableSet alloc] initWithCapacity:2]; + + for ( NSHTTPCookie *cookie in [coder decodeObjectForKey:kPersistedCookies] ) + { + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; + } + multicastDelegate = [[MulticastDelegate alloc] init]; } @@ -1120,26 +1118,26 @@ - (void)resendRemainingRequests } - (BOOL)supportsPause { - return YES; + return YES; } - (void)pause { - isPaused = true; - - for (ASIHTTPRequest *request in pendingHTTPRequests_) - { - DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); - [request clearDelegatesAndCancel]; - } - [pendingHTTPRequests_ removeAllObjects]; + isPaused = true; + + for (ASIHTTPRequest *request in pendingHTTPRequests_) + { + DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); + [request clearDelegatesAndCancel]; + } + [pendingHTTPRequests_ removeAllObjects]; } - (void)resume { - isPaused = false; - - [self resendRemainingRequests]; + isPaused = false; + + [self resendRemainingRequests]; } From 2dc0fa9f1651a346ec0b981d939e1c86efa7226c Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Fri, 30 Dec 2011 10:41:40 +0530 Subject: [PATCH 145/180] BoshTransport: Simlify logic of trySendingStanzas method. because makeBodyAndSendHTTPRequestWithPayload:attributes:namespaces: method has same behaviour for empty array as payload And nil. --- Core/Transports/BoshTransport.m | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index ebb7391..5bd3b3d 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -477,19 +477,13 @@ - (void)trySendingStanzas if( state != DISCONNECTED && ![boshWindowManager isWindowFull] ) { if (state == CONNECTED) { - if ( [pendingXMPPStanzas count] > 0 ) + if ( [pendingXMPPStanzas count] > 0 || [boshWindowManager isWindowEmpty] ) { [self makeBodyAndSendHTTPRequestWithPayload:pendingXMPPStanzas attributes:nil namespaces:nil]; [pendingXMPPStanzas removeAllObjects]; } - else if ([boshWindowManager isWindowEmpty]) - { - [self makeBodyAndSendHTTPRequestWithPayload:nil - attributes:nil - namespaces:nil]; - } } else if(state == DISCONNECTING) { From 69af57f91306bbbf7967cdce29bd3838e716f6b3 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 3 Jan 2012 12:24:52 +0530 Subject: [PATCH 146/180] Make bosh properties nonatomic --- Core/Transports/BoshTransport.h | 38 ++++++++++++++++----------------- Core/Transports/BoshTransport.m | 4 ++-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Core/Transports/BoshTransport.h b/Core/Transports/BoshTransport.h index 32746a2..8c46395 100644 --- a/Core/Transports/BoshTransport.h +++ b/Core/Transports/BoshTransport.h @@ -69,8 +69,8 @@ typedef enum { NSMutableSet *receivedRids; } -@property unsigned int windowSize; -@property (readonly) long long maxRidReceived; +@property (nonatomic) unsigned int windowSize; +@property (nonatomic, readonly) long long maxRidReceived; - (id)initWithRid:(long long)rid; - (void)sentRequestForRid:(long long)rid; @@ -105,23 +105,23 @@ typedef enum { unsigned int requests; } -@property(retain) XMPPJID *myJID; -@property(assign) unsigned int wait; -@property(assign) unsigned int hold; -@property(copy) NSString *lang; -@property(copy) NSString *domain; -@property(copy) NSString *routeProtocol; -@property(copy) NSString *host; -@property(assign) unsigned int port; -@property(assign) unsigned int inactivity; -@property(readonly) BOOL secure; -@property(readonly) unsigned int requests; -@property(copy) NSString *authid; -@property(copy) NSString *sid; -@property(copy) NSURL *url; -@property(readonly) NSError *disconnectError; - -@property(readonly) BOOL isPaused; +@property (nonatomic, retain) XMPPJID *myJID; +@property (nonatomic, assign) unsigned int wait; +@property (nonatomic, assign) unsigned int hold; +@property (nonatomic, copy) NSString *lang; +@property (nonatomic, copy) NSString *domain; +@property (nonatomic, copy) NSString *routeProtocol; +@property (nonatomic, copy) NSString *host; +@property (nonatomic, assign) unsigned int port; +@property (nonatomic, assign) unsigned int inactivity; +@property (nonatomic, readonly) BOOL secure; +@property (nonatomic, readonly) unsigned int requests; +@property (nonatomic, copy) NSString *authid; +@property (nonatomic, copy) NSString *sid; +@property (nonatomic, copy) NSURL *url; +@property (nonatomic, readonly) NSError *disconnectError; + +@property(nonatomic, readonly) BOOL isPaused; /* init Methods */ - (id)initWithUrl:(NSURL *)url forDomain:(NSString *)domain; diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 5293085..deb5260 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -210,8 +210,8 @@ - (id)initWithCoder: (NSCoder *)coder static const NSString *XMPP_NS = @"urn:xmpp:xbosh"; @interface BoshTransport() -@property(readwrite, assign) NSError *disconnectError; -@property(retain) NSMutableSet *pendingHTTPRequests; +@property (nonatomic, readwrite, assign) NSError *disconnectError; +@property (nonatomic, retain) NSMutableSet *pendingHTTPRequests; - (void)setInactivityFromString:(NSString *)givenInactivity; - (void)setSecureFromString:(NSString *)isSecure; From e82189f6ed60df8a3a9cea22e394cd136b6a1587 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 3 Jan 2012 12:29:52 +0530 Subject: [PATCH 147/180] Make XMPPSocketTransport properties nonatomic --- Core/Transports/XMPPSocketTransport.h | 8 ++++---- Core/Transports/XMPPSocketTransport.m | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/Transports/XMPPSocketTransport.h b/Core/Transports/XMPPSocketTransport.h index 2f42864..f0c5950 100644 --- a/Core/Transports/XMPPSocketTransport.h +++ b/Core/Transports/XMPPSocketTransport.h @@ -79,10 +79,10 @@ enum xmppSocketState { NSArray *srvResults; NSUInteger srvResultsIndex; } -@property (readonly, copy) NSString *host; -@property (retain) XMPPJID *myJID; -@property (retain) XMPPJID *remoteJID; -@property (readonly) BOOL isP2PRecipient; +@property (nonatomic, readonly, copy) NSString *host; +@property (nonatomic, retain) XMPPJID *myJID; +@property (nonatomic, retain) XMPPJID *remoteJID; +@property (nonatomic, readonly) BOOL isP2PRecipient; - (id)init; - (id)initWithHost:(NSString *)host port:(UInt16)port; diff --git a/Core/Transports/XMPPSocketTransport.m b/Core/Transports/XMPPSocketTransport.m index bf8b8dc..2baa67a 100644 --- a/Core/Transports/XMPPSocketTransport.m +++ b/Core/Transports/XMPPSocketTransport.m @@ -15,8 +15,8 @@ #import "RFSRVResolver.h" @interface XMPPSocketTransport () -@property (readwrite, copy) NSString *host; -@property (readwrite, assign) UInt16 port; +@property (nonatomic, readwrite, copy) NSString *host; +@property (nonatomic, readwrite, assign) UInt16 port; - (BOOL)sendString:(NSString *)string; - (void)sendOpeningNegotiation; @end From 2887401f7a9cce4382d183624034b3188702730c Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Wed, 4 Jan 2012 14:29:55 +0530 Subject: [PATCH 148/180] XMPPRoster: Make hasRoster/setHasRoster methods public --- Roster/XMPPRoster.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Roster/XMPPRoster.h b/Roster/XMPPRoster.h index 833cf78..295377c 100644 --- a/Roster/XMPPRoster.h +++ b/Roster/XMPPRoster.h @@ -56,6 +56,9 @@ - (id )userForJID:(XMPPJID *)jid; - (id )resourceForJID:(XMPPJID *)jid; +- (BOOL)hasRoster; +- (void)setHasRoster:(BOOL)flag; + @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From d4fede5831b0ab48a91bbe5be7a25ad40807474a Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Wed, 4 Jan 2012 14:54:01 +0530 Subject: [PATCH 149/180] XmppStream: RemoveDelegate self from Transport on dealloc --- Core/XMPPStream.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 8bf18e6..0a47413 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -141,6 +141,8 @@ - (NSXMLElement *)newRootElement **/ - (void)dealloc { + [transport removeDelegate:self]; + [transport release]; [multicastDelegate release]; From bdc71e7265397d44e54dc6540c535916aa36ec66 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Fri, 13 Jan 2012 21:29:40 +0530 Subject: [PATCH 150/180] BoshTransport: Dont save retryCounter/ nextRequestDelay on pause, and on resume, reset to initial values --- Core/Transports/BoshTransport.m | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index deb5260..117cd37 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -978,9 +978,6 @@ - (void)dealloc #define kDisconnectError_ @"disconnectError_" -#define kRetryCounter @"retryCounter" -#define kNextRequestDelay @"nextRequestDelay" - #define kMyJID @"myJID" #define kWait @"wait" #define kHold @"hold" @@ -1011,9 +1008,6 @@ - (void)encodeWithCoder: (NSCoder *)coder [coder encodeObject:disconnectError_ forKey:kDisconnectError_]; - [coder encodeInt:retryCounter forKey:kRetryCounter]; - [coder encodeDouble:nextRequestDelay forKey:kNextRequestDelay]; - [coder encodeObject:self.myJID forKey:kMyJID]; [coder encodeInt:self.wait forKey:kWait]; [coder encodeInt:self.hold forKey:kHold]; @@ -1046,11 +1040,7 @@ - (void)commonInitWithCoder:(NSCoder *)coder requestResponsePairs = [[coder decodeObjectForKey:kRequestResponsePairs] retain]; disconnectError_ = [[coder decodeObjectForKey:kDisconnectError_] retain]; - - retryCounter = [coder decodeIntForKey:kRetryCounter]; - nextRequestDelay= [coder decodeDoubleForKey:kNextRequestDelay]; - - + self.myJID= [coder decodeObjectForKey:kMyJID]; self.wait= [coder decodeIntForKey:kWait]; self.hold= [coder decodeIntForKey:kHold]; @@ -1073,6 +1063,9 @@ - (void)commonInitWithCoder:(NSCoder *)coder [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; } + retryCounter = 0; + nextRequestDelay= INITIAL_RETRY_DELAY; + multicastDelegate = [[MulticastDelegate alloc] init]; } From 5c2fdacb308890ac0509219178f17ac83540651d Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Mon, 16 Jan 2012 17:29:30 +0530 Subject: [PATCH 151/180] BoshTransport: Reset retryCounter on resumption --- Core/Transports/BoshTransport.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 117cd37..4425ec7 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -1126,6 +1126,10 @@ - (void)resume isPaused = false; [self resendRemainingRequests]; + + // reset retry timeout + retryCounter = 0; + nextRequestDelay= INITIAL_RETRY_DELAY; } From 5bdfa21a2e734178c7b71e585850b702aa4aa749 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Mon, 16 Jan 2012 17:30:44 +0530 Subject: [PATCH 152/180] BoshTransport: Enable reconnection on DISCONNECTING state as well --- Core/Transports/BoshTransport.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 4425ec7..ecd655d 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -731,7 +731,7 @@ - (void)requestFailed:(ASIHTTPRequest *)request [pendingHTTPRequests_ removeObject:request]; BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && ( retryCounter < RETRY_COUNT_LIMIT ) && - (state == CONNECTED); + (state == CONNECTED || state == DISCONNECTING); if(shouldReconnect) { DDLogInfo(@"Resending the request"); From 6661d2ad2a226cfc42c74674289be37b03b17306 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Mon, 16 Jan 2012 17:33:32 +0530 Subject: [PATCH 153/180] BoshTransport: Limit max retryRequestDelay --- Core/Transports/BoshTransport.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index ecd655d..45b9ade 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -739,7 +739,9 @@ - (void)requestFailed:(ASIHTTPRequest *)request withObject:request afterDelay:nextRequestDelay]; ++retryCounter; - nextRequestDelay *= DELAY_EXPONENTIATING_FACTOR; + if (nextRequestDelay < DELAY_UPPER_LIMIT) { + nextRequestDelay *= DELAY_EXPONENTIATING_FACTOR; + } } else { From 5ad7a95523ec3f7f780970161638f081bcbf0895 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Mon, 16 Jan 2012 17:35:31 +0530 Subject: [PATCH 154/180] Set max-delay-limit before reconnect to 64 sec --- Core/Transports/BoshTransport.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 45b9ade..361477c 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -201,7 +201,7 @@ - (id)initWithCoder: (NSCoder *)coder static const int RETRY_COUNT_LIMIT = 25; static const NSTimeInterval RETRY_DELAY = 1.0; -static const NSTimeInterval DELAY_UPPER_LIMIT = 128.0; +static const NSTimeInterval DELAY_UPPER_LIMIT = 64.0; static const NSTimeInterval DELAY_EXPONENTIATING_FACTOR = 2.0; static const NSTimeInterval INITIAL_RETRY_DELAY = 1.0; From b54c661e84911772e99515ddb947fa42ea4c2bbf Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 16 Jan 2012 18:52:44 +0530 Subject: [PATCH 155/180] BoshTransport: Limit |nextRequestDelay| to DELAY_UPPER_LIMIT. --- Core/Transports/BoshTransport.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 361477c..633fefc 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -741,6 +741,7 @@ - (void)requestFailed:(ASIHTTPRequest *)request ++retryCounter; if (nextRequestDelay < DELAY_UPPER_LIMIT) { nextRequestDelay *= DELAY_EXPONENTIATING_FACTOR; + nextRequestDelay = MIN(nextRequestDelay, DELAY_UPPER_LIMIT); } } else From 2a4e777411cc15b93aa5f45d9b1a2c809dfcc5c4 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 16 Jan 2012 18:53:21 +0530 Subject: [PATCH 156/180] BoshTransport: Reduce DELAY_UPPER_LIMIT to 32 seconds. --- Core/Transports/BoshTransport.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 633fefc..4f4ce69 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -201,7 +201,7 @@ - (id)initWithCoder: (NSCoder *)coder static const int RETRY_COUNT_LIMIT = 25; static const NSTimeInterval RETRY_DELAY = 1.0; -static const NSTimeInterval DELAY_UPPER_LIMIT = 64.0; +static const NSTimeInterval DELAY_UPPER_LIMIT = 32.0; static const NSTimeInterval DELAY_EXPONENTIATING_FACTOR = 2.0; static const NSTimeInterval INITIAL_RETRY_DELAY = 1.0; From 9f65e9a3e3e4b33dee753b94335ce7fa804c96b8 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 16 Jan 2012 18:53:49 +0530 Subject: [PATCH 157/180] Fix formatting. --- Core/Transports/BoshTransport.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 4f4ce69..b5a10a3 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -1132,7 +1132,7 @@ - (void)resume // reset retry timeout retryCounter = 0; - nextRequestDelay= INITIAL_RETRY_DELAY; + nextRequestDelay = INITIAL_RETRY_DELAY; } From 750a78d9a49e931f1ae41216bce92eda8878bf3b Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 16 Jan 2012 18:57:08 +0530 Subject: [PATCH 158/180] BoshTransport: Ensure that queued performSelector: requests are cancelled. --- Core/Transports/BoshTransport.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index b5a10a3..bf18952 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -945,6 +945,7 @@ - (NSNumber *)numberFromString:(NSString *)stringNumber - (void)dealloc { + [NSObject cancelPreviousPerformRequestsWithTarget:self]; for (ASIHTTPRequest *request in pendingHTTPRequests_) { DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); From 46b687916c0c66ca66d2d7101b90ad2c8f3cb988 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 17 Jan 2012 15:16:09 +0530 Subject: [PATCH 159/180] BoshTransport: Cancel scheduled performSelector:withDelay: when cancelling requests. A failed request schedules method |resendRequest:| after some delay. Therefore, while cancelling a request, we shud also cancel this scheduled execution of resending request. --- Core/Transports/BoshTransport.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index bf18952..2aafd65 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -681,6 +681,7 @@ - (void)handleDisconnection for (ASIHTTPRequest *request in pendingHTTPRequests_) { DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resendRequest:) object:request]; [request clearDelegatesAndCancel]; } [pendingHTTPRequests_ removeAllObjects]; @@ -1119,6 +1120,7 @@ - (void)pause for (ASIHTTPRequest *request in pendingHTTPRequests_) { + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resendRequest:) object:request]; DDLogWarn(@"Cancelling pending request with rid = %qi", [self getRidFromRequest:request]); [request clearDelegatesAndCancel]; } From 29598a1bb59efabea8f8c697bf932ecc509a9cc0 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Thu, 19 Jan 2012 17:48:55 +0530 Subject: [PATCH 160/180] Cache values returned by XMPPElement's -from and -to methods. Unfortunately we have to use associative references and not instance variables to cache these values. See note in the implementation of -[XMPPElement to] to understand why. --- Core/XMPPElement.m | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Core/XMPPElement.m b/Core/XMPPElement.m index 0e0758d..b39947d 100644 --- a/Core/XMPPElement.m +++ b/Core/XMPPElement.m @@ -1,8 +1,12 @@ #import "XMPPElement.h" #import "XMPPJID.h" #import "NSXMLElementAdditions.h" +#import +static char toKey; +static char fromKey; + @implementation XMPPElement //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -69,12 +73,28 @@ - (NSString *)fromStr - (XMPPJID *)to { - return [XMPPJID jidWithString:[self toStr]]; + // Cache the value returned by this method + // Unfortunately we can't use instance variables here since the + // xmppframework changes instances of NSXMLElement directly to XMPPIQ, + // XMPPPresence, etc. (subclasses of XMPPElement) using object_setClass(). + // This forbids us from adding any instance variables to these classes. + // See note in the implementation of +[XMPPIQ initialize] for more. + XMPPJID *to = objc_getAssociatedObject(self, &toKey); + if (to == nil) { + to = [XMPPJID jidWithString:[self toStr]]; + objc_setAssociatedObject(self, &toKey, to, OBJC_ASSOCIATION_RETAIN); + } + return to; } - (XMPPJID *)from { - return [XMPPJID jidWithString:[self fromStr]]; + XMPPJID *from = objc_getAssociatedObject(self, &fromKey); + if (from == nil) { + from = [XMPPJID jidWithString:[self fromStr]]; + objc_setAssociatedObject(self, &fromKey, from, OBJC_ASSOCIATION_RETAIN); + } + return from; } @end From a4da712d2de0ba568918011aada1b0789f0d6de7 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 20 Jan 2012 10:49:51 +0530 Subject: [PATCH 161/180] Cache values returned by LibIDN. --- Utilities/LibIDN.m | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Utilities/LibIDN.m b/Utilities/LibIDN.m index 8d0b132..70589bf 100644 --- a/Utilities/LibIDN.m +++ b/Utilities/LibIDN.m @@ -2,12 +2,28 @@ #import "stringprep.h" +static NSMutableDictionary *nodeDictionary; +static NSMutableDictionary *domainDictionary; +static NSMutableDictionary *resourceDictionary; + + @implementation LibIDN ++ (void)initialize { + if (self == [LibIDN class]) { + nodeDictionary = [[NSMutableDictionary alloc] init]; + domainDictionary = [[NSMutableDictionary alloc] init]; + resourceDictionary = [[NSMutableDictionary alloc] init]; + } +} + + (NSString *)prepNode:(NSString *)node { if(node == nil) return nil; + NSString *cachedValue = [nodeDictionary objectForKey:node]; + if (cachedValue != nil) return cachedValue; + // Each allowable portion of a JID MUST NOT be more than 1023 bytes in length. // We make the buffer just big enough to hold a null-terminated string of this length. char buf[1024]; @@ -23,6 +39,9 @@ + (NSString *)prepDomain:(NSString *)domain { if(domain == nil) return nil; + NSString *cachedValue = [nodeDictionary objectForKey:domain]; + if (cachedValue != nil) return cachedValue; + // Each allowable portion of a JID MUST NOT be more than 1023 bytes in length. // We make the buffer just big enough to hold a null-terminated string of this length. char buf[1024]; @@ -38,6 +57,9 @@ + (NSString *)prepResource:(NSString *)resource { if(resource == nil) return nil; + NSString *cachedValue = [nodeDictionary objectForKey:resource]; + if (cachedValue != nil) return cachedValue; + // Each allowable portion of a JID MUST NOT be more than 1023 bytes in length. // We make the buffer just big enough to hold a null-terminated string of this length. char buf[1024]; From ac3fe3499bcd3793045e3a99a596c4b491b36b2f Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 20 Jan 2012 15:10:47 +0530 Subject: [PATCH 162/180] Bugfix: Values returned by LibIDN weren't actually being cached. --- Utilities/LibIDN.m | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Utilities/LibIDN.m b/Utilities/LibIDN.m index 70589bf..56467bc 100644 --- a/Utilities/LibIDN.m +++ b/Utilities/LibIDN.m @@ -32,14 +32,16 @@ + (NSString *)prepNode:(NSString *)node if(stringprep_xmpp_nodeprep(buf, sizeof(buf)) != 0) return nil; - return [NSString stringWithUTF8String:buf]; + NSString *returnValue = [NSString stringWithUTF8String:buf]; + [nodeDictionary setObject:returnValue forKey:node]; + return returnValue; } + (NSString *)prepDomain:(NSString *)domain { if(domain == nil) return nil; - NSString *cachedValue = [nodeDictionary objectForKey:domain]; + NSString *cachedValue = [domainDictionary objectForKey:domain]; if (cachedValue != nil) return cachedValue; // Each allowable portion of a JID MUST NOT be more than 1023 bytes in length. @@ -50,14 +52,16 @@ + (NSString *)prepDomain:(NSString *)domain if(stringprep_nameprep(buf, sizeof(buf)) != 0) return nil; - return [NSString stringWithUTF8String:buf]; + NSString *returnValue = [NSString stringWithUTF8String:buf]; + [domainDictionary setObject:returnValue forKey:domain]; + return returnValue; } + (NSString *)prepResource:(NSString *)resource { if(resource == nil) return nil; - NSString *cachedValue = [nodeDictionary objectForKey:resource]; + NSString *cachedValue = [resourceDictionary objectForKey:resource]; if (cachedValue != nil) return cachedValue; // Each allowable portion of a JID MUST NOT be more than 1023 bytes in length. @@ -68,7 +72,9 @@ + (NSString *)prepResource:(NSString *)resource if(stringprep_xmpp_resourceprep(buf, sizeof(buf)) != 0) return nil; - return [NSString stringWithUTF8String:buf]; + NSString *returnValue = [NSString stringWithUTF8String:buf]; + [resourceDictionary setObject:returnValue forKey:resource]; + return returnValue; } @end From e79456573fbc16209f2672ae363872c9163d8286 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 31 Jan 2012 16:56:43 +0530 Subject: [PATCH 163/180] Disable presence cache for unavailable roster. --- Roster/XMPPRoster.m | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/Roster/XMPPRoster.m b/Roster/XMPPRoster.m index 0520404..da60845 100644 --- a/Roster/XMPPRoster.m +++ b/Roster/XMPPRoster.m @@ -407,33 +407,6 @@ - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)prese if (sender != xmppStream) { return; } - if (![self hasRoster]) - { - // We received a presence notification, - // but we don't have a roster to apply it to yet. - // - // This is possible if we send our presence before we've received our roster. - // It's even possible if we send our presence after we've requested our roster. - // There is no guarantee the server will process our requests serially, - // and the server may start sending presence elements before it sends our roster. - // - // However, if we've requested the roster, - // then it shouldn't be too long before we receive it. - // So we should be able to simply queue the presence elements for later processing. - - if ([self requestedRoster]) - { - // We store the presence element until we get our roster. - [self.earlyPresenceElements addObject:presence]; - } - else - { - // The user has not requested the roster. - // This is a rogue presence element, or the user is simply not using our roster management. - } - - return; - } if ([[presence type] isEqualToString:@"subscribe"]) { From 72f5113bbeb86367bc2a9058acae09b2de1dfd4c Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 31 Jan 2012 16:59:07 +0530 Subject: [PATCH 164/180] Cleanup- remove earlyPresenceElements. --- Roster/XMPPRoster.h | 4 +--- Roster/XMPPRoster.m | 19 ------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/Roster/XMPPRoster.h b/Roster/XMPPRoster.h index 295377c..fc21a1a 100644 --- a/Roster/XMPPRoster.h +++ b/Roster/XMPPRoster.h @@ -25,9 +25,7 @@ MulticastDelegate *multicastDelegate; - Byte flags; - - NSMutableArray *earlyPresenceElements; + Byte flags; } - (id)initWithStream:(XMPPStream *)xmppStream rosterStorage:(id )storage; diff --git a/Roster/XMPPRoster.m b/Roster/XMPPRoster.m index da60845..37ac3ff 100644 --- a/Roster/XMPPRoster.m +++ b/Roster/XMPPRoster.m @@ -11,7 +11,6 @@ @interface XMPPRoster () -@property(retain) NSMutableArray *earlyPresenceElements; @property(assign) Byte flags; - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence; @@ -24,7 +23,6 @@ - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)prese @implementation XMPPRoster -@synthesize earlyPresenceElements; @synthesize flags; @synthesize xmppStream; @synthesize xmppRosterStorage; @@ -53,8 +51,6 @@ - (id)initWithStream:(XMPPStream *)stream rosterStorage:(id ) } flags = 0; - - earlyPresenceElements = [[NSMutableArray alloc] initWithCapacity:2]; } return self; } @@ -68,8 +64,6 @@ - (void)dealloc [xmppRosterStorage release]; - [earlyPresenceElements release]; - [super dealloc]; } @@ -315,15 +309,6 @@ - (void)updateRosterWithQuery:(NSXMLElement *)query { [self setHasRoster:YES]; - // Which means we can process any premature presence elements we received - for (XMPPPresence *presence in self.earlyPresenceElements) - { - // needs to happen on the main thread or context will get messed up - [self performSelectorOnMainThread:@selector(didReceivePresence:) - withObject:presence - waitUntilDone:NO]; - } - [self.earlyPresenceElements removeAllObjects]; [pool release]; } @@ -456,8 +441,6 @@ - (void)xmppStream:(XMPPStream *)sender didSendPresence:(XMPPPresence *)presence // So there's no need to refetch the roster. [xmppRosterStorage clearAllResourcesForXMPPStream:sender]; - - [self.earlyPresenceElements removeAllObjects]; } } @@ -472,8 +455,6 @@ - (void)xmppStreamDidDisconnect:(XMPPStream *)sender [self setRequestedRoster:NO]; [self setHasRoster:NO]; - - [self.earlyPresenceElements removeAllObjects]; } @end From a9ce12908aa94610da9d4d47b0f9bf9dc4e61326 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 1 Feb 2012 13:00:23 +0530 Subject: [PATCH 165/180] Remove unused method. --- Roster/XMPPRoster.m | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Roster/XMPPRoster.m b/Roster/XMPPRoster.m index 37ac3ff..2d2277b 100644 --- a/Roster/XMPPRoster.m +++ b/Roster/XMPPRoster.m @@ -286,11 +286,6 @@ - (void)fetchRoster } -- (void)didReceivePresence:(XMPPPresence *)presence { - [self xmppStream:xmppStream didReceivePresence:presence]; -} - - - (void)updateRosterWithQuery:(NSXMLElement *)query { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; From 24b41e2d13124a44d9334a0091b7d85130c762ce Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Fri, 24 Feb 2012 15:51:36 +0530 Subject: [PATCH 166/180] Stop using private API selector (ignore:). --- Core/XMPPParser.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Core/XMPPParser.m b/Core/XMPPParser.m index 348c997..138bbc0 100644 --- a/Core/XMPPParser.m +++ b/Core/XMPPParser.m @@ -656,13 +656,17 @@ - (void)stop #pragma mark Threading //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +- (void)nopTimer:(NSTimer *)timer { + // do nothing +} + - (void)parsingThread_Main { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 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 10,000 years. - [NSTimer scheduledTimerWithTimeInterval:DBL_MAX target:self selector:@selector(ignore:) userInfo:nil repeats:NO]; + [NSTimer scheduledTimerWithTimeInterval:DBL_MAX target:self selector:@selector(nopTimer:) userInfo:nil repeats:NO]; // Run the runloop From 18ed3e636e6e31b1cd94771805a3bcb1455e8a86 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Tue, 20 Mar 2012 15:03:44 +0530 Subject: [PATCH 167/180] Add log for sending packet. --- Core/Transports/BoshTransport.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 2aafd65..d684624 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -766,6 +766,7 @@ - (void)requestFailed:(ASIHTTPRequest *)request - (void)requestFinished:(ASIHTTPRequest *)request { long long rid = [self getRidFromRequest:request]; + DDLogInfo(@"BOSH: RECD[%qi]", rid); DDLogRecvPre(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); NSData *responseData = [request responseData]; @@ -813,7 +814,9 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid [pendingHTTPRequests_ addObject:request]; [request startAsynchronous]; + DDLogWarn(@"BOSH: SEND[%qi]", rid); DDLogSend(@"BOSH: SEND[%qi] = %@", rid, body); + return; } From 8c492543531daa2bcbdabd64babed9fea6fd1cb0 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 21 Mar 2012 16:55:11 +0530 Subject: [PATCH 168/180] BoshTransport: Log response in case there is a parsing failure. --- Core/Transports/BoshTransport.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 2aafd65..a44dffb 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -775,6 +775,14 @@ - (void)requestFinished:(ASIHTTPRequest *)request ![parsedResponse.name isEqualToString:@"body"] || ![[parsedResponse namespaceStringValueForPrefix:@""] isEqualToString:BODY_NS]) { +#if DEBUG_WARN + if (parsedResponse != nil) { + DDLogWarn(@"BOSH: Parse Failure: Unexpected XML in response: %@", parsedResponse); + } else { + DDLogWarn(@"BOSH: Parse Failure: Cannot parse response string: %@", [request responseString]); + DDLogWarn(@"BOSH: Parse Failure: Response headers: %@", [request responseHeaders]); + } +#endif [self requestFailed:request]; return; } From b9e30758a61742ac49d2c2bb65d09d2256d14696 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 21 Mar 2012 17:11:35 +0530 Subject: [PATCH 169/180] Log BOSH sid and rid for non-debug builds. --- Core/Transports/BoshTransport.m | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index bd9092d..ec9fbe4 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -766,8 +766,13 @@ - (void)requestFailed:(ASIHTTPRequest *)request - (void)requestFinished:(ASIHTTPRequest *)request { long long rid = [self getRidFromRequest:request]; - DDLogInfo(@"BOSH: RECD[%qi]", rid); - DDLogRecvPre(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); + + if (DEBUG_RECV_PRE) { + DDLogRecvPre(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); + } else { + DDLogInfo(@"BOSH: RECD sid: %@, rid: %qi", sid_, rid); + } + NSData *responseData = [request responseData]; NSXMLElement *parsedResponse = [self parseXMLData:responseData]; @@ -822,8 +827,11 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid [pendingHTTPRequests_ addObject:request]; [request startAsynchronous]; - DDLogWarn(@"BOSH: SEND[%qi]", rid); - DDLogSend(@"BOSH: SEND[%qi] = %@", rid, body); + if (DEBUG_SEND) { + DDLogSend(@"BOSH: SEND[%qi] = %@", rid, body); + } else { + DDLogInfo(@"BOSH: SEND sid: %@, rid: %qi", sid_, rid); + } return; } From a589f08cd2abb77f1b0f7e78c95e444c17ace1ab Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Thu, 22 Mar 2012 11:08:33 +0530 Subject: [PATCH 170/180] Log when the presence comes. ONLY FOR INTERNAL BUILD. --- Roster/XMPPRoster.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Roster/XMPPRoster.m b/Roster/XMPPRoster.m index 2d2277b..1ef092e 100644 --- a/Roster/XMPPRoster.m +++ b/Roster/XMPPRoster.m @@ -1,5 +1,6 @@ #import "XMPPRoster.h" #import "XMPP.h" +#import "DDLog.h" enum XMPPRosterFlags @@ -387,7 +388,8 @@ - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)prese if (sender != xmppStream) { return; } - + DDLogInfo(@"Presence received for %@ as %@",[presence from], [[presence show] length] == 0 ? @"available" : [presence show]); + if ([[presence type] isEqualToString:@"subscribe"]) { id user = [xmppRosterStorage userForJID:[presence from] xmppStream:sender]; From c73747f44fbf0bdb119d3b1edf47da899d901c8b Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Thu, 22 Mar 2012 11:38:08 +0530 Subject: [PATCH 171/180] Log when sending request for vcard. ONLY FOR INTERNAL BUILD. --- Extensions/XEP-0054/XMPPvCardTempModule.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Extensions/XEP-0054/XMPPvCardTempModule.m b/Extensions/XEP-0054/XMPPvCardTempModule.m index b458d85..971359b 100644 --- a/Extensions/XEP-0054/XMPPvCardTempModule.m +++ b/Extensions/XEP-0054/XMPPvCardTempModule.m @@ -69,7 +69,9 @@ - (void)_fetchvCardTempForJID:(XMPPJID *)jid { _openFetchRequests++; DDLogVerbose(@"%s %d", __PRETTY_FUNCTION__, _openFetchRequests); #endif - + + DDLogVerbose(@"sending fetch request for vcard avatar for %@", jid); + [xmppStream sendElement:iq]; } From 9e26923156648159cbfc247c424f6e8093045520 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 4 Apr 2012 13:27:04 +0530 Subject: [PATCH 172/180] Don't disconnect stream in case of BOSH parse failure. --- Core/Transports/BoshTransport.m | 74 ++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index ec9fbe4..ffbdf95 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -11,6 +11,8 @@ #import "NSXMLElementAdditions.h" #import "DDLog.h" +NSString *const BOSHParsingErrorDomain = @"BOSHParsingErrorDomain"; + @interface NSMutableSet(BoshTransport) - (void)addLongLong:(long long)number; - (void)removeLongLong:(long long)number; @@ -236,7 +238,7 @@ - (NSXMLElement *)newBodyElementWithPayload:(NSArray *)payload namespaces:(NSMutableDictionary *)namespaces; - (NSArray *)newXMLNodeArrayFromDictionary:(NSDictionary *)dict ofType:(XMLNodeType)type; -- (NSXMLElement *)parseXMLData:(NSData *)xml; +- (NSXMLElement *)parseXMLData:(NSData *)xml error:(NSError **)error; - (NSXMLElement *)parseXMLString:(NSString *)xml; - (BOOL) createSession:(NSError **)error; @end @@ -714,25 +716,18 @@ - (void)processResponses - (void)resendRequest:(ASIHTTPRequest *)request { long long rid = [self getRidFromRequest:request]; - [self sendHTTPRequestWithBody:[self parseXMLData:[request postBody]] rid:rid]; -} - -- (void)requestFailed:(ASIHTTPRequest *)request -{ - NSError *error = [request error]; - long long rid = [self getRidFromRequest:request]; - -#if DEBUG_WARN - NSString *requestString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; - DDLogWarn(@"BOSH: Request Failed[%qi] = %@", rid, requestString); - DDLogWarn(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); - [requestString release]; -#endif - - [pendingHTTPRequests_ removeObject:request]; - - BOOL shouldReconnect = ([error code] == ASIRequestTimedOutErrorType || [error code] == ASIConnectionFailureErrorType) && ( retryCounter < RETRY_COUNT_LIMIT ) && - (state == CONNECTED || state == DISCONNECTING); + [self sendHTTPRequestWithBody:[self parseXMLData:[request postBody] error:NULL] rid:rid]; +} + +- (void)processError:(NSError *)error forRequest:(ASIHTTPRequest *)request { + NSString *errorDomain = [error domain]; + BOOL shouldReconnect = ( ( [errorDomain isEqualToString:BOSHParsingErrorDomain] + || [errorDomain isEqualToString:@"DDXMLErrorDomain"] + || ( [errorDomain isEqualToString:NetworkRequestErrorDomain] + && ( [error code] == ASIRequestTimedOutErrorType + || [error code] == ASIConnectionFailureErrorType))) + && (retryCounter < RETRY_COUNT_LIMIT) + && (state == CONNECTED || state == DISCONNECTING)); if(shouldReconnect) { DDLogInfo(@"Resending the request"); @@ -757,6 +752,24 @@ - (void)requestFailed:(ASIHTTPRequest *)request state = DISCONNECTED; [self handleDisconnection]; } + +} + +- (void)requestFailed:(ASIHTTPRequest *)request +{ + NSError *error = [request error]; + long long rid = [self getRidFromRequest:request]; + +#if DEBUG_WARN + NSString *requestString = [[NSString alloc] initWithData:[request postBody] encoding:NSUTF8StringEncoding]; + DDLogWarn(@"BOSH: Request Failed[%qi] = %@", rid, requestString); + DDLogWarn(@"Failure HTTP statusCode = %d, error domain = %@, error code = %d", [request responseStatusCode],[[request error] domain], [[request error] code]); + [requestString release]; +#endif + + [pendingHTTPRequests_ removeObject:request]; + + [self processError:error forRequest:request]; } /* @@ -775,21 +788,23 @@ - (void)requestFinished:(ASIHTTPRequest *)request NSData *responseData = [request responseData]; - NSXMLElement *parsedResponse = [self parseXMLData:responseData]; - + NSError *error; + NSXMLElement *parsedResponse = [self parseXMLData:responseData error:&error]; + if (!parsedResponse || parsedResponse.kind != DDXMLElementKind || ![parsedResponse.name isEqualToString:@"body"] || ![[parsedResponse namespaceStringValueForPrefix:@""] isEqualToString:BODY_NS]) { -#if DEBUG_WARN if (parsedResponse != nil) { DDLogWarn(@"BOSH: Parse Failure: Unexpected XML in response: %@", parsedResponse); + error = [NSError errorWithDomain:BOSHParsingErrorDomain + code:0 + userInfo:nil]; } else { DDLogWarn(@"BOSH: Parse Failure: Cannot parse response string: %@", [request responseString]); - DDLogWarn(@"BOSH: Parse Failure: Response headers: %@", [request responseHeaders]); } -#endif - [self requestFailed:request]; + DDLogWarn(@"BOSH: Parse Failure: Response headers: %@", [request responseHeaders]); + [self processError:error forRequest:request]; return; } @@ -901,11 +916,14 @@ - (NSXMLElement *)parseXMLString:(NSString *)xml return element; } -- (NSXMLElement *)parseXMLData:(NSData *)xml +- (NSXMLElement *)parseXMLData:(NSData *)xml error:(NSError **)error { NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithData:xml options:0 - error:NULL] autorelease]; + error:error] autorelease]; + if (doc == nil) { + return nil; + } NSXMLElement *element = [doc rootElement]; [element detach]; return element; From 16c611b33bde42e655cf9fbf0fa1bc578e89b299 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Wed, 4 Apr 2012 16:24:05 +0530 Subject: [PATCH 173/180] Move logging level for presences to DDLogVerbose. --- Roster/XMPPRoster.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roster/XMPPRoster.m b/Roster/XMPPRoster.m index 1ef092e..8cc07ee 100644 --- a/Roster/XMPPRoster.m +++ b/Roster/XMPPRoster.m @@ -388,7 +388,7 @@ - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)prese if (sender != xmppStream) { return; } - DDLogInfo(@"Presence received for %@ as %@",[presence from], [[presence show] length] == 0 ? @"available" : [presence show]); + DDLogVerbose(@"Presence received for %@ as %@",[presence from], [[presence show] length] == 0 ? @"available" : [presence show]); if ([[presence type] isEqualToString:@"subscribe"]) { From 8e941f89a132c24ff30e4cd8b9f9d7f58318115a Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Thu, 5 Apr 2012 13:34:54 +0530 Subject: [PATCH 174/180] Fix style for multi-line conditional. --- Core/Transports/BoshTransport.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index ffbdf95..3dbbcdf 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -721,13 +721,13 @@ - (void)resendRequest:(ASIHTTPRequest *)request - (void)processError:(NSError *)error forRequest:(ASIHTTPRequest *)request { NSString *errorDomain = [error domain]; - BOOL shouldReconnect = ( ( [errorDomain isEqualToString:BOSHParsingErrorDomain] - || [errorDomain isEqualToString:@"DDXMLErrorDomain"] - || ( [errorDomain isEqualToString:NetworkRequestErrorDomain] - && ( [error code] == ASIRequestTimedOutErrorType - || [error code] == ASIConnectionFailureErrorType))) - && (retryCounter < RETRY_COUNT_LIMIT) - && (state == CONNECTED || state == DISCONNECTING)); + BOOL shouldReconnect = (([errorDomain isEqualToString:BOSHParsingErrorDomain] || + [errorDomain isEqualToString:@"DDXMLErrorDomain"] || + ([errorDomain isEqualToString:NetworkRequestErrorDomain] && + ([error code] == ASIRequestTimedOutErrorType || + [error code] == ASIConnectionFailureErrorType))) && + (retryCounter < RETRY_COUNT_LIMIT) && + (state == CONNECTED || state == DISCONNECTING)); if(shouldReconnect) { DDLogInfo(@"Resending the request"); From 201bdfec7fa5b644a76dac57a5632ac681bcb42e Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Thu, 12 Apr 2012 14:25:04 +0530 Subject: [PATCH 175/180] Fix presence logging in xmppStream rather than xmppRoster. --- Core/XMPPStream.m | 4 +++- Roster/XMPPRoster.m | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 0a47413..e156928 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1655,7 +1655,9 @@ - (void)transport:(id)sender didReceiveStanza:(NSXMLEleme } else if([elementName isEqualToString:@"presence"]) { - [multicastDelegate xmppStream:self didReceivePresence:[XMPPPresence presenceFromElement:element]]; + XMPPPresence *presence = [XMPPPresence presenceFromElement:element]; + DDLogVerbose(@"Presence received for %@ as %@",[presence from], [[presence type] isEqualToString:@"unavailable"] ? @"unavailable" : [[presence show] length] == 0 ? @"available" : [presence show]); + [multicastDelegate xmppStream:self didReceivePresence:presence]; } else if([self isP2P] && ([elementName isEqualToString:@"stream:features"] || [elementName isEqualToString:@"features"])) diff --git a/Roster/XMPPRoster.m b/Roster/XMPPRoster.m index 8cc07ee..4ae034e 100644 --- a/Roster/XMPPRoster.m +++ b/Roster/XMPPRoster.m @@ -388,7 +388,6 @@ - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)prese if (sender != xmppStream) { return; } - DDLogVerbose(@"Presence received for %@ as %@",[presence from], [[presence show] length] == 0 ? @"available" : [presence show]); if ([[presence type] isEqualToString:@"subscribe"]) { From 24db6974f952fb62bf3e9db782c64dcba5c04603 Mon Sep 17 00:00:00 2001 From: Sailesh Mittal Date: Fri, 27 Apr 2012 17:40:12 +0530 Subject: [PATCH 176/180] Change send/receive log levels to verbose. --- Core/Transports/BoshTransport.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index 3dbbcdf..b2075f7 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -783,7 +783,7 @@ - (void)requestFinished:(ASIHTTPRequest *)request if (DEBUG_RECV_PRE) { DDLogRecvPre(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); } else { - DDLogInfo(@"BOSH: RECD sid: %@, rid: %qi", sid_, rid); + DDLogVerbose(@"BOSH: RECD sid: %@, rid: %qi", sid_, rid); } NSData *responseData = [request responseData]; @@ -845,7 +845,7 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid if (DEBUG_SEND) { DDLogSend(@"BOSH: SEND[%qi] = %@", rid, body); } else { - DDLogInfo(@"BOSH: SEND sid: %@, rid: %qi", sid_, rid); + DDLogVerbose(@"BOSH: SEND sid: %@, rid: %qi", sid_, rid); } return; From 391bb72412230de44323959d32f0ed698a1ba182 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 23 May 2012 18:50:34 +0530 Subject: [PATCH 177/180] Remove the xml:lang attribute from incoming presence elements. Its ns->prefix gets deallocated for an unknown reason later on and crashes the app afterwards when we try to access an attribute of this NSXMLElement. --- Core/XMPPStream.m | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index e156928..b51b838 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -1655,8 +1655,16 @@ - (void)transport:(id)sender didReceiveStanza:(NSXMLEleme } else if([elementName isEqualToString:@"presence"]) { - XMPPPresence *presence = [XMPPPresence presenceFromElement:element]; - DDLogVerbose(@"Presence received for %@ as %@",[presence from], [[presence type] isEqualToString:@"unavailable"] ? @"unavailable" : [[presence show] length] == 0 ? @"available" : [presence show]); + XMPPPresence *presence = [XMPPPresence presenceFromElement:element]; + DDLogVerbose(@"Presence received for %@ as %@",[presence from], [[presence type] isEqualToString:@"unavailable"] ? @"unavailable" : [[presence show] length] == 0 ? @"available" : [presence show]); + // Remove the xml:lang attribute from presence since its ns->prefix gets deallocated + // for an unknown reason later on and crashes the app afterwards when we try to + // access an attribute of this NSXMLElement + // + // Since -removeAttributeForName works only with the name and not with namespaces, + // we cannot explicitly remove xml:lang. This means that any XML attribute named + // "lang" will be removed even it doesn't belong to the xml namespace + [presence removeAttributeForName:@"lang"]; [multicastDelegate xmppStream:self didReceivePresence:presence]; } else if([self isP2P] && From 8f295d3ad5acac65c723ac9b831645c521833c5c Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Wed, 13 Jun 2012 11:38:23 +0530 Subject: [PATCH 178/180] Fix memory leak. --- Core/Transports/BoshTransport.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index b2075f7..ade0ebb 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -948,6 +948,7 @@ - (NSArray *)newXMLNodeArrayFromDictionary:(NSDictionary *)dict } else { + [array release]; NSException *exception = [NSException exceptionWithName:@"InvalidXMLNodeType" reason:@"BOSH: Wrong Type Passed to createArrayFrom Dictionary" userInfo:nil]; From a498f4bdab8188b25d4e620ad6ff16a82f23d4b3 Mon Sep 17 00:00:00 2001 From: Chaitanya Gupta Date: Mon, 18 Jun 2012 18:03:33 +0530 Subject: [PATCH 179/180] BoshTransport: Stop using DDLogSend and DDLogRecvPre for logging. --- Core/Transports/BoshTransport.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/Transports/BoshTransport.m b/Core/Transports/BoshTransport.m index ade0ebb..5c791d4 100644 --- a/Core/Transports/BoshTransport.m +++ b/Core/Transports/BoshTransport.m @@ -780,10 +780,10 @@ - (void)requestFinished:(ASIHTTPRequest *)request { long long rid = [self getRidFromRequest:request]; - if (DEBUG_RECV_PRE) { - DDLogRecvPre(@"BOSH: RECD[%qi] = %@", rid, [request responseString]); + if (DEBUG_VERBOSE) { + DDLogVerbose(@"BOSH: RECD sid: %@, rid: %qi = %@", sid_, rid, [request responseString]); } else { - DDLogVerbose(@"BOSH: RECD sid: %@, rid: %qi", sid_, rid); + DDLogInfo(@"BOSH: RECD sid: %@, rid: %qi", sid_, rid); } NSData *responseData = [request responseData]; @@ -842,10 +842,10 @@ - (void)sendHTTPRequestWithBody:(NSXMLElement *)body rid:(long long)rid [pendingHTTPRequests_ addObject:request]; [request startAsynchronous]; - if (DEBUG_SEND) { - DDLogSend(@"BOSH: SEND[%qi] = %@", rid, body); + if (DEBUG_VERBOSE) { + DDLogVerbose(@"BOSH: SEND sid: %@, rid: %qi = %@", sid_, rid, body); } else { - DDLogVerbose(@"BOSH: SEND sid: %@, rid: %qi", sid_, rid); + DDLogInfo(@"BOSH: SEND sid: %@, rid: %qi", sid_, rid); } return; From 16a6684962affb56ab714a0f2644afcbdfe07fd1 Mon Sep 17 00:00:00 2001 From: PushpRaj Agrawal Date: Mon, 13 Aug 2012 17:34:29 +0530 Subject: [PATCH 180/180] XMPPvCardTemp: Add 'id' attribute to vcard request iq packet. --- Extensions/XEP-0054/XMPPvCardTemp.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Extensions/XEP-0054/XMPPvCardTemp.m b/Extensions/XEP-0054/XMPPvCardTemp.m index c2110bc..48f48a0 100644 --- a/Extensions/XEP-0054/XMPPvCardTemp.m +++ b/Extensions/XEP-0054/XMPPvCardTemp.m @@ -15,6 +15,7 @@ #import "DDLog.h" #import "NSDataAdditions.h" #import "XMPPDateTimeProfiles.h" +#import "XMPPStream.h" NSString *const kXMPPNSvCardTemp = @"vcard-temp"; @@ -65,7 +66,7 @@ + (XMPPvCardTemp *)vCardTempFromIQ:(XMPPIQ *)iq { + (XMPPIQ *)iqvCardRequestForJID:(XMPPJID *)jid { - XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:[jid bareJID]]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:[jid bareJID] elementID:[XMPPStream generateUUID]]; NSXMLElement *vCardElem = [NSXMLElement elementWithName:kXMPPvCardTempElement xmlns:kXMPPNSvCardTemp]; [vCardElem addAttributeWithName:@"x-only-base64encodedimage-required" stringValue:@"true"];