diff options
-rw-r--r-- | Swift/ChangeLog.md | 39 | ||||
-rw-r--r-- | Swift/Controllers/ConnectionSettings.h | 40 | ||||
-rw-r--r-- | Swift/Controllers/MainController.cpp | 103 | ||||
-rw-r--r-- | Swift/Controllers/MainController.h | 5 | ||||
-rw-r--r-- | Swift/Controllers/UIInterfaces/LoginWindow.h | 5 | ||||
-rw-r--r-- | Swift/QtUI/QtConnectionSettings.ui | 535 | ||||
-rw-r--r-- | Swift/QtUI/QtConnectionSettingsWindow.cpp | 129 | ||||
-rw-r--r-- | Swift/QtUI/QtConnectionSettingsWindow.h | 36 | ||||
-rw-r--r-- | Swift/QtUI/QtLoginWindow.cpp | 22 | ||||
-rw-r--r-- | Swift/QtUI/QtLoginWindow.h | 5 | ||||
-rw-r--r-- | Swift/QtUI/SConscript | 2 | ||||
-rw-r--r-- | Swiften/Base/URL.h | 31 |
12 files changed, 920 insertions, 32 deletions
diff --git a/Swift/ChangeLog.md b/Swift/ChangeLog.md index ce52f1d..bef90f5 100644 --- a/Swift/ChangeLog.md +++ b/Swift/ChangeLog.md @@ -1,19 +1,20 @@ 2.0-beta2 --------- -- Enable auto-completion of nicknames that don't start with a letter -- Generate crash dumps on Windows -- Connection timeouts are now on each connection separately, instead of on the complete connection process -- Don't allow pressing `<Enter>` in the roster to trigger logins -- Don't lose security labels when correcting a message -- Don't crash when completing user search without selection -- Always auto join MUC with a consistent nickname -- Renamed `swift` binary to `swift-im` on Linux -- Avoid woosh down and woosh up on Mac when opening chat windows -- Improved MUC invitation dialog -- Fixed problem with displaying group names containing '&' in the 'Edit' dialog -- Show stream encryption status in header -- Show extended certificate dialog when encryption errors occur -- Don't allow server command results to get interpreted as HTML +- Enable auto-completion of nicknames that don't start with a letter. +- Generate crash dumps on Windows. +- Connection timeouts are now on each connection separately, instead of on the complete connection process. +- Don't allow pressing `<Enter>` in the roster to trigger logins. +- Don't lose security labels when correcting a message. +- Don't crash when completing user search without selection. +- Always auto join MUC with a consistent nickname. +- Renamed `swift` binary to `swift-im` on Linux. +- Avoid woosh down and woosh up on Mac when opening chat windows. +- Improved MUC invitation dialog. +- Fixed problem with displaying group names containing '&' in the 'Edit' dialog. +- Show stream encryption status in header. +- Show extended certificate dialog when encryption errors occur. +- Don't allow server command results to get interpreted as HTML. +- Additional connection settings (such as manual host setting, proxy configuration and BOSH) can now be specified through a new dialog on the login window. Thanks to Tobias Markmann. @@ -28,9 +29,9 @@ Thanks to Tobias Markmann. - Swift will now use appropriate algorithms when run on a Windows platform locked down in FIPS-140-2 mode. - Our TLS provider has been changed to the platform-provided one (Schannel) on Windows, allowing us to use certificates (both file and card-based) from the system store (CAPI). -- Added support for message receipts in one-to-one conversations (XEP-0184) +- Added support for message receipts in one-to-one conversations (XEP-0184). - Added support for several MUC operations (kicking, banning, invites, topic changes, room destruction, - changing participant affiliations, ...) + changing participant affiliations, ...). - It is now possible to resize the font in the chat window conversations. - Added support for message correction. Use 'up' to edit the previous message. - A list of recent chats is now kept in the 'Chats' tab of the main window. @@ -38,8 +39,8 @@ Thanks to Tobias Markmann. - Chat tabs with unread messages from several chats will now be a little more descriptive. - Use a bar for showing where the last read messages in a chat are. - Added support for SOCKS5 and HTTPConnect proxies. These settings are taken from the platform preferences. -- Retrieve incremental contact list on login on servers that support it (XEP-0237: Roster Versioning) -- Lots of bugfixes, performance fixes, and other changes +- Retrieve incremental contact list on login on servers that support it (XEP-0237: Roster Versioning). +- Lots of bugfixes, performance fixes, and other changes. Thanks to Tobias Markmann, Jan Kaluza, Thilo Cestonaro, Arnt Gulbrandsen, Vlad Voicu, Vitaly Takmazov, Yoann Blein, Catalin Badea, Pavol Babincak, Mateusz Piekos, Alexey Melnikov and Soren Dreijer. @@ -47,4 +48,4 @@ Yoann Blein, Catalin Badea, Pavol Babincak, Mateusz Piekos, Alexey Melnikov and 1.0 --- -- Initial release +- Initial release. diff --git a/Swift/Controllers/ConnectionSettings.h b/Swift/Controllers/ConnectionSettings.h new file mode 100644 index 0000000..c02c5d4 --- /dev/null +++ b/Swift/Controllers/ConnectionSettings.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> + +struct ConnectionSettings { + enum Method { + Automatic, + Manual, + BOSH + }; + enum ProxyType { + None, + System, + SOCKS5, + HTTPConnect + }; + + Method method; + struct { + bool useManualServer; + std::string manualServerHostname; + int manualServerPort; + ProxyType proxyType; + bool useManualProxy; + std::string manualProxyHostname; + int manualProxyPort; + } manualSettings; + struct { + std::string boshURI; + bool useManualProxy; + std::string manualProxyHostname; + int manualProxyPort; + } boshSettings; +}; diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 2c02ba8..f3a0226 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -15,6 +15,8 @@ #include <Swiften/Base/format.h> #include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/String.h> +#include <Swiften/StringCodecs/Base64.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/UIInterfaces/UIFactory.h> #include "Swiften/Network/TimerFactory.h" @@ -145,6 +147,7 @@ MainController::MainController( bool loginAutomatically = settings_->getSetting(SettingConstants::LOGIN_AUTOMATICALLY); std::string cachedPassword; std::string cachedCertificate; + ClientOptions cachedOptions; bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS); if (!eagle) { foreach (std::string profile, settings->getAvailableProfiles()) { @@ -152,10 +155,12 @@ MainController::MainController( std::string password = profileSettings.getStringSetting("pass"); std::string certificate = profileSettings.getStringSetting("certificate"); std::string jid = profileSettings.getStringSetting("jid"); - loginWindow_->addAvailableAccount(jid, password, certificate); + ClientOptions clientOptions = parseClientOptions(profileSettings.getStringSetting("options")); + loginWindow_->addAvailableAccount(jid, password, certificate, clientOptions); if (jid == selectedLoginJID) { cachedPassword = password; cachedCertificate = certificate; + cachedOptions = clientOptions; } } loginWindow_->selectUser(selectedLoginJID); @@ -163,7 +168,7 @@ MainController::MainController( } - loginWindow_->onLoginRequest.connect(boost::bind(&MainController::handleLoginRequest, this, _1, _2, _3, _4, _5, _6)); + loginWindow_->onLoginRequest.connect(boost::bind(&MainController::handleLoginRequest, this, _1, _2, _3, _4, _5, _6, _7)); loginWindow_->onPurgeSavedLoginRequest.connect(boost::bind(&MainController::handlePurgeSavedLoginRequest, this, _1)); loginWindow_->onCancelLoginRequest.connect(boost::bind(&MainController::handleCancelLoginRequest, this)); loginWindow_->onQuitRequest.connect(boost::bind(&MainController::handleQuitRequest, this)); @@ -180,7 +185,7 @@ MainController::MainController( if (loginAutomatically) { profileSettings_ = new ProfileSettingsProvider(selectedLoginJID, settings_); /* FIXME: deal with autologin with a cert*/ - handleLoginRequest(selectedLoginJID, cachedPassword, cachedCertificate, CertificateWithKey::ref(), true, true); + handleLoginRequest(selectedLoginJID, cachedPassword, cachedCertificate, CertificateWithKey::ref(), cachedOptions, true, true); } else { profileSettings_ = NULL; } @@ -445,7 +450,7 @@ void MainController::handleShowCertificateRequest() { rosterController_->getWindow()->openCertificateDialog(chain); } -void MainController::handleLoginRequest(const std::string &username, const std::string &password, const std::string& certificatePath, CertificateWithKey::ref certificate, bool remember, bool loginAutomatically) { +void MainController::handleLoginRequest(const std::string &username, const std::string &password, const std::string& certificatePath, CertificateWithKey::ref certificate, const ClientOptions& options, bool remember, bool loginAutomatically) { jid_ = JID(username); if (!jid_.isValid() || jid_.getNode().empty()) { loginWindow_->setMessage(QT_TRANSLATE_NOOP("", "User address invalid. User address should be of the form 'alice@wonderland.lit'")); @@ -458,13 +463,15 @@ void MainController::handleLoginRequest(const std::string &username, const std:: profileSettings_->storeString("jid", username); profileSettings_->storeString("certificate", certificatePath); profileSettings_->storeString("pass", (remember || loginAutomatically) ? password : ""); + profileSettings_->storeString("options", serializeClientOptions(options)); settings_->storeSetting(SettingConstants::LAST_LOGIN_JID, username); settings_->storeSetting(SettingConstants::LOGIN_AUTOMATICALLY, loginAutomatically); - loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate")); + loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate"), options); } password_ = password; certificate_ = certificate; + clientOptions_ = options; performLoginFromCachedCredentials(); } } @@ -524,7 +531,7 @@ void MainController::performLoginFromCachedCredentials() { if (rosterController_) { rosterController_->getWindow()->setConnecting(); } - ClientOptions clientOptions; + ClientOptions clientOptions = clientOptions_; bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS); clientOptions.forgetPassword = eagle; clientOptions.useTLS = eagle ? ClientOptions::RequireTLS : ClientOptions::UseTLSWhenAvailable; @@ -731,4 +738,88 @@ void MainController::handleQuitRequest() { } } +#define SERIALIZE_BOOL(option) result += options.option ? "1" : "0"; result += ","; +#define SERIALIZE_INT(option) result += boost::lexical_cast<std::string>(options.option); result += ","; +#define SERIALIZE_STRING(option) result += Base64::encode(createByteArray(options.option)); result += ","; +#define SERIALIZE_SAFE_STRING(option) result += safeByteArrayToString(Base64::encode(options.option)); result += ","; +#define SERIALIZE_URL(option) SERIALIZE_STRING(option.getScheme()) SERIALIZE_STRING(option.getHost()) SERIALIZE_INT(option.getPort()) SERIALIZE_STRING(option.getPath()) + +std::string MainController::serializeClientOptions(const ClientOptions& options) { + std::string result; + SERIALIZE_BOOL(useStreamCompression); + switch (options.useTLS) { + case ClientOptions::NeverUseTLS: result += "1";break; + case ClientOptions::UseTLSWhenAvailable: result += "2";break; + case ClientOptions::RequireTLS: result += "3";break; + } + result += ","; + SERIALIZE_BOOL(allowPLAINWithoutTLS); + SERIALIZE_BOOL(useStreamResumption); + SERIALIZE_BOOL(useAcks); + SERIALIZE_STRING(manualHostname); + SERIALIZE_INT(manualPort); + switch (options.proxyType) { + case ClientOptions::NoProxy: result += "1";break; + case ClientOptions::SystemConfiguredProxy: result += "2";break; + case ClientOptions::SOCKS5Proxy: result += "3";break; + case ClientOptions::HTTPConnectProxy: result += "4";break; + } + result += ","; + SERIALIZE_STRING(manualProxyHostname); + SERIALIZE_INT(manualProxyPort); + SERIALIZE_URL(boshURL); + SERIALIZE_URL(boshHTTPConnectProxyURL); + SERIALIZE_SAFE_STRING(boshHTTPConnectProxyAuthID); + SERIALIZE_SAFE_STRING(boshHTTPConnectProxyAuthPassword); + return result; +} + +#define CHECK_PARSE_LENGTH if (i >= segments.size()) {return result;} +#define PARSE_INT_RAW CHECK_PARSE_LENGTH intVal = 0; try {intVal = boost::lexical_cast<int>(segments[i]);} catch(const boost::bad_lexical_cast&) {};i++; +#define PARSE_STRING_RAW CHECK_PARSE_LENGTH stringVal = byteArrayToString(Base64::decode(segments[i]));i++; + +#define PARSE_BOOL(option) PARSE_INT_RAW; result.option = (intVal == 1); +#define PARSE_INT(option) PARSE_INT_RAW; result.option = intVal; +#define PARSE_STRING(option) PARSE_STRING_RAW; result.option = stringVal; +#define PARSE_SAFE_STRING(option) PARSE_STRING_RAW; result.option = SafeString(createSafeByteArray(stringVal)); +#define PARSE_URL(option) {PARSE_STRING_RAW; std::string scheme = stringVal; PARSE_STRING_RAW; std::string host = stringVal; PARSE_INT_RAW; int port = intVal; PARSE_STRING_RAW; std::string path = stringVal; result.option = !scheme.empty() && !host.empty() ? URL(scheme, host, port, path) : URL();} + + +ClientOptions MainController::parseClientOptions(const std::string& optionString) { + ClientOptions result; + size_t i = 0; + int intVal = 0; + std::string stringVal; + std::vector<std::string> segments = String::split(optionString, ','); + + PARSE_BOOL(useStreamCompression); + PARSE_INT_RAW; + switch (intVal) { + case 1: result.useTLS = ClientOptions::NeverUseTLS;break; + case 2: result.useTLS = ClientOptions::UseTLSWhenAvailable;break; + case 3: result.useTLS = ClientOptions::RequireTLS;break; + default:; + } + PARSE_BOOL(allowPLAINWithoutTLS); + PARSE_BOOL(useStreamResumption); + PARSE_BOOL(useAcks); + PARSE_STRING(manualHostname); + PARSE_INT(manualPort); + PARSE_INT_RAW; + switch (intVal) { + case 1: result.proxyType = ClientOptions::NoProxy;break; + case 2: result.proxyType = ClientOptions::SystemConfiguredProxy;break; + case 3: result.proxyType = ClientOptions::SOCKS5Proxy;break; + case 4: result.proxyType = ClientOptions::HTTPConnectProxy;break; + } + PARSE_STRING(manualProxyHostname); + PARSE_INT(manualProxyPort); + PARSE_URL(boshURL); + PARSE_URL(boshHTTPConnectProxyURL); + PARSE_SAFE_STRING(boshHTTPConnectProxyAuthID); + PARSE_SAFE_STRING(boshHTTPConnectProxyAuthPassword); + + return result; +} + } diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index 8f04f6c..6f7482e 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -93,7 +93,7 @@ namespace Swift { private: void resetClient(); void handleConnected(); - void handleLoginRequest(const std::string& username, const std::string& password, const std::string& certificatePath, CertificateWithKey::ref certificate, bool remember, bool loginAutomatically); + void handleLoginRequest(const std::string& username, const std::string& password, const std::string& certificatePath, CertificateWithKey::ref certificate, const ClientOptions& options, bool remember, bool loginAutomatically); void handleCancelLoginRequest(); void handleQuitRequest(); void handleChangeStatusRequest(StatusShow::Type show, const std::string &statusText); @@ -118,6 +118,8 @@ namespace Swift { void handleNotificationClicked(const JID& jid); void handleForceQuit(); void purgeCachedCredentials(); + std::string serializeClientOptions(const ClientOptions& options); + ClientOptions parseClientOptions(const std::string& optionString); private: EventLoop* eventLoop_; @@ -159,6 +161,7 @@ namespace Swift { std::string vCardPhotoHash_; std::string password_; CertificateWithKey::ref certificate_; + ClientOptions clientOptions_; boost::shared_ptr<ErrorEvent> lastDisconnectError_; bool useDelayForLatency_; UserSearchController* userSearchControllerChat_; diff --git a/Swift/Controllers/UIInterfaces/LoginWindow.h b/Swift/Controllers/UIInterfaces/LoginWindow.h index f1c2ce1..dbc77c4 100644 --- a/Swift/Controllers/UIInterfaces/LoginWindow.h +++ b/Swift/Controllers/UIInterfaces/LoginWindow.h @@ -12,6 +12,7 @@ #include <string> #include <Swiften/TLS/Certificate.h> #include <Swiften/TLS/CertificateWithKey.h> +#include <Swiften/Client/ClientOptions.h> namespace Swift { class MainWindow; @@ -24,10 +25,10 @@ namespace Swift { virtual void setShowNotificationToggle(bool) = 0; virtual void setMessage(const std::string&) = 0; virtual void setIsLoggingIn(bool loggingIn) = 0; - virtual void addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate) = 0; + virtual void addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate, const ClientOptions& options) = 0; virtual void removeAvailableAccount(const std::string& jid) = 0; /** The certificate is what is used for login, the certificatePath is used for remembering paths to populate the loginwindow with*/ - boost::signal<void (const std::string&, const std::string&, const std::string& /*CertificatePath*/, CertificateWithKey::ref /* clientCertificate */, bool /* remember password*/, bool /* login automatically */)> onLoginRequest; + boost::signal<void (const std::string&, const std::string&, const std::string& /*CertificatePath*/, CertificateWithKey::ref /* clientCertificate */, const ClientOptions& /*options*/, bool /* remember password*/, bool /* login automatically */)> onLoginRequest; virtual void setLoginAutomatically(bool loginAutomatically) = 0; virtual void quit() = 0; /** Blocking request whether a cert should be permanently trusted.*/ diff --git a/Swift/QtUI/QtConnectionSettings.ui b/Swift/QtUI/QtConnectionSettings.ui new file mode 100644 index 0000000..4086cba --- /dev/null +++ b/Swift/QtUI/QtConnectionSettings.ui @@ -0,0 +1,535 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtConnectionSettings</class> + <widget class="QDialog" name="QtConnectionSettings"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>406</width> + <height>434</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Connection Method:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="connectionMethod"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <item> + <property name="text"> + <string>Automatic</string> + </property> + </item> + <item> + <property name="text"> + <string>Manual</string> + </property> + </item> + <item> + <property name="text"> + <string>BOSH</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>238</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QStackedWidget" name="stackedWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="page_3"/> + <widget class="QWidget" name="page_2"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Secure connection:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="manual_useTLS"> + <item> + <property name="text"> + <string>Never</string> + </property> + </item> + <item> + <property name="text"> + <string>Encrypt when possible</string> + </property> + </item> + <item> + <property name="text"> + <string>Always encrypt</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="manual_allowCompression"> + <property name="text"> + <string>Allow Compression</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="manual_allowPLAINWithoutTLS"> + <property name="text"> + <string>Allow sending password over insecure connection</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>4</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QCheckBox" name="manual_manualHost"> + <property name="text"> + <string>Manually select server</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="manual_manualHostNameLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Hostname:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="manual_manualHostName"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>4</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="manual_manualHostPortLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="manual_manualHostPort"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Connection Proxy</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Proxy type:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="manual_proxyType"> + <property name="currentIndex"> + <number>1</number> + </property> + <item> + <property name="text"> + <string>None</string> + </property> + </item> + <item> + <property name="text"> + <string>Use system-configured proxy</string> + </property> + </item> + <item> + <property name="text"> + <string>SOCKS5</string> + </property> + </item> + <item> + <property name="text"> + <string>HTTP Connect</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_7"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="manual_manualProxy"> + <property name="text"> + <string>Override system-configured proxy</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <spacer name="horizontalSpacer_8"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="manual_manualProxyHostLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Hostname:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="manual_manualProxyHost"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>4</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="manual_manualProxyPortLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="manual_manualProxyPort"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>88</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_4"> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QLabel" name="bosh_uriLabel"> + <property name="text"> + <string>BOSH URI:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="bosh_uri"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>4</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="bosh_manualProxy"> + <property name="text"> + <string>Manually select HTTP proxy</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="bosh_manualProxyHostLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Hostname:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="bosh_manualProxyHost"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>4</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="bosh_manualProxyPortLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="bosh_manualProxyPort"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>80</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>QtConnectionSettings</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>QtConnectionSettings</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/Swift/QtUI/QtConnectionSettingsWindow.cpp b/Swift/QtUI/QtConnectionSettingsWindow.cpp new file mode 100644 index 0000000..324eb06 --- /dev/null +++ b/Swift/QtUI/QtConnectionSettingsWindow.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2012 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swift/QtUI/QtConnectionSettingsWindow.h" + +#include <boost/lexical_cast.hpp> + +#include <QCoreApplication> +#include <QIcon> +#include <QLabel> +#include <QVBoxLayout> +#include <QtGlobal> +#include <QPushButton> +#include <QTextEdit> +#include <QFile> +#include <QTextStream> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtConnectionSettingsWindow::QtConnectionSettingsWindow(const ClientOptions& options) : QDialog() { + ui.setupUi(this); + + connect(ui.connectionMethod, SIGNAL(currentIndexChanged(int)), ui.stackedWidget, SLOT(setCurrentIndex(int))); + + connect(ui.manual_manualHost, SIGNAL(toggled(bool)), ui.manual_manualHostNameLabel, SLOT(setEnabled(bool))); + connect(ui.manual_manualHost, SIGNAL(toggled(bool)), ui.manual_manualHostName, SLOT(setEnabled(bool))); + connect(ui.manual_manualHost, SIGNAL(toggled(bool)), ui.manual_manualHostPortLabel, SLOT(setEnabled(bool))); + connect(ui.manual_manualHost, SIGNAL(toggled(bool)), ui.manual_manualHostPort, SLOT(setEnabled(bool))); + + connect(ui.manual_manualProxy, SIGNAL(toggled(bool)), ui.manual_manualProxyHostLabel, SLOT(setEnabled(bool))); + connect(ui.manual_manualProxy, SIGNAL(toggled(bool)), ui.manual_manualProxyHost, SLOT(setEnabled(bool))); + connect(ui.manual_manualProxy, SIGNAL(toggled(bool)), ui.manual_manualProxyPortLabel, SLOT(setEnabled(bool))); + connect(ui.manual_manualProxy, SIGNAL(toggled(bool)), ui.manual_manualProxyPort, SLOT(setEnabled(bool))); + + connect(ui.bosh_manualProxy, SIGNAL(toggled(bool)), ui.bosh_manualProxyHostLabel, SLOT(setEnabled(bool))); + connect(ui.bosh_manualProxy, SIGNAL(toggled(bool)), ui.bosh_manualProxyHost, SLOT(setEnabled(bool))); + connect(ui.bosh_manualProxy, SIGNAL(toggled(bool)), ui.bosh_manualProxyPortLabel, SLOT(setEnabled(bool))); + connect(ui.bosh_manualProxy, SIGNAL(toggled(bool)), ui.bosh_manualProxyPort, SLOT(setEnabled(bool))); + + connect(ui.manual_proxyType, SIGNAL(currentIndexChanged(int)), SLOT(handleProxyTypeChanged(int))); + + ui.manual_useTLS->setCurrentIndex(2); + + ui.manual_proxyType->setCurrentIndex(0); + + ClientOptions defaults; + if (options.boshURL.empty()) { + bool isDefault = options.useStreamCompression == defaults.useStreamCompression && options.useTLS == defaults.useTLS && options.allowPLAINWithoutTLS == defaults.allowPLAINWithoutTLS && options.useStreamCompression == defaults.useStreamCompression && options.useAcks == defaults.useAcks && options.manualHostname == defaults.manualHostname && options.manualPort == defaults.manualPort && options.proxyType == defaults.proxyType && options.manualProxyHostname == defaults.manualProxyHostname && options.manualProxyPort == defaults.manualProxyPort; + if (!isDefault) { + ui.connectionMethod->setCurrentIndex(1); + ui.manual_useTLS->setCurrentIndex(options.useTLS); + ui.manual_allowPLAINWithoutTLS->setChecked(options.allowPLAINWithoutTLS); + ui.manual_allowCompression->setChecked(options.useStreamCompression); + if (!options.manualHostname.empty()) { + ui.manual_manualHost->setChecked(true); + ui.manual_manualHostName->setText(P2QSTRING(options.manualHostname)); + ui.manual_manualHostPort->setText(P2QSTRING(boost::lexical_cast<std::string>(options.manualPort))); + } + ui.manual_proxyType->setCurrentIndex(options.proxyType); + if (!options.manualProxyHostname.empty()) { + ui.manual_manualProxy->setChecked(true); + ui.manual_manualProxyHost->setText(P2QSTRING(options.manualProxyHostname)); + ui.manual_manualHostPort->setText(P2QSTRING(boost::lexical_cast<std::string>(options.manualProxyPort))); + } + } + } else { + ui.connectionMethod->setCurrentIndex(2); + ui.bosh_uri->setText(P2QSTRING(options.boshURL.toString())); + if (!options.boshHTTPConnectProxyURL.empty()) { + ui.bosh_manualProxy->setChecked(true); + ui.bosh_manualProxyHost->setText(P2QSTRING(options.boshHTTPConnectProxyURL.getHost())); + ui.bosh_manualProxyPort->setText(P2QSTRING(boost::lexical_cast<std::string>(options.boshHTTPConnectProxyURL.getPort()))); + } + } +} + +void QtConnectionSettingsWindow::handleProxyTypeChanged(int index) { + bool proxySettingsVisible = index != NoProxy && index != SystemProxy; + ui.manual_manualProxy->setVisible(proxySettingsVisible); + ui.manual_manualProxyHostLabel->setVisible(proxySettingsVisible); + ui.manual_manualProxyHost->setVisible(proxySettingsVisible); + ui.manual_manualProxyPortLabel->setVisible(proxySettingsVisible); + ui.manual_manualProxyPort->setVisible(proxySettingsVisible); +} + +ClientOptions QtConnectionSettingsWindow::getOptions() { + ClientOptions options; + if (ui.connectionMethod->currentIndex() > 0) { + /* Not automatic */ + if (ui.connectionMethod->currentIndex() == 1) { + /* Manual */ + options.useTLS = static_cast<ClientOptions::UseTLS>(ui.manual_useTLS->currentIndex()); + options.useStreamCompression = ui.manual_allowCompression->isChecked(); + options.allowPLAINWithoutTLS = ui.manual_allowPLAINWithoutTLS->isChecked(); + if (ui.manual_manualHost->isChecked()) { + options.manualHostname = Q2PSTRING(ui.manual_manualHostName->text()); + try { + options.manualPort = boost::lexical_cast<int>(Q2PSTRING(ui.manual_manualHostPort->text())); + } catch (const boost::bad_lexical_cast&) {} + } + options.proxyType = static_cast<ClientOptions::ProxyType>(ui.manual_proxyType->currentIndex()); + if (ui.manual_manualProxy->isChecked()) { + options.manualProxyHostname = Q2PSTRING(ui.manual_manualProxyHost->text()); + try { + options.manualProxyPort = boost::lexical_cast<int>(Q2PSTRING(ui.manual_manualProxyPort->text())); + } catch (const boost::bad_lexical_cast&) {} + } + } + else { + /* BOSH */ + options.boshURL = URL(Q2PSTRING(ui.bosh_uri->text())); + if (ui.bosh_manualProxy->isChecked()) { + std::string host = Q2PSTRING(ui.bosh_manualProxyHost->text()); + int port = 80; + try { + port = boost::lexical_cast<int>(Q2PSTRING(ui.bosh_manualProxyPort->text())); + } catch (const boost::bad_lexical_cast&) {} + options.boshHTTPConnectProxyURL = URL("http", host, port, ""); + } + } + } + return options; +} + +} diff --git a/Swift/QtUI/QtConnectionSettingsWindow.h b/Swift/QtUI/QtConnectionSettingsWindow.h new file mode 100644 index 0000000..2aed5d1 --- /dev/null +++ b/Swift/QtUI/QtConnectionSettingsWindow.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QDialog> + +#include "ui_QtConnectionSettings.h" + +#include <Swiften/Client/ClientOptions.h> + +namespace Swift { + class QtConnectionSettingsWindow : public QDialog { + Q_OBJECT + + public: + QtConnectionSettingsWindow(const ClientOptions& options); + + ClientOptions getOptions(); + + private slots: + void handleProxyTypeChanged(int); + + private: + enum { + NoProxy = 0, + SystemProxy = 1, + SOCKS5Proxy = 2, + HTTPProxy = 3 + }; + Ui::QtConnectionSettings ui; + }; +} diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp index 38ad25d..5caeb1f 100644 --- a/Swift/QtUI/QtLoginWindow.cpp +++ b/Swift/QtUI/QtLoginWindow.cpp @@ -40,6 +40,7 @@ #include <QtSwiftUtil.h> #include <QtMainWindow.h> #include <QtUtilities.h> +#include <QtConnectionSettingsWindow.h> #ifdef HAVE_SCHANNEL #include "CAPICertificateSelector.h" @@ -137,6 +138,13 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* set loginButton_->setDefault(true); layout->addWidget(loginButton_); + QLabel* connectionOptionsLabel = new QLabel(this); + connectionOptionsLabel->setText("<a href=\"#\"><font size='-1'>Connection Options</font></a>"); + connectionOptionsLabel->setTextFormat(Qt::RichText); + connectionOptionsLabel->setAlignment(Qt::AlignRight|Qt::AlignVCenter); + layout->addWidget(connectionOptionsLabel); + connect(connectionOptionsLabel, SIGNAL(linkActivated(const QString&)), SLOT(handleOpenConnectionOptions())); + message_ = new QLabel(this); message_->setTextFormat(Qt::RichText); message_->setWordWrap(true); @@ -296,7 +304,7 @@ void QtLoginWindow::removeAvailableAccount(const std::string& jid) { } } -void QtLoginWindow::addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate) { +void QtLoginWindow::addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate, const ClientOptions& options) { QString username = P2QSTRING(defaultJID); int index = -1; for (int i = 0; i < usernames_.count(); i++) { @@ -308,11 +316,13 @@ void QtLoginWindow::addAvailableAccount(const std::string& defaultJID, const std usernames_.append(username); passwords_.append(P2QSTRING(defaultPassword)); certificateFiles_.append(P2QSTRING(defaultCertificate)); + options_.push_back(options); username_->addItem(username); } else { usernames_[index] = username; passwords_[index] = P2QSTRING(defaultPassword); certificateFiles_[index] = P2QSTRING(defaultCertificate); + options_[index] = options; } } @@ -323,6 +333,7 @@ void QtLoginWindow::handleUsernameTextChanged() { certificateFile_ = certificateFiles_[i]; password_->setText(passwords_[i]); remember_->setChecked(password_->text() != ""); + currentOptions_ = options_[i]; } } if (!certificateFile_.isEmpty()) { @@ -376,7 +387,7 @@ void QtLoginWindow::loginClicked() { certificate = boost::make_shared<PKCS12Certificate>(certificateString, createSafeByteArray(Q2PSTRING(password_->text()))); #endif - onLoginRequest(Q2PSTRING(username_->currentText()), Q2PSTRING(password_->text()), certificateString, certificate, remember_->isChecked(), loginAutomatically_->isChecked()); + onLoginRequest(Q2PSTRING(username_->currentText()), Q2PSTRING(password_->text()), certificateString, certificate, currentOptions_, remember_->isChecked(), loginAutomatically_->isChecked()); if (settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) { /* Mustn't remember logins */ username_->clearEditText(); password_->setText(""); @@ -528,4 +539,11 @@ bool QtLoginWindow::askUserToTrustCertificatePermanently(const std::string& mess } } +void QtLoginWindow::handleOpenConnectionOptions() { + QtConnectionSettingsWindow connectionSettings(currentOptions_); + if (connectionSettings.exec() == QDialog::Accepted) { + currentOptions_ = connectionSettings.getOptions(); + } +} + } diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h index 572902e..c1966d8 100644 --- a/Swift/QtUI/QtLoginWindow.h +++ b/Swift/QtUI/QtLoginWindow.h @@ -43,7 +43,7 @@ namespace Swift { virtual void loggedOut(); virtual void setShowNotificationToggle(bool); virtual void setMessage(const std::string& message); - virtual void addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate); + virtual void addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate, const ClientOptions& options); virtual void removeAvailableAccount(const std::string& jid); virtual void setLoginAutomatically(bool loginAutomatically); virtual void setIsLoggingIn(bool loggingIn); @@ -71,6 +71,7 @@ namespace Swift { void resizeEvent(QResizeEvent* event); void moveEvent(QMoveEvent* event); void handleSettingChanged(const std::string& settingPath); + void handleOpenConnectionOptions(); protected: bool eventFilter(QObject *obj, QEvent *event); @@ -81,6 +82,7 @@ namespace Swift { QStringList usernames_; QStringList passwords_; QStringList certificateFiles_; + std::vector<ClientOptions> options_; QComboBox* username_; QLineEdit* password_; QPushButton* loginButton_; @@ -102,5 +104,6 @@ namespace Swift { QAction* xmlConsoleAction_; QAction* fileTransferOverviewAction_; TimerFactory* timerFactory_; + ClientOptions currentOptions_; }; } diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 51873a0..fe186e5 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -116,6 +116,7 @@ sources = [ "QtLineEdit.cpp", "QtJoinMUCWindow.cpp", "QtInviteToChatWindow.cpp", + "QtConnectionSettingsWindow.cpp", "Roster/RosterModel.cpp", "Roster/QtTreeWidget.cpp", # "Roster/QtTreeWidgetItem.cpp", @@ -205,6 +206,7 @@ myenv.Uic4("QtBookmarkDetailWindow.ui") myenv.Uic4("QtAffiliationEditor.ui") myenv.Uic4("QtJoinMUCWindow.ui") myenv.Uic4("QtHistoryWindow.ui") +myenv.Uic4("QtConnectionSettings.ui") myenv.Qrc("DefaultTheme.qrc") myenv.Qrc("Swift.qrc") diff --git a/Swiften/Base/URL.h b/Swiften/Base/URL.h index 7a5aa59..94dc4cb 100644 --- a/Swiften/Base/URL.h +++ b/Swiften/Base/URL.h @@ -7,6 +7,7 @@ #pragma once #include <string> +#include <boost/lexical_cast.hpp> namespace Swift { @@ -16,6 +17,14 @@ class URL { URL() : scheme(""), user(""), password(""), host(""), port(-1), path(""), isEmpty(true) { } + URL(const std::string& urlString) { + host = urlString; + port = 80; + scheme = "http"; + isEmpty = false; + //FIXME + } + URL(const std::string& scheme, const std::string& host, int port, const std::string& path) : scheme(scheme), user(), password(), host(host), port(port), path(path), isEmpty(false) { } @@ -55,7 +64,27 @@ class URL { return path; } - + const std::string toString() const { + if (isEmpty) { + return ""; + } + std::string result = scheme + "://"; + if (!user.empty()) { + result += user; + if (!password.empty()) { + result += ":" + password; + } + result += "@"; + } + result += host; + if (port > 0) { + result += ":"; + result += boost::lexical_cast<std::string>(port); + } + result += "/"; + result += path; + return result; + } private: std::string scheme; |