diff options
| author | Remko Tronçon <git@el-tramo.be> | 2011-04-02 20:46:42 (GMT) | 
|---|---|---|
| committer | Remko Tronçon <git@el-tramo.be> | 2011-04-18 19:11:41 (GMT) | 
| commit | 40587e704a2a8dfe7d29cc2e28e140f01c9f86bc (patch) | |
| tree | 1e7b339e6d4038c34e033088469f244ff971f496 | |
| parent | e3d2137622cea23298f203801bc698eff08e0ea1 (diff) | |
| download | swift-40587e704a2a8dfe7d29cc2e28e140f01c9f86bc.zip swift-40587e704a2a8dfe7d29cc2e28e140f01c9f86bc.tar.bz2 | |
Added RFC5122 XMPP URI parsing and basic handling.
URI Handling currently only works on Mac OS X.
32 files changed, 809 insertions, 15 deletions
| diff --git a/BuildTools/SCons/Tools/AppBundle.py b/BuildTools/SCons/Tools/AppBundle.py index c271575..6a343f6 100644 --- a/BuildTools/SCons/Tools/AppBundle.py +++ b/BuildTools/SCons/Tools/AppBundle.py @@ -1,7 +1,7 @@  import SCons.Util, os.path  def generate(env) : -  def createAppBundle(env, bundle, version = "1.0", resources = [], frameworks = [], info = {}) : +  def createAppBundle(env, bundle, version = "1.0", resources = [], frameworks = [], info = {}, handlesXMPPURIs = False) :      bundleDir = bundle + ".app"      bundleContentsDir = bundleDir + "/Contents"      resourcesDir = bundleContentsDir + "/Resources" @@ -32,6 +32,18 @@ def generate(env) :      for key, value in infoDict.items() :        plist += "<key>" + key + "</key>\n"        plist += "<string>" + value.encode("utf-8") + "</string>\n" +    if handlesXMPPURIs : +      plist += """<key>CFBundleURLTypes</key> +<array> +    <dict> +        <key>CFBundleURLName</key> +        <string>XMPP URL</string> +        <key>CFBundleURLSchemes</key> +        <array> +            <string>xmpp</string> +        </array> +    </dict> +</array>\n"""      plist += """</dict>    </plist>    """ diff --git a/Slimber/Cocoa/CocoaMenulet.h b/Slimber/Cocoa/CocoaMenulet.h index 7f2758b..5c7c33e 100644 --- a/Slimber/Cocoa/CocoaMenulet.h +++ b/Slimber/Cocoa/CocoaMenulet.h @@ -9,7 +9,7 @@  #include <Cocoa/Cocoa.h>  #include "Slimber/Menulet.h" -#include "Slimber/Cocoa/CocoaAction.h" +#include <SwifTools/Cocoa/CocoaAction.h>  class CocoaMenulet : public Menulet {  	public: diff --git a/Slimber/Cocoa/SConscript b/Slimber/Cocoa/SConscript index e2d8221..d664846 100644 --- a/Slimber/Cocoa/SConscript +++ b/Slimber/Cocoa/SConscript @@ -16,7 +16,6 @@ myenv.Program("Slimber", [  		"main.mm",  		"CocoaController.mm",  		"CocoaMenulet.mm", -		"CocoaAction.mm"  	])  myenv.Nib("MainMenu") diff --git a/Slimber/Cocoa/CocoaAction.h b/SwifTools/Cocoa/CocoaAction.h index a46ef7c..a46ef7c 100644 --- a/Slimber/Cocoa/CocoaAction.h +++ b/SwifTools/Cocoa/CocoaAction.h diff --git a/Slimber/Cocoa/CocoaAction.mm b/SwifTools/Cocoa/CocoaAction.mm index 15498a1..d560787 100644 --- a/Slimber/Cocoa/CocoaAction.mm +++ b/SwifTools/Cocoa/CocoaAction.mm @@ -1,4 +1,10 @@ -#include "Slimber/Cocoa/CocoaAction.h" +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <SwifTools/Cocoa/CocoaAction.h>  @implementation CocoaAction diff --git a/SwifTools/Cocoa/SConscript b/SwifTools/Cocoa/SConscript new file mode 100644 index 0000000..4ae4a07 --- /dev/null +++ b/SwifTools/Cocoa/SConscript @@ -0,0 +1,8 @@ +Import("swiftools_env", "env") + +sources = [] +if swiftools_env["PLATFORM"] == "darwin" and swiftools_env["target"] == "native" : +	sources += ["CocoaAction.mm"] + +objects = swiftools_env.StaticObject(sources) +swiftools_env.Append(SWIFTOOLS_OBJECTS = [objects]) diff --git a/SwifTools/Linkify.cpp b/SwifTools/Linkify.cpp index 91c713f..a5deccb 100644 --- a/SwifTools/Linkify.cpp +++ b/SwifTools/Linkify.cpp @@ -12,7 +12,7 @@  namespace Swift { -static boost::regex linkifyRegexp("^https?://.*"); +static boost::regex linkifyRegexp("^(https?://|xmpp:).*");  std::string Linkify::linkify(const std::string& input) {  	std::ostringstream result; diff --git a/SwifTools/SConscript b/SwifTools/SConscript index d4747db..8d00418 100644 --- a/SwifTools/SConscript +++ b/SwifTools/SConscript @@ -50,8 +50,10 @@ if env["SCONS_STAGE"] == "build" :  			"Application",  			"Dock",  			"Notifier", +			"URIHandler",  			"Idle/IdleQuerierTest",  			"Idle/UnitTest", +			"Cocoa",  			"UnitTest"  		]) diff --git a/SwifTools/URIHandler/MacOSXURIHandler.h b/SwifTools/URIHandler/MacOSXURIHandler.h new file mode 100644 index 0000000..f803420 --- /dev/null +++ b/SwifTools/URIHandler/MacOSXURIHandler.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <SwifTools/URIHandler/URIHandler.h> + +namespace Swift { +	class MacOSXURIHandler : public URIHandler { +		public: +			MacOSXURIHandler(); +			virtual ~MacOSXURIHandler(); + +			virtual void start(); +			virtual void stop(); + +		private: +			class Private; +			Private* p; +	}; +} diff --git a/SwifTools/URIHandler/MacOSXURIHandler.mm b/SwifTools/URIHandler/MacOSXURIHandler.mm new file mode 100644 index 0000000..3542e2f --- /dev/null +++ b/SwifTools/URIHandler/MacOSXURIHandler.mm @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <SwifTools/URIHandler/MacOSXURIHandler.h> + +#include <Cocoa/Cocoa.h> +#include <iostream> + +using namespace Swift; + +@interface MacOSXURIEventHandler : NSObject { +	URIHandler* handler; +} +- (id) initWithHandler: (URIHandler*) handler; +- (void) getUrl: (NSAppleEventDescriptor*) event withReplyEvent: (NSAppleEventDescriptor*) replyEvent; + +@end +@implementation MacOSXURIEventHandler +	- (id) initWithHandler: (URIHandler*) h { +		if ([super init]) { +			handler = h; +		} +		return self; +	} + +	- (void) getUrl: (NSAppleEventDescriptor*) event withReplyEvent: (NSAppleEventDescriptor*) replyEvent { +		(void) replyEvent; +		NSString* url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; +		handler->onURI(std::string([url UTF8String])); +	} +@end + +class MacOSXURIHandler::Private { +	public: +		MacOSXURIEventHandler* eventHandler; +}; + +MacOSXURIHandler::MacOSXURIHandler() { +	p = new Private(); +	p->eventHandler = [[MacOSXURIEventHandler alloc] initWithHandler: this];  +} + +MacOSXURIHandler::~MacOSXURIHandler() { +	[p->eventHandler release]; +	delete p; +} + +void MacOSXURIHandler::start() { +	[[NSAppleEventManager sharedAppleEventManager] setEventHandler:p->eventHandler andSelector:@selector(getUrl:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; +	NSString* bundleID = [[NSBundle mainBundle] bundleIdentifier]; +	LSSetDefaultHandlerForURLScheme((CFStringRef)@"xmpp", (CFStringRef)bundleID); +} + +void MacOSXURIHandler::stop() { +	[[NSAppleEventManager sharedAppleEventManager] removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL]; +} diff --git a/SwifTools/URIHandler/MacOSXURIHandlerHelpers.h b/SwifTools/URIHandler/MacOSXURIHandlerHelpers.h new file mode 100644 index 0000000..5a2db7a --- /dev/null +++ b/SwifTools/URIHandler/MacOSXURIHandlerHelpers.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { +	void registerAppAsDefaultXMPPURIHandler(); +} diff --git a/SwifTools/URIHandler/MacOSXURIHandlerHelpers.mm b/SwifTools/URIHandler/MacOSXURIHandlerHelpers.mm new file mode 100644 index 0000000..dca91b8 --- /dev/null +++ b/SwifTools/URIHandler/MacOSXURIHandlerHelpers.mm @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <SwifTools/URIHandler/MacOSXURIHandlerHelpers.h> + +#include <Cocoa/Cocoa.h> + +namespace Swift { +	void registerAppAsDefaultXMPPURIHandler() { +		NSString* bundleID = [[NSBundle mainBundle] bundleIdentifier]; +		LSSetDefaultHandlerForURLScheme((CFStringRef)@"xmpp", (CFStringRef)bundleID); +	} +} diff --git a/SwifTools/URIHandler/NullURIHandler.h b/SwifTools/URIHandler/NullURIHandler.h new file mode 100644 index 0000000..28c35bb --- /dev/null +++ b/SwifTools/URIHandler/NullURIHandler.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <SwifTools/URIHandler/URIHandler.h> + +namespace Swift { +	class NullURIHandler : public URIHandler { +		public: +			virtual void start() { +			} + +			virtual void stop() { +			} +	}; +} diff --git a/SwifTools/URIHandler/SConscript b/SwifTools/URIHandler/SConscript new file mode 100644 index 0000000..42c6ca8 --- /dev/null +++ b/SwifTools/URIHandler/SConscript @@ -0,0 +1,23 @@ +Import("swiftools_env", "env") + +sources = [ +		"XMPPURI.cpp", +		"URIHandler.cpp", +	] + +if swiftools_env["PLATFORM"] == "darwin" and swiftools_env["target"] == "native" : +	sources += [ +			"MacOSXURIHandler.mm", +			"MacOSXURIHandlerHelpers.mm", +		] +elif swiftools_env["PLATFORM"] == "win32" : +	sources += [] +else : +	sources += [] + +objects = swiftools_env.StaticObject(sources) +swiftools_env.Append(SWIFTOOLS_OBJECTS = [objects]) + +env.Append(UNITTEST_SOURCES = [ +		File("UnitTest/XMPPURITest.cpp"), +	]) diff --git a/SwifTools/URIHandler/URIHandler.cpp b/SwifTools/URIHandler/URIHandler.cpp new file mode 100644 index 0000000..91e54e5 --- /dev/null +++ b/SwifTools/URIHandler/URIHandler.cpp @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <SwifTools/URIHandler/URIHandler.h> + +using namespace Swift; + +URIHandler::URIHandler() { +} + +URIHandler::~URIHandler() { +} diff --git a/SwifTools/URIHandler/URIHandler.h b/SwifTools/URIHandler/URIHandler.h new file mode 100644 index 0000000..9dd13a8 --- /dev/null +++ b/SwifTools/URIHandler/URIHandler.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <Swiften/Base/boost_bsignals.h> + +namespace Swift { +	class URIHandler { +		public: +			URIHandler(); +			virtual ~URIHandler(); + +			boost::signal<void (const std::string&)> onURI; +	}; +} diff --git a/SwifTools/URIHandler/UnitTest/XMPPURITest.cpp b/SwifTools/URIHandler/UnitTest/XMPPURITest.cpp new file mode 100644 index 0000000..8d03b60 --- /dev/null +++ b/SwifTools/URIHandler/UnitTest/XMPPURITest.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <SwifTools/URIHandler/XMPPURI.h> + +using namespace Swift; + +class XMPPURITest : public CppUnit::TestFixture { +		CPPUNIT_TEST_SUITE(XMPPURITest); +		CPPUNIT_TEST(testFromString_Authority); +		CPPUNIT_TEST(testFromString_AuthorityWithPath); +		CPPUNIT_TEST(testFromString_AuthorityWithFragment); +		CPPUNIT_TEST(testFromString_AuthorityWithPathAndFragment); +		CPPUNIT_TEST(testFromString_AuthorityWithIntlChars); +		CPPUNIT_TEST(testFromString_AuthorityWithQueryWithoutParameters); +		CPPUNIT_TEST(testFromString_AuthorityWithQueryWithParameters); +		CPPUNIT_TEST(testFromString_AuthorityWithQueryWithoutParametersWithFragment); +		CPPUNIT_TEST(testFromString_AuthorityWithQueryWithParametersWithFragment); +		CPPUNIT_TEST(testFromString_Path); +		CPPUNIT_TEST(testFromString_PathWithFragment); +		CPPUNIT_TEST(testFromString_PathWithIntlChars); +		CPPUNIT_TEST(testFromString_PathWithInvalidEscapedChar); +		CPPUNIT_TEST(testFromString_PathWithIncompleteEscapedChar); +		CPPUNIT_TEST(testFromString_PathWithIncompleteEscapedChar2); +		CPPUNIT_TEST(testFromString_PathWithQueryWithoutParameters); +		CPPUNIT_TEST(testFromString_PathWithQueryWithParameters); +		CPPUNIT_TEST(testFromString_PathWithQueryWithoutParametersWithFragment); +		CPPUNIT_TEST(testFromString_PathWithQueryWithParametersWithFragment); +		CPPUNIT_TEST(testFromString_NoPrefix); +		CPPUNIT_TEST_SUITE_END(); + +	public: +		void testFromString_Authority() { +			XMPPURI testling = XMPPURI::fromString("xmpp://foo@bar.com"); + +			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), testling.getAuthority()); +		} + +		void testFromString_AuthorityWithPath() { +			XMPPURI testling = XMPPURI::fromString("xmpp://foo@bar.com/baz@example.com"); + +			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), testling.getAuthority()); +			CPPUNIT_ASSERT_EQUAL(JID("baz@example.com"), testling.getPath()); +		} + +		void testFromString_AuthorityWithFragment() { +			XMPPURI testling = XMPPURI::fromString("xmpp://foo@bar.com#myfragment"); + +			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), testling.getAuthority()); +			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment()); +		} + +		void testFromString_AuthorityWithPathAndFragment() { +			XMPPURI testling = XMPPURI::fromString("xmpp://foo@bar.com/baz@example.com#myfragment"); + +			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), testling.getAuthority()); +			CPPUNIT_ASSERT_EQUAL(JID("baz@example.com"), testling.getPath()); +			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment()); +		} + +		void testFromString_AuthorityWithIntlChars() { +			XMPPURI testling = XMPPURI::fromString("xmpp://nasty!%23$%25()*+,-.;=\%3F\%5B\%5C\%5D\%5E_\%60\%7B\%7C\%7D~node@example.com"); + +			CPPUNIT_ASSERT_EQUAL(JID("nasty!#$\%()*+,-.;=?[\\]^_`{|}~node@example.com"), testling.getAuthority()); +		} + +		void testFromString_AuthorityWithQueryWithoutParameters() { +			XMPPURI testling = XMPPURI::fromString("xmpp://test@example.com?message"); + +			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getAuthority()); +			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType()); +		} + +		void testFromString_AuthorityWithQueryWithParameters() { +			XMPPURI testling = XMPPURI::fromString("xmpp://test@example.com?message;subject=Test%20Message;body=Here%27s%20a%20test%20message"); + +			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getAuthority()); +			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType()); +			CPPUNIT_ASSERT_EQUAL(std::string("Test Message"), get(testling.getQueryParameters(), "subject")); +			CPPUNIT_ASSERT_EQUAL(std::string("Here's a test message"), get(testling.getQueryParameters(), "body")); +		} + +		void testFromString_AuthorityWithQueryWithoutParametersWithFragment() { +			XMPPURI testling = XMPPURI::fromString("xmpp://test@example.com?message#myfragment"); + +			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getAuthority()); +			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType()); +			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment()); +		} + +		void testFromString_AuthorityWithQueryWithParametersWithFragment() { +			XMPPURI testling = XMPPURI::fromString("xmpp://test@example.com?message;subject=Test%20Message;body=Here%27s%20a%20test%20message#myfragment"); + +			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getAuthority()); +			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType()); +			CPPUNIT_ASSERT_EQUAL(std::string("Test Message"), get(testling.getQueryParameters(), "subject")); +			CPPUNIT_ASSERT_EQUAL(std::string("Here's a test message"), get(testling.getQueryParameters(), "body")); +			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment()); +		} + +		void testFromString_Path() { +			XMPPURI testling = XMPPURI::fromString("xmpp:baz@example.com"); + +			CPPUNIT_ASSERT_EQUAL(JID("baz@example.com"), testling.getPath()); +		} + +		void testFromString_PathWithFragment() { +			XMPPURI testling = XMPPURI::fromString("xmpp:baz@example.com#myfragment"); + +			CPPUNIT_ASSERT_EQUAL(JID("baz@example.com"), testling.getPath()); +			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment()); +		} + +		void testFromString_PathWithIntlChars() { +			XMPPURI testling = XMPPURI::fromString("xmpp:nasty!%23$%25()*+,-.;=\%3F\%5B\%5C\%5D\%5E_\%60\%7B\%7C\%7D~node@example.com"); + +			CPPUNIT_ASSERT_EQUAL(JID("nasty!#$\%()*+,-.;=?[\\]^_`{|}~node@example.com"), testling.getPath()); +		} + +		void testFromString_PathWithInvalidEscapedChar() { +			XMPPURI testling = XMPPURI::fromString("xmpp:test%%@example.com"); + +			CPPUNIT_ASSERT_EQUAL(JID(), testling.getPath()); +		} + +		void testFromString_PathWithIncompleteEscapedChar() { +			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com%"); + +			CPPUNIT_ASSERT_EQUAL(JID(), testling.getPath()); +		} + +		void testFromString_PathWithIncompleteEscapedChar2() { +			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com%1"); + +			CPPUNIT_ASSERT_EQUAL(JID(), testling.getPath()); +		} + +		void testFromString_PathWithQueryWithoutParameters() { +			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com?message"); + +			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getPath()); +			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType()); +		} + +		void testFromString_PathWithQueryWithParameters() { +			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com?message;subject=Test%20Message;body=Here%27s%20a%20test%20message"); + +			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getPath()); +			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType()); +			CPPUNIT_ASSERT_EQUAL(std::string("Test Message"), get(testling.getQueryParameters(), "subject")); +			CPPUNIT_ASSERT_EQUAL(std::string("Here's a test message"), get(testling.getQueryParameters(), "body")); +		} + +		void testFromString_PathWithQueryWithoutParametersWithFragment() { +			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com?message#myfragment"); + +			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getPath()); +			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType()); +			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment()); +		} + +		void testFromString_PathWithQueryWithParametersWithFragment() { +			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com?message;subject=Test%20Message;body=Here%27s%20a%20test%20message#myfragment"); + +			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getPath()); +			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType()); +			CPPUNIT_ASSERT_EQUAL(std::string("Test Message"), get(testling.getQueryParameters(), "subject")); +			CPPUNIT_ASSERT_EQUAL(std::string("Here's a test message"), get(testling.getQueryParameters(), "body")); +			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment()); +		} + +		void testFromString_NoPrefix() { +			XMPPURI testling = XMPPURI::fromString("baz@example.com"); + +			CPPUNIT_ASSERT(testling.isNull()); +		} + +	private: +		std::string get(const std::map<std::string, std::string>& m, const std::string& k) { +			std::map<std::string, std::string>::const_iterator i = m.find(k); +			return i == m.end() ? "" : i->second; +		} +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(XMPPURITest); diff --git a/SwifTools/URIHandler/XMPPURI.cpp b/SwifTools/URIHandler/XMPPURI.cpp new file mode 100644 index 0000000..d9fde07 --- /dev/null +++ b/SwifTools/URIHandler/XMPPURI.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <SwifTools/URIHandler/XMPPURI.h> + +#include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/find_format.hpp> +#include <boost/algorithm/string/formatter.hpp> +#include <boost/algorithm/string/find_iterator.hpp> +#include <boost/algorithm/string/split.hpp> +#include <boost/algorithm/string/classification.hpp> +#include <sstream> +#include <stdexcept> +#include <vector> + +using namespace Swift; + +namespace { + +	struct PercentEncodedCharacterFinder { +		template<typename Iterator> +		boost::iterator_range<Iterator> operator()(Iterator begin, Iterator end) { +			boost::iterator_range<Iterator> r = boost::first_finder("%")(begin, end); +			if (r.end() == end) { +				return r; +			} +			else { +				if (r.end() + 1 == end || r.end() + 2 == end) { +					throw std::runtime_error("Incomplete escape character"); +				} +				else { +					r.advance_end(2); +					return r; +				} +			} +		} +	}; + +	struct PercentUnencodeFormatter { +		template<typename FindResult> +		std::string operator()(const FindResult& match) const { +			std::stringstream s; +			s << std::hex << std::string(match.begin() + 1, match.end()); +			unsigned int value; +			s >> value; +			if (s.fail() || s.bad()) { +				throw std::runtime_error("Invalid escape character"); +			} +			return std::string(reinterpret_cast<const char*>(&value), 1); +		} +	}; +	 + +	std::string unescape(const std::string& s) { +		try { +			return boost::find_format_all_copy(s, PercentEncodedCharacterFinder(), PercentUnencodeFormatter()); +		} +		catch (const std::exception&) { +			return ""; +		} +	} +} + +XMPPURI::XMPPURI() { +} + +XMPPURI XMPPURI::fromString(const std::string& s) { +	XMPPURI result; +	if (boost::starts_with(s, "xmpp:")) { +		std::string uri = s.substr(5, s.npos); +		bool parsePath = true; +		bool parseQuery = true; +		bool parseFragment = true; + +		// Parse authority +		if (boost::starts_with(uri, "//")) { +			size_t i = uri.find_first_of("/#?", 2); +			result.setAuthority(JID(unescape(uri.substr(2, i - 2)))); +			if (i == uri.npos) { +				uri = ""; +				parsePath = parseQuery = parseFragment = false; +			} +			else { +				if (uri[i] == '?') { +					parsePath = false; +				} +				else if (uri[i] == '#') { +					parseQuery = parsePath = false; +				} +				uri = uri.substr(i + 1, uri.npos); +			} +		} + +		// Parse path +		if (parsePath) { +			size_t i = uri.find_first_of("#?"); +			result.setPath(JID(unescape(uri.substr(0, i)))); +			if (i == uri.npos) { +				uri = ""; +				parseQuery = parseFragment = false; +			} +			else { +				if (uri[i] == '#') { +					parseQuery = false; +				} +				uri = uri.substr(i + 1, uri.npos); +			} +		} + +		// Parse query +		if (parseQuery) { +			size_t end = uri.find_first_of("#"); +			std::string query = uri.substr(0, end); +			bool haveType = false; +			typedef boost::split_iterator<std::string::iterator> split_iterator; +	    for (split_iterator it = boost::make_split_iterator(query, boost::first_finder(";")); it != split_iterator(); ++it) { +	    	if (haveType) { +	    		std::vector<std::string> keyValue; +	    		boost::split(keyValue, *it, boost::is_any_of("=")); +	    		if (keyValue.size() == 1) { +	    			result.addQueryParameter(unescape(keyValue[0]), ""); +	    		} +	    		else if (keyValue.size() >= 2) { +	    			result.addQueryParameter(unescape(keyValue[0]), unescape(keyValue[1])); +	    		} +	    	} +	    	else { +	    		result.setQueryType(unescape(boost::copy_range<std::string>(*it))); +	    		haveType = true; +	    	} +	    } +	    uri = (end == uri.npos ? "" : uri.substr(end + 1, uri.npos)); +		} + +		// Parse fragment +		if (parseFragment) { +			result.setFragment(unescape(uri)); +		} +	} +	return result; +} diff --git a/SwifTools/URIHandler/XMPPURI.h b/SwifTools/URIHandler/XMPPURI.h new file mode 100644 index 0000000..266b79b --- /dev/null +++ b/SwifTools/URIHandler/XMPPURI.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <map> + +#include <Swiften/JID/JID.h> + +namespace Swift { +	class XMPPURI { +		public: +			XMPPURI(); + +			const JID& getAuthority() const { +				return authority; +			} + +			void setAuthority(const JID& j) { +				authority = j; +			} + +			const JID& getPath() const { +				return path; +			} + +			void setPath(const JID& j) { +				path = j; +			} + +			const std::string& getQueryType() const { +				return queryType; +			} + +			void setQueryType(const std::string& q) { +				queryType = q; +			} + +			const std::map<std::string, std::string>& getQueryParameters() const { +				return queryParameters; +			} + +			void addQueryParameter(const std::string& key, const std::string& path) { +				queryParameters[key] = path; +			} + +			const std::string& getFragment() const { +				return fragment; +			} + +			void setFragment(const std::string& f) { +				fragment = f; +			} + +			bool isNull() const { +				return !authority.isValid() && !path.isValid(); +			} + +			static XMPPURI fromString(const std::string&); + +		private: +			JID authority; +			JID path; +			std::string fragment; +			std::string queryType; +			std::map<std::string, std::string> queryParameters; +	}; +} diff --git a/SwifTools/UnitTest/SConscript b/SwifTools/UnitTest/SConscript index 8034905..9fd1375 100644 --- a/SwifTools/UnitTest/SConscript +++ b/SwifTools/UnitTest/SConscript @@ -2,5 +2,5 @@ Import("env")  env.Append(UNITTEST_SOURCES = [  		File("LinkifyTest.cpp"), -		File("TabCompleteTest.cpp") +		File("TabCompleteTest.cpp"),  	]) diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index 5f51ac6..972501f 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -158,13 +158,13 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {  	else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) {  		handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getNick(), false);  	} -	else if (boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) { +	else if (boost::shared_ptr<RequestJoinMUCUIEvent> joinEvent = boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {  		if (!joinMUCWindow_) {  			joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow();  			joinMUCWindow_->onJoinMUC.connect(boost::bind(&ChatsManager::handleJoinMUCRequest, this, _1, _2, _3));  			joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this));  		} -		joinMUCWindow_->setMUC(""); +		joinMUCWindow_->setMUC(joinEvent->getRoom());  		joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_));  		joinMUCWindow_->show();  	} diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 9a35cc1..3f86b43 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -65,6 +65,7 @@  #include "Swiften/Network/NetworkFactories.h"  #include <Swift/Controllers/ProfileController.h>  #include <Swift/Controllers/ContactEditController.h> +#include <Swift/Controllers/XMPPURIController.h>  namespace Swift { @@ -84,6 +85,7 @@ MainController::MainController(  		CertificateStorageFactory* certificateStorageFactory,  		Dock* dock,  		Notifier* notifier, +		URIHandler* uriHandler,  		bool useDelayForLatency) :  			eventLoop_(eventLoop),  			networkFactories_(networkFactories), @@ -92,6 +94,7 @@ MainController::MainController(  			storagesFactory_(storagesFactory),  			certificateStorageFactory_(certificateStorageFactory),  			settings_(settings), +			uriHandler_(uriHandler),  			loginWindow_(NULL) ,  			useDelayForLatency_(useDelayForLatency) {  	storages_ = NULL; @@ -121,6 +124,8 @@ MainController::MainController(  	loginWindow_ = uiFactory_->createLoginWindow(uiEventStream_);  	soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings, uiEventStream_); +	xmppURIController_ = new XMPPURIController(uriHandler_, uiEventStream_); +  	std::string selectedLoginJID = settings_->getStringSetting("lastLoginJID");  	bool loginAutomatically = settings_->getBoolSetting("loginAutomatically", false);  	std::string cachedPassword; @@ -167,6 +172,7 @@ MainController::~MainController() {  	resetClient();  	delete xmlConsoleController_; +	delete xmppURIController_;  	delete soundEventController_;  	delete systemTrayController_;  	delete eventController_; diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index f402f8f..f9722de 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -62,6 +62,8 @@ namespace Swift {  	class Storages;  	class StoragesFactory;  	class NetworkFactories; +	class URIHandler; +	class XMPPURIController;  	class MainController {  		public: @@ -76,6 +78,7 @@ namespace Swift {  					CertificateStorageFactory* certificateStorageFactory,  					Dock* dock,  					Notifier* notifier, +					URIHandler* uriHandler,  					bool useDelayForLatency);  			~MainController(); @@ -123,6 +126,7 @@ namespace Swift {  			SettingsProvider *settings_;  			ProfileSettingsProvider* profileSettings_;  			Dock* dock_; +			URIHandler* uriHandler_;  			TogglableNotifier* notifier_;  			PresenceNotifier* presenceNotifier_;  			EventNotifier* eventNotifier_; @@ -139,6 +143,7 @@ namespace Swift {  			JID boundJID_;  			SystemTrayController* systemTrayController_;  			SoundEventController* soundEventController_; +			XMPPURIController* xmppURIController_;  			std::string vCardPhotoHash_;  			std::string password_;  			std::string certificateFile_; diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 61da9fb..c523419 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -54,6 +54,7 @@ if env["SCONS_STAGE"] == "build" :  			"CertificateFileStorage.cpp",  			"StatusUtil.cpp",  			"Translator.cpp", +			"XMPPURIController.cpp",	  		])  	env.Append(UNITTEST_SOURCES = [ diff --git a/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h b/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h index dd2ff6c..2c7b105 100644 --- a/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h +++ b/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h @@ -6,18 +6,25 @@  #pragma once -#include <boost/optional.hpp>  #include <boost/shared_ptr.hpp> -  #include <string> +  #include <Swift/Controllers/UIEvents/UIEvent.h> +#include <Swiften/JID/JID.h>  namespace Swift {  	class RequestJoinMUCUIEvent : public UIEvent {  		public:  			typedef boost::shared_ptr<RequestJoinMUCUIEvent> ref; -			RequestJoinMUCUIEvent() { +			RequestJoinMUCUIEvent(const JID& room = JID()) : room(room) {  			} + +			const JID& getRoom() const { +				return room; +			} + +		private: +			JID room;  	};  } diff --git a/Swift/Controllers/XMPPURIController.cpp b/Swift/Controllers/XMPPURIController.cpp new file mode 100644 index 0000000..00759b8 --- /dev/null +++ b/Swift/Controllers/XMPPURIController.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/XMPPURIController.h> + +#include <boost/bind.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <SwifTools/URIHandler/URIHandler.h> +#include <SwifTools/URIHandler/XMPPURI.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> + +using namespace Swift; + +XMPPURIController::XMPPURIController(URIHandler* uriHandler, UIEventStream* uiEventStream) : uriHandler(uriHandler), uiEventStream(uiEventStream) { +	uriHandler->onURI.connect(boost::bind(&XMPPURIController::handleURI, this, _1)); +} + +XMPPURIController::~XMPPURIController() { +	uriHandler->onURI.disconnect(boost::bind(&XMPPURIController::handleURI, this, _1)); +} + +void XMPPURIController::handleURI(const std::string& s) { +	XMPPURI uri = XMPPURI::fromString(s); +	if (!uri.isNull()) { +		if (uri.getQueryType() == "join") { +			uiEventStream->send(boost::make_shared<RequestJoinMUCUIEvent>(uri.getPath())); +		} +		else { +			uiEventStream->send(boost::make_shared<RequestChatUIEvent>(uri.getPath())); +		} +	} +} diff --git a/Swift/Controllers/XMPPURIController.h b/Swift/Controllers/XMPPURIController.h new file mode 100644 index 0000000..54534d4 --- /dev/null +++ b/Swift/Controllers/XMPPURIController.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <Swiften/Base/boost_bsignals.h> + +namespace Swift { +	class URIHandler; +	class JID; +	class UIEventStream; + +	class XMPPURIController { +		public: +			XMPPURIController(URIHandler* uriHandler, UIEventStream* uiEventStream); +			~XMPPURIController(); + +			boost::signal<void (const JID&)> onStartChat; +			boost::signal<void (const JID&)> onJoinMUC; + +		private: +			void handleURI(const std::string&); + +		private: +			URIHandler* uriHandler; +			UIEventStream* uiEventStream; +	}; +} diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp index d4c306f..d977637 100644 --- a/Swift/QtUI/QtSwift.cpp +++ b/Swift/QtUI/QtSwift.cpp @@ -32,20 +32,28 @@  #include "Swift/Controllers/BuildVersion.h"  #include "SwifTools/AutoUpdater/AutoUpdater.h"  #include "SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h" +  #if defined(SWIFTEN_PLATFORM_WINDOWS)  #include "WindowsNotifier.h" -#endif -#if defined(HAVE_GROWL) +#elif defined(HAVE_GROWL)  #include "SwifTools/Notifier/GrowlNotifier.h"  #elif defined(SWIFTEN_PLATFORM_LINUX)  #include "FreeDesktopNotifier.h"  #else  #include "SwifTools/Notifier/NullNotifier.h"  #endif +  #if defined(SWIFTEN_PLATFORM_MACOSX)  #include "SwifTools/Dock/MacOSXDock.h" -#endif +#else  #include "SwifTools/Dock/NullDock.h" +#endif + +#if defined(SWIFTEN_PLATFORM_MACOSX) +#include "QtURIHandler.h" +#else +#include <SwifTools/URIHandler/NullURIHandler.h> +#endif  namespace Swift{ @@ -123,6 +131,12 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa  	dock_ = new NullDock();  #endif +#if defined(SWIFTEN_PLATFORM_MACOSX) +	uriHandler_ = new QtURIHandler(); +#else +	uriHandler_ = new NullURIHandler(); +#endif +  	if (splitter_) {  		splitter_->show();  	} @@ -145,6 +159,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa  				certificateStorageFactory_,  				dock_,  				notifier_, +				uriHandler_,  				options.count("latency-debug") > 0);  		mainControllers_.push_back(mainController);  	} @@ -172,6 +187,7 @@ QtSwift::~QtSwift() {  	}  	delete tabs_;  	delete splitter_; +	delete uriHandler_;  	delete dock_;  	delete soundPlayer_;  	delete chatWindowFactory_; diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h index 978fa14..4bf5c97 100644 --- a/Swift/QtUI/QtSwift.h +++ b/Swift/QtUI/QtSwift.h @@ -44,6 +44,7 @@ namespace Swift {  	class QtMUCSearchWindowFactory;  	class QtUserSearchWindowFactory;  	class EventLoop; +	class URIHandler;  	class QtSwift : public QObject {  		Q_OBJECT @@ -63,6 +64,7 @@ namespace Swift {  			QSplitter* splitter_;  			QtSoundPlayer* soundPlayer_;  			Dock* dock_; +			URIHandler* uriHandler_;  			QtChatTabs* tabs_;  			ApplicationPathProvider* applicationPathProvider_;  			StoragesFactory* storagesFactory_; diff --git a/Swift/QtUI/QtURIHandler.cpp b/Swift/QtUI/QtURIHandler.cpp new file mode 100644 index 0000000..43f3ed1 --- /dev/null +++ b/Swift/QtUI/QtURIHandler.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "QtURIHandler.h" + +#include <QCoreApplication> +#include <QFileOpenEvent> +#include <QUrl> + +#include "QtSwiftUtil.h" +#ifdef Q_WS_MAC +#include <SwifTools/URIHandler/MacOSXURIHandlerHelpers.h> +#endif + +using namespace Swift; + +QtURIHandler::QtURIHandler() { +	qApp->installEventFilter(this); +#ifdef Q_WS_MAC +	registerAppAsDefaultXMPPURIHandler(); +#endif +} + +bool QtURIHandler::eventFilter(QObject*, QEvent* event) { +	if (event->type() == QEvent::FileOpen) { +		QFileOpenEvent* fileOpenEvent = static_cast<QFileOpenEvent*>(event); +		if (fileOpenEvent->url().scheme() == "xmpp") { +			onURI(Q2PSTRING(fileOpenEvent->url().toString())); +			return true; +		} +	} +	return false; +} diff --git a/Swift/QtUI/QtURIHandler.h b/Swift/QtUI/QtURIHandler.h new file mode 100644 index 0000000..a4b56f8 --- /dev/null +++ b/Swift/QtUI/QtURIHandler.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QObject> +#include <SwifTools/URIHandler/URIHandler.h> + +class QUrl; + +namespace Swift { +	class QtURIHandler : public QObject, public URIHandler { +		public: +			QtURIHandler(); + +			virtual void start() { +			} + +			virtual void stop() { +			} + +		private: +			bool eventFilter(QObject* obj, QEvent* event); +	}; +} diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index d8d9abd..e2775a6 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -77,6 +77,7 @@ sources = [      "QtStatusWidget.cpp",  		"QtScaledAvatarCache.cpp",      "QtSwift.cpp", +    "QtURIHandler.cpp",      "QtChatView.cpp",      "QtChatTheme.cpp",      "QtChatTabs.cpp", @@ -215,7 +216,7 @@ if env["PLATFORM"] == "darwin" :    if env["HAVE_GROWL"] :      frameworks.append(env["GROWL_FRAMEWORK"])    commonResources[""] = commonResources.get("", []) + ["../resources/MacOSX/Swift.icns"] -  app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks) +  app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks, handlesXMPPURIs = True)    if env["DIST"] :      myenv.Command(["Swift-${SWIFT_VERSION}.dmg"], [app], ["Swift/Packaging/MacOSX/package.sh " + app.path + " Swift/Packaging/MacOSX/Swift.dmg.gz $TARGET $QTDIR"]) | 
 Swift
 Swift