diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6ba4877d..61cb4648 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -130,6 +130,13 @@ if (UNIX)
message (FATAL_ERROR "Missing library: pthread")
endif()
+ # curl is used on both Linux and Mac
+ find_package (CURL)
+ if (CURL_FOUND)
+ list (APPEND libs curl)
+ else()
+ message (FATAL_ERROR "Missing library: curl")
+ endif()
if (APPLE)
set (CMAKE_CXX_FLAGS "--sysroot ${CMAKE_OSX_SYSROOT} ${CMAKE_CXX_FLAGS} -DGTEST_USE_OWN_TR1_TUPLE=1")
diff --git a/src/lib/arch/Arch.h b/src/lib/arch/Arch.h
index 9035fbae..0b6740a6 100644
--- a/src/lib/arch/Arch.h
+++ b/src/lib/arch/Arch.h
@@ -50,6 +50,7 @@
# include "arch/win32/ArchSystemWindows.h"
# include "arch/win32/ArchTaskBarWindows.h"
# include "arch/win32/ArchTimeWindows.h"
+# include "arch/win32/ArchInternetWindows.h"
#elif SYSAPI_UNIX
# include "arch/unix/ArchConsoleUnix.h"
# include "arch/unix/ArchDaemonUnix.h"
@@ -64,6 +65,7 @@
# include "arch/unix/ArchSystemUnix.h"
# include "arch/unix/ArchTaskBarXWindows.h"
# include "arch/unix/ArchTimeUnix.h"
+# include "arch/unix/ArchInternetUnix.h"
#endif
/*!
@@ -118,9 +120,11 @@ public:
static void setInstance(Arch* s) { s_instance = s; }
+ ARCH_INTERNET& internet() const { return (ARCH_INTERNET&)m_internet; }
private:
static Arch* s_instance;
+ ARCH_INTERNET m_internet;
};
//! Convenience object to lock/unlock an arch mutex
diff --git a/src/lib/arch/unix/ArchInternetUnix.cpp b/src/lib/arch/unix/ArchInternetUnix.cpp
new file mode 100644
index 00000000..06326ed6
--- /dev/null
+++ b/src/lib/arch/unix/ArchInternetUnix.cpp
@@ -0,0 +1,126 @@
+/*
+ * synergy -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "arch/unix/ArchInternetUnix.h"
+
+#include "arch/XArch.h"
+#include "common/Version.h"
+#include "base/Log.h"
+
+#include
+#include
+
+class CurlFacade {
+public:
+ CurlFacade();
+ ~CurlFacade();
+ String get(const String& url);
+ String urlEncode(const String& url);
+
+private:
+ CURL* m_curl;
+};
+
+//
+// ArchInternetUnix
+//
+
+String
+ArchInternetUnix::get(const String& url)
+{
+ CurlFacade curl;
+ return curl.get(url);
+}
+
+String
+ArchInternetUnix::urlEncode(const String& url)
+{
+ CurlFacade curl;
+ return curl.urlEncode(url);
+}
+
+//
+// CurlFacade
+//
+
+static size_t
+curlWriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
+{
+ ((std::string*)userp)->append((char*)contents, size * nmemb);
+ return size * nmemb;
+}
+
+CurlFacade::CurlFacade() :
+ m_curl(NULL)
+{
+ CURLcode init = curl_global_init(CURL_GLOBAL_ALL);
+ if (init != CURLE_OK) {
+ throw XArch("CURL global init failed.");
+ }
+
+ m_curl = curl_easy_init();
+ if (m_curl == NULL) {
+ throw XArch("CURL easy init failed.");
+ }
+}
+
+CurlFacade::~CurlFacade()
+{
+ if (m_curl != NULL) {
+ curl_easy_cleanup(m_curl);
+ }
+
+ curl_global_cleanup();
+}
+
+String
+CurlFacade::get(const String& url)
+{
+ curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);
+
+ std::stringstream userAgent;
+ userAgent << "Synergy ";
+ userAgent << kVersion;
+ curl_easy_setopt(m_curl, CURLOPT_USERAGENT, userAgent.str().c_str());
+
+ std::string result;
+ curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &result);
+
+ CURLcode code = curl_easy_perform(m_curl);
+ if (code != CURLE_OK) {
+ LOG((CLOG_ERR "curl perform error: %s", curl_easy_strerror(code)));
+ throw XArch("CURL perform failed.");
+ }
+
+ return result;
+}
+
+String
+CurlFacade::urlEncode(const String& url)
+{
+ char* resultCStr = curl_easy_escape(m_curl, url.c_str(), 0);
+
+ if (resultCStr == NULL) {
+ throw XArch("CURL escape failed.");
+ }
+
+ std::string result(resultCStr);
+ curl_free(resultCStr);
+
+ return result;
+}
diff --git a/src/lib/arch/unix/ArchInternetUnix.h b/src/lib/arch/unix/ArchInternetUnix.h
new file mode 100644
index 00000000..6fcf2b59
--- /dev/null
+++ b/src/lib/arch/unix/ArchInternetUnix.h
@@ -0,0 +1,28 @@
+/*
+ * synergy -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#define ARCH_INTERNET ArchInternetUnix
+
+#include "base/String.h"
+
+class ArchInternetUnix {
+public:
+ String get(const String& url);
+ String urlEncode(const String& url);
+};
diff --git a/src/lib/arch/win32/ArchInternetWindows.cpp b/src/lib/arch/win32/ArchInternetWindows.cpp
new file mode 100644
index 00000000..5844d15d
--- /dev/null
+++ b/src/lib/arch/win32/ArchInternetWindows.cpp
@@ -0,0 +1,224 @@
+/*
+ * synergy -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "arch/win32/ArchInternetWindows.h"
+#include "arch/win32/XArchWindows.h"
+#include "arch/Arch.h"
+#include "common/Version.h"
+
+#include
+#include
+#include
+
+struct WinINetUrl {
+ String m_scheme;
+ String m_host;
+ String m_path;
+ INTERNET_PORT m_port;
+ DWORD m_flags;
+};
+
+class WinINetRequest {
+public:
+ WinINetRequest(const String& url);
+ ~WinINetRequest();
+
+ String send();
+ void openSession();
+ void connect();
+ void openRequest();
+
+private:
+ HINTERNET m_session;
+ HINTERNET m_connect;
+ HINTERNET m_request;
+ WinINetUrl m_url;
+ bool m_used;
+};
+
+//
+// ArchInternetWindows
+//
+
+String
+ArchInternetWindows::get(const String& url)
+{
+ WinINetRequest request(url);
+ return request.send();
+}
+
+String
+ArchInternetWindows::urlEncode(const String& url)
+{
+ TCHAR buffer[1024];
+ DWORD bufferSize = sizeof(buffer);
+
+ if (UrlEscape(url.c_str(), buffer, &bufferSize, URL_ESCAPE_UNSAFE) != S_OK) {
+ throw XArch(new XArchEvalWindows());
+ }
+
+ String result(buffer);
+
+ // the win32 url encoding funcitons are pretty useless (to us) and only
+ // escape "unsafe" chars, but not + or =, so we need to replace these
+ // manually (and probably many other chars).
+ synergy::string::findReplaceAll(result, "+", "%2B");
+ synergy::string::findReplaceAll(result, "=", "%3D");
+
+ return result;
+}
+
+//
+// WinINetRequest
+//
+
+static WinINetUrl parseUrl(const String& url);
+
+WinINetRequest::WinINetRequest(const String& url) :
+ m_session(NULL),
+ m_connect(NULL),
+ m_request(NULL),
+ m_used(false),
+ m_url(parseUrl(url))
+{
+}
+
+WinINetRequest::~WinINetRequest()
+{
+ if (m_request != NULL) {
+ InternetCloseHandle(m_request);
+ }
+
+ if (m_connect != NULL) {
+ InternetCloseHandle(m_connect);
+ }
+
+ if (m_session != NULL) {
+ InternetCloseHandle(m_session);
+ }
+}
+
+String
+WinINetRequest::send()
+{
+ if (m_used) {
+ throw XArch("class is one time use.");
+ }
+ m_used = true;
+
+ openSession();
+ connect();
+ openRequest();
+
+ String headers("Content-Type: text/html");
+ if (!HttpSendRequest(m_request, headers.c_str(), (DWORD)headers.length(), NULL, NULL)) {
+ throw XArch(new XArchEvalWindows());
+ }
+
+ std::stringstream result;
+ CHAR buffer[1025];
+ DWORD read = 0;
+
+ while (InternetReadFile(m_request, buffer, sizeof(buffer) - 1, &read) && (read != 0)) {
+ buffer[read] = 0;
+ result << buffer;
+ read = 0;
+ }
+
+ return result.str();
+}
+
+void
+WinINetRequest::openSession()
+{
+ std::stringstream userAgent;
+ userAgent << "Synergy ";
+ userAgent << kVersion;
+
+ m_session = InternetOpen(
+ userAgent.str().c_str(),
+ INTERNET_OPEN_TYPE_PRECONFIG,
+ NULL,
+ NULL,
+ NULL);
+
+ if (m_session == NULL) {
+ throw XArch(new XArchEvalWindows());
+ }
+}
+
+void
+WinINetRequest::connect()
+{
+ m_connect = InternetConnect(
+ m_session,
+ m_url.m_host.c_str(),
+ m_url.m_port,
+ NULL,
+ NULL,
+ INTERNET_SERVICE_HTTP,
+ NULL,
+ NULL);
+
+ if (m_connect == NULL) {
+ throw XArch(new XArchEvalWindows());
+ }
+}
+
+void
+WinINetRequest::openRequest()
+{
+ m_request = HttpOpenRequest(
+ m_connect,
+ "GET",
+ m_url.m_path.c_str(),
+ HTTP_VERSION,
+ NULL,
+ NULL,
+ m_url.m_flags,
+ NULL);
+
+ if (m_request == NULL) {
+ throw XArch(new XArchEvalWindows());
+ }
+}
+
+// nb: i tried to use InternetCrackUrl here, but couldn't quite get that to
+// work. here's some (less robust) code to split the url into components.
+// this works fine with simple urls, but doesn't consider the full url spec.
+static WinINetUrl
+parseUrl(const String& url)
+{
+ WinINetUrl parsed;
+
+ size_t schemeEnd = url.find("://");
+ size_t hostEnd = url.find('/', schemeEnd + 3);
+
+ parsed.m_scheme = url.substr(0, schemeEnd);
+ parsed.m_host = url.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
+ parsed.m_path = url.substr(hostEnd);
+
+ parsed.m_port = INTERNET_DEFAULT_HTTP_PORT;
+ parsed.m_flags = 0;
+
+ if (parsed.m_scheme.find("https") != String::npos) {
+ parsed.m_port = INTERNET_DEFAULT_HTTPS_PORT;
+ parsed.m_flags = INTERNET_FLAG_SECURE;
+ }
+
+ return parsed;
+}
diff --git a/src/lib/arch/win32/ArchInternetWindows.h b/src/lib/arch/win32/ArchInternetWindows.h
new file mode 100644
index 00000000..3fda3334
--- /dev/null
+++ b/src/lib/arch/win32/ArchInternetWindows.h
@@ -0,0 +1,28 @@
+/*
+ * synergy -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#define ARCH_INTERNET ArchInternetWindows
+
+#include "base/String.h"
+
+class ArchInternetWindows {
+public:
+ String get(const String& url);
+ String urlEncode(const String& url);
+};
diff --git a/src/lib/synergy/ToolApp.cpp b/src/lib/synergy/ToolApp.cpp
new file mode 100644
index 00000000..8a9e1faf
--- /dev/null
+++ b/src/lib/synergy/ToolApp.cpp
@@ -0,0 +1,206 @@
+/*
+ * synergy -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "synergy/ToolApp.h"
+
+#include "synergy/ArgParser.h"
+#include "arch/Arch.h"
+#include "base/Log.h"
+#include "base/String.h"
+
+#include
+#include
+
+#if SYSAPI_WIN32
+#include "platform/MSWindowsSession.h"
+#endif
+
+#define JSON_URL "https://symless.com/account/json/"
+
+enum {
+ kErrorOk,
+ kErrorArgs,
+ kErrorException,
+ kErrorUnknown
+};
+
+UInt32
+ToolApp::run(int argc, char** argv)
+{
+ if (argc <= 1) {
+ std::cerr << "no args" << std::endl;
+ return kErrorArgs;
+ }
+
+ try {
+ ArgParser argParser(this);
+ bool result = argParser.parseToolArgs(m_args, argc, argv);
+
+ if (!result) {
+ m_bye(kExitArgs);
+ }
+
+ if (m_args.m_printActiveDesktopName) {
+#if SYSAPI_WIN32
+ MSWindowsSession session;
+ String name = session.getActiveDesktopName();
+ if (name.empty()) {
+ LOG((CLOG_CRIT "failed to get active desktop name"));
+ return kExitFailed;
+ }
+ else {
+ String output = synergy::string::sprintf("activeDesktop:%s", name.c_str());
+ LOG((CLOG_INFO "%s", output.c_str()));
+ }
+#endif
+ }
+ else if (m_args.m_loginAuthenticate) {
+ loginAuth();
+ }
+ else if (m_args.m_getInstalledDir) {
+ std::cout << ARCH->getInstalledDirectory() << std::endl;
+ }
+ else if (m_args.m_getProfileDir) {
+ std::cout << ARCH->getProfileDirectory() << std::endl;
+ }
+ else if (m_args.m_getArch) {
+ std::cout << ARCH->getPlatformName() << std::endl;
+ }
+ else if (m_args.m_notifyUpdate) {
+ notifyUpdate();
+ }
+ else if (m_args.m_notifyActivation) {
+ notifyActivation();
+ }
+ else {
+ throw XSynergy("Nothing to do");
+ }
+ }
+ catch (std::exception& e) {
+ LOG((CLOG_CRIT "An error occurred: %s\n", e.what()));
+ return kExitFailed;
+ }
+ catch (...) {
+ LOG((CLOG_CRIT "An unknown error occurred.\n"));
+ return kExitFailed;
+ }
+
+#if WINAPI_XWINDOWS
+ // HACK: avoid sigsegv on linux
+ m_bye(kErrorOk);
+#endif
+
+ return kErrorOk;
+}
+
+void
+ToolApp::help()
+{
+}
+
+void
+ToolApp::loginAuth()
+{
+ String credentials;
+ std::cin >> credentials;
+
+ std::vector parts = synergy::string::splitString(credentials, ':');
+ size_t count = parts.size();
+
+ if (count == 2 ) {
+ String email = parts[0];
+ String password = parts[1];
+
+ std::stringstream ss;
+ ss << JSON_URL << "auth/";
+ ss << "?email=" << ARCH->internet().urlEncode(email);
+ ss << "&password=" << password;
+
+ std::cout << ARCH->internet().get(ss.str()) << std::endl;
+ }
+ else {
+ throw XSynergy("Invalid credentials.");
+ }
+}
+
+void
+ToolApp::notifyUpdate()
+{
+ String data;
+ std::cin >> data;
+
+ std::vector parts = synergy::string::splitString(data, ':');
+ size_t count = parts.size();
+
+ if (count == 3) {
+ std::stringstream ss;
+ ss << JSON_URL << "notify/update";
+ ss << "?from=" << parts[0];
+ ss << "&to=" << parts[1];
+ ss << "&serial=" << parts[2];
+
+ std::cout << ARCH->internet().get(ss.str()) << std::endl;
+ }
+ else {
+ throw XSynergy("Invalid update data.");
+ }
+}
+
+void
+ToolApp::notifyActivation()
+{
+ String info;
+ std::cin >> info;
+
+ std::vector parts = synergy::string::splitString(info, ':');
+ size_t count = parts.size();
+
+ if (count == 3 || count == 4) {
+ String action = parts[0];
+ String identity = parts[1];
+ String macHash = parts[2];
+ String os;
+
+ if (count == 4) {
+ os = parts[3];
+ }
+ else {
+ os = ARCH->getOSName();
+ }
+
+ std::stringstream ss;
+ ss << JSON_URL << "notify/";
+ ss << "?action=" << action;
+ ss << "&identity=" << ARCH->internet().urlEncode(identity);
+ ss << "&mac=" << ARCH->internet().urlEncode(macHash);
+ ss << "&os=" << ARCH->internet().urlEncode(ARCH->getOSName());
+ ss << "&arch=" << ARCH->internet().urlEncode(ARCH->getPlatformName());
+
+ try {
+ std::cout << ARCH->internet().get(ss.str()) << std::endl;
+ }
+ catch (std::exception& e) {
+ LOG((CLOG_NOTE "An error occurred during notification: %s\n", e.what()));
+ }
+ catch (...) {
+ LOG((CLOG_NOTE "An unknown error occurred during notification.\n"));
+ }
+ }
+ else {
+ LOG((CLOG_NOTE "notification failed"));
+ }
+}
diff --git a/src/test/integtests/arch/ArchInternetTests.cpp b/src/test/integtests/arch/ArchInternetTests.cpp
new file mode 100644
index 00000000..95823e9f
--- /dev/null
+++ b/src/test/integtests/arch/ArchInternetTests.cpp
@@ -0,0 +1,37 @@
+/*
+ * synergy -- mouse and keyboard sharing utility
+ * Copyright (C) 2014-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "arch/Arch.h"
+
+#include "test/global/gtest.h"
+
+#define TEST_URL "https://symless.com/tests/?testString"
+//#define TEST_URL "http://localhost/synergy/tests/?testString"
+
+TEST(ArchInternetTests, get)
+{
+ ARCH_INTERNET internet;
+ String result = internet.get(TEST_URL);
+ ASSERT_EQ("Hello world!", result);
+}
+
+TEST(ArchInternetTests, urlEncode)
+{
+ ARCH_INTERNET internet;
+ String result = internet.urlEncode("hello=+&world");
+ ASSERT_EQ("hello%3D%2B%26world", result);
+}