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); +}