./0000755000004100000410000000000013066525473011256 5ustar www-datawww-data./CMakeLists.txt0000644000004100000410000000755313066525473014030 0ustar www-datawww-dataproject(url-dispatcher C CXX) cmake_minimum_required(VERSION 2.8.9) string(TOLOWER "${CMAKE_BUILD_TYPE}" cmake_build_type_lower) # Build types should always be lowercase but sometimes they are not. if ("${cmake_build_type_lower}" STREQUAL "debug") option (werror "Treat warnings as errors." FALSE) else() option (werror "Treat warnings as errors." TRUE) endif() enable_testing () option (enable_lcov "Generate Coverage Reports" ON) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}") set(PACKAGE ${CMAKE_PROJECT_NAME}) set(GETTEXT_PACKAGE ${CMAKE_PROJECT_NAME}) find_package(PkgConfig REQUIRED) find_package(GDbus) include(GNUInstallDirs) include(CheckIncludeFile) include(CheckFunctionExists) include(UseGlibGeneration) include(UseConstantBuilder) # Workaround for libexecdir on debian if (EXISTS "/etc/debian_version") set(CMAKE_INSTALL_LIBEXECDIR ${CMAKE_INSTALL_LIBDIR}) set(CMAKE_INSTALL_FULL_LIBEXECDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}") endif() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") # https://bugzilla.gnome.org/show_bug.cgi?id=669355 # Since gdbus-codegen does not produce warning-free code # and we use -Werror, only enable these warnings for debug # builds. Drop this once the issue has been fixed and landed. if ("${cmake_build_type_lower}" STREQUAL debug) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wpedantic")# -Wextra") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpedantic -Wextra") endif() if (${werror}) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -Werror") endif() pkg_check_modules(UBUNTU_APP_LAUNCH REQUIRED ubuntu-app-launch-3>=0.5) include_directories(${UBUNTU_APP_LAUNCH_INCLUDE_DIRS}) pkg_check_modules(GLIB2 REQUIRED glib-2.0) include_directories(${GLIB2_INCLUDE_DIRS}) pkg_check_modules(GOBJECT2 REQUIRED gobject-2.0) include_directories(${GOBJECT2_INCLUDE_DIRS}) pkg_check_modules(GIO2 REQUIRED gio-2.0) include_directories(${GIO2_INCLUDE_DIRS}) pkg_check_modules(GIO_UNIX2 REQUIRED gio-unix-2.0) include_directories(${GIO_UNIX2_INCLUDE_DIRS}) pkg_check_modules(JSONGLIB REQUIRED json-glib-1.0) include_directories(${JSONGLIB_INCLUDE_DIRS}) pkg_check_modules(DBUSTEST REQUIRED dbustest-1>=14.04.0) include_directories(${DBUSTEST_INCLUDE_DIRS}) pkg_check_modules(SQLITE REQUIRED sqlite3) include_directories(${SQLITE_INCLUDE_DIRS}) pkg_check_modules(SCOPES REQUIRED libunity-scopes) include_directories(${SCOPES_INCLUDE_DIRS}) pkg_check_modules(APPARMOR REQUIRED libapparmor) include_directories(${APPARMOR_INCLUDE_DIRS}) pkg_check_modules(WHOOPSIE REQUIRED libwhoopsie) include_directories(${WHOOPSIE_INCLUDE_DIRS}) if(${LOCAL_INSTALL}) set(DBUSSERVICEDIR "${CMAKE_INSTALL_DATADIR}/dbus-1/services/") else() EXEC_PROGRAM(${PKG_CONFIG_EXECUTABLE} ARGS dbus-1 --variable session_bus_services_dir OUTPUT_VARIABLE DBUSSERVICEDIR ) endif() message("Installing DBus services to ${DBUSSERVICEDIR}") if(${LOCAL_INSTALL}) set(DBUSIFACEDIR "${CMAKE_INSTALL_DATADIR}/dbus-1/interfaces/") else() EXEC_PROGRAM(${PKG_CONFIG_EXECUTABLE} ARGS dbus-1 --variable interfaces_dir OUTPUT_VARIABLE DBUSIFACEDIR ) endif() message("Installing DBus interfaces to ${DBUSIFACEDIR}") include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -fPIC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC") add_subdirectory(data) add_subdirectory(service) add_subdirectory(liburl-dispatcher) add_subdirectory(tools) add_subdirectory(gui) add_subdirectory(po) add_subdirectory(tests) # coverage reporting find_package(CoverageReport) get_property(COVERAGE_TARGETS GLOBAL PROPERTY COVERAGE_TARGETS) get_property(COVERAGE_TESTS GLOBAL PROPERTY COVERAGE_TESTS) ENABLE_COVERAGE_REPORT( TARGETS ${COVERAGE_TARGETS} TESTS ${COVERAGE_TESTS} FILTER /usr/include ${CMAKE_BINARY_DIR}/* ) ./service/0000755000004100000410000000000013066525505012712 5ustar www-datawww-data./service/CMakeLists.txt0000644000004100000410000000526513066525505015462 0ustar www-datawww-data include(UseConstantBuilder) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_definitions( -DOVERLAY_SYSTEM_DIRECTORY="${CMAKE_INSTALL_FULL_DATADIR}/url-dispatcher/url-overlays" ) ########################### # Generated Lib ########################### add_gdbus_codegen( SERVICE_GENERATED service-iface com.canonical.URLDispatcher. ${CMAKE_SOURCE_DIR}/data/com.canonical.URLDispatcher.xml NAMESPACE ServiceIface ) add_library(service-generated STATIC ${SERVICE_GENERATED_SOURCES}) target_link_libraries(service-generated ${GLIB2_LIBRARIES} ${GOBJECT2_LIBRARIES} ${GIO2_LIBRARIES} ) ########################### # Dispatcher Lib ########################### add_library(dispatcher-lib STATIC dispatcher.h dispatcher.c glib-thread.h glib-thread.cpp overlay-tracker.h overlay-tracker.cpp overlay-tracker-iface.h overlay-tracker-mir.h overlay-tracker-mir.cpp scope-checker-facade.h scope-checker.h scope-checker.cpp) target_link_libraries(dispatcher-lib url-db-lib service-generated -pthread ${APPARMOR_LIBRARIES} ${GLIB2_LIBRARIES} ${GOBJECT2_LIBRARIES} ${GIO2_LIBRARIES} ${SCOPES_LIBRARIES} ${SQLITE_LIBRARIES} ${UBUNTU_APP_LAUNCH_LIBRARIES} ${WHOOPSIE_LIBRARIES} ) ########################### # URL DB Lib ########################### set(URL_DB_SOURCES url-db.c url-db.h create-db-sql.h ) add_constant_template(URL_DB_SOURCES create-db-sql create_db_sql "${CMAKE_CURRENT_SOURCE_DIR}/create-db.sql" ) add_library(url-db-lib STATIC ${URL_DB_SOURCES} ) target_link_libraries(url-db-lib ${GLIB2_LIBRARIES} ${SQLITE_LIBRARIES} ) ########################### # Service Executable ########################### include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_executable(service-exec service.cpp) set_target_properties(service-exec PROPERTIES OUTPUT_NAME "url-dispatcher") target_link_libraries(service-exec ${WHOOPSIE_LIBRARIES} dispatcher-lib) ########################### # Update Directory ########################### add_executable(update-directory update-directory.c) set_target_properties(update-directory PROPERTIES OUTPUT_NAME "update-directory") target_link_libraries(update-directory ${GIO2_LIBRARIES} ${JSONGLIB_LIBRARIES} ${WHOOPSIE_LIBRARIES} url-db-lib) ########################### # Coverage ########################### set_property( GLOBAL APPEND PROPERTY COVERAGE_TARGETS dispatcher-lib url-db-lib service-exec update-directory) ########################### # Installation ########################### install( TARGETS service-exec update-directory RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_LIBEXECDIR}/url-dispatcher" ) ########################### # Exec Tools ########################### add_subdirectory(url-overlay) add_subdirectory(bad-url) ./service/dispatcher.h0000644000004100000410000000267313066525443015222 0ustar www-datawww-data/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Author: Ted Gould * */ #ifndef DISPATCHER_H #define DISPATCHER_H 1 #include #include "overlay-tracker.h" #include "scope-checker.h" G_BEGIN_DECLS gboolean dispatcher_init (GMainLoop * mainloop, OverlayTracker * tracker, ScopeChecker * checker); gboolean dispatcher_shutdown (); gboolean dispatcher_url_to_appid (const gchar * url, gchar ** out_appid, const gchar ** out_url); gboolean dispatcher_appid_restrict (const gchar * appid, const gchar * package); gboolean dispatcher_is_overlay (const gchar * appid); gboolean dispatcher_send_to_app (const gchar * appid, const gchar * url); gboolean dispatcher_send_to_overlay (const gchar * app_id, const gchar * url, GDBusConnection * conn, const gchar * sender); G_END_DECLS #endif /* DISPATCHER_H */ ./service/overlay-tracker-mir.cpp0000644000004100000410000001420213066525443017315 0ustar www-datawww-data/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Ted Gould */ #include "overlay-tracker-mir.h" #include static const char * OVERLAY_HELPER_TYPE = "url-overlay"; static const char * BAD_URL_HELPER_TYPE = "bad-url"; static const char * BAD_URL_APP_ID = "url-dispatcher-bad-url-helper"; OverlayTrackerMir::OverlayTrackerMir () : thread([this] { /* Setup Helper Observer */ ubuntu_app_launch_observer_add_helper_stop(overlayHelperStoppedStatic, OVERLAY_HELPER_TYPE, this); ubuntu_app_launch_observer_add_helper_stop(overlayHelperStoppedStatic, BAD_URL_HELPER_TYPE, this); }, [this] { /* Remove Helper Observer */ ubuntu_app_launch_observer_delete_helper_stop(overlayHelperStoppedStatic, OVERLAY_HELPER_TYPE, this); ubuntu_app_launch_observer_delete_helper_stop(overlayHelperStoppedStatic, BAD_URL_HELPER_TYPE, this); }) { mir = std::shared_ptr([] { gchar * path = g_build_filename(g_get_user_runtime_dir(), "mir_socket_trusted", NULL); MirConnection * con = mir_connect_sync(path, "url-dispatcher"); g_free(path); return con; }(), [] (MirConnection * connection) { if (connection != nullptr) mir_connection_release(connection); }); if (!mir) { throw std::runtime_error("Unable to connect to Mir"); } } /* Enforce a shutdown order, sessions before connection */ OverlayTrackerMir::~OverlayTrackerMir () { thread.executeOnThread([this] { for (auto& sessionType : ongoingSessions) { while (!sessionType.second.empty()) { removeSession(sessionType.first, sessionType.second.begin()->session.get()); } } return true; }); mir.reset(); } bool OverlayTrackerMir::addOverlay (const char * appid, unsigned long pid, const char * url) { return addOverlayCore(OVERLAY_HELPER_TYPE, appid, pid, url, overlaySessionStateChangedStatic); } bool OverlayTrackerMir::addOverlayCore (const char * helper_id, const char * appid, unsigned long pid, const char * url, void (*stateChangedFunction) (MirPromptSession*, MirPromptSessionState, void *)) { OverlayData data; data.appid = appid; std::string surl(url); return thread.executeOnThread([this, helper_id, &data, pid, surl, stateChangedFunction] { g_debug("Setting up over lay for PID %d with '%s'", int(pid), data.appid.c_str()); data.session = std::shared_ptr( mir_connection_create_prompt_session_sync(mir.get(), pid, stateChangedFunction, this), [] (MirPromptSession * session) { if (session) mir_prompt_session_release_sync(session); }); if (!data.session) { g_critical("Unable to create trusted prompt session for %d with appid '%s'", (int)pid, data.appid.c_str()); return false; } std::array urls { surl.c_str(), nullptr }; auto instance = ubuntu_app_launch_start_session_helper(helper_id, data.session.get(), data.appid.c_str(), urls.data()); if (instance == nullptr) { g_critical("Unable to start helper for %d with appid '%s'", int(pid), data.appid.c_str()); return false; } data.instanceid = instance; ongoingSessions[helper_id].push_back(data); g_free(instance); return true; }); } bool OverlayTrackerMir::badUrl (unsigned long pid, const char * url) { return addOverlayCore(BAD_URL_HELPER_TYPE, BAD_URL_APP_ID, pid, url, badUrlSessionStateChangedStatic); } void OverlayTrackerMir::overlaySessionStateChangedStatic (MirPromptSession * session, MirPromptSessionState state, void * user_data) { reinterpret_cast(user_data)->sessionStateChanged(session, state, OVERLAY_HELPER_TYPE); } void OverlayTrackerMir::badUrlSessionStateChangedStatic (MirPromptSession * session, MirPromptSessionState state, void * user_data) { reinterpret_cast(user_data)->sessionStateChanged(session, state, BAD_URL_HELPER_TYPE); } void OverlayTrackerMir::removeSession (const std::string &type, MirPromptSession * session) { g_debug("Removing session: %p", (void*)session); auto& sessions = ongoingSessions[type]; for (auto it = sessions.begin(); it != sessions.end(); it++) { if (it->session.get() == session) { ubuntu_app_launch_stop_multiple_helper(type.c_str(), it->appid.c_str(), it->instanceid.c_str()); sessions.erase(it); break; } } } void OverlayTrackerMir::sessionStateChanged (MirPromptSession * session, MirPromptSessionState state, const std::string &type) { if (state != mir_prompt_session_state_stopped) { /* We only care about the stopped state */ return; } /* Executing on the Mir thread, which is nice and all, but we want to get back on our thread */ thread.executeOnThread([this, type, session]() { removeSession(type, session); }); } void OverlayTrackerMir::overlayHelperStoppedStatic (const gchar * appid, const gchar * instanceid, const gchar * helpertype, gpointer user_data) { reinterpret_cast(user_data)->overlayHelperStopped(appid, instanceid, helpertype); } void OverlayTrackerMir::overlayHelperStopped(const gchar * appid, const gchar * instanceid, const gchar * helpertype) { /* This callback will happen on our thread already, we don't need to proxy it on */ if (g_strcmp0(helpertype, OVERLAY_HELPER_TYPE) != 0 && g_strcmp0(helpertype, BAD_URL_HELPER_TYPE) != 0) { return; } /* Making the code in the loop easier to read by using std::string outside */ std::string sappid(appid); std::string sinstanceid(instanceid); for (auto it = ongoingSessions[helpertype].begin(); it != ongoingSessions[helpertype].end(); it++) { if (it->appid == sappid && it->instanceid == sinstanceid) { ongoingSessions[helpertype].erase(it); break; } } } ./service/overlay-tracker-iface.h0000644000004100000410000000167613066525443017255 0ustar www-datawww-data/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Ted Gould */ #pragma once class OverlayTrackerIface { public: virtual ~OverlayTrackerIface() = default; virtual bool addOverlay (const char * appid, unsigned long pid, const char * url) = 0; virtual bool badUrl (unsigned long pid, const char * url) = 0; }; ./service/url-db.h0000644000004100000410000000423113066525443014251 0ustar www-datawww-data/** * Copyright © 2014 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Author: Ted Gould * */ #ifndef URL_DB_H #define URL_DB_H 1 #include #include G_BEGIN_DECLS sqlite3 * url_db_create_database (); gboolean url_db_get_file_motification_time (sqlite3 * db, const gchar * filename, GTimeVal * timeval); gboolean url_db_set_file_motification_time (sqlite3 * db, const gchar * filename, GTimeVal * timeval); gboolean url_db_insert_url (sqlite3 * db, const gchar * filename, const gchar * protocol, const gchar * domainsuffix); gchar * url_db_find_url (sqlite3 * db, const gchar * protocol, const gchar * domainsuffix); GList * url_db_files_for_dir (sqlite3 * db, const gchar * dir); gboolean url_db_remove_file (sqlite3 * db, const gchar * path); G_END_DECLS #endif /* URL_DB_H */ ./service/overlay-tracker.h0000644000004100000410000000211313066525443016173 0ustar www-datawww-data/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Ted Gould */ #pragma once #include typedef struct _OverlayTracker OverlayTracker; OverlayTracker * overlay_tracker_new (); void overlay_tracker_delete (OverlayTracker * tracker); gboolean overlay_tracker_add (OverlayTracker * tracker, const char * appid, unsigned long pid, const char * url); gboolean overlay_tracker_badurl (OverlayTracker * tracker, unsigned long pid, const char * url); ./service/service.cpp0000644000004100000410000000317413066525505015063 0ustar www-datawww-data/** * Copyright (C) 2013-2017 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ extern "C" { #include "dispatcher.h" } #include #include #include /* Where it all begins */ int main (int argc, char * argv[]) { auto mainloop = g_main_loop_new(nullptr, false); g_unix_signal_add(SIGTERM, [](gpointer user_data) { g_main_loop_quit(static_cast(user_data)); return G_SOURCE_REMOVE; }, mainloop); auto tracker = overlay_tracker_new(); ScopeChecker* checker = nullptr; /* Allow disabing for testing */ if (g_getenv("URL_DISPATCHER_DISABLE_SCOPE_CHECKING") == nullptr) { checker = scope_checker_new(); } /* Initialize Dispatcher */ if (!dispatcher_init(mainloop, tracker, checker)) { return -1; } /* Run Main */ g_main_loop_run(mainloop); /* Clean up globals */ dispatcher_shutdown(); overlay_tracker_delete(tracker); scope_checker_delete(checker); g_main_loop_unref(mainloop); return 0; } ./service/overlay-tracker.cpp0000644000004100000410000000365213066525443016537 0ustar www-datawww-data/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Ted Gould */ extern "C" { #include "overlay-tracker.h" } #include "overlay-tracker-iface.h" #include "overlay-tracker-mir.h" OverlayTracker * overlay_tracker_new () { try { OverlayTrackerMir * cpptracker = new OverlayTrackerMir(); return reinterpret_cast(cpptracker); } catch (...) { return nullptr; } } void overlay_tracker_delete (OverlayTracker * tracker) { g_return_if_fail(tracker != nullptr); auto cpptracker = reinterpret_cast(tracker); delete cpptracker; return; } gboolean overlay_tracker_add (OverlayTracker * tracker, const char * appid, unsigned long pid, const gchar * url) { g_return_val_if_fail(tracker != nullptr, FALSE); g_return_val_if_fail(appid != nullptr, FALSE); g_return_val_if_fail(pid != 0, FALSE); g_return_val_if_fail(url != nullptr, FALSE); return reinterpret_cast(tracker)->addOverlay(appid, pid, url) ? TRUE : FALSE; } gboolean overlay_tracker_badurl (OverlayTracker * tracker, unsigned long pid, const gchar * url) { g_return_val_if_fail(tracker != nullptr, FALSE); g_return_val_if_fail(pid != 0, FALSE); g_return_val_if_fail(url != nullptr, FALSE); return reinterpret_cast(tracker)->badUrl(pid, url) ? TRUE : FALSE; } ./service/create-db-sql.h0000644000004100000410000000006213066525443015505 0ustar www-datawww-data#pragma once extern const char * create_db_sql; ./service/dispatcher.c0000644000004100000410000004171213066525473015215 0ustar www-datawww-data/** * Copyright (C) 2013-2017 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Author: Ted Gould * */ #include #include #include #include "dispatcher.h" #include "service-iface.h" #include "scope-checker.h" #include "url-db.h" /* Globals */ static OverlayTracker * tracker = NULL; static ScopeChecker * checker = NULL; static GCancellable * cancellable = NULL; static ServiceIfaceComCanonicalURLDispatcher * skel = NULL; static GRegex * applicationre = NULL; static GRegex * appidre = NULL; static GRegex * genericre = NULL; static GRegex * intentre = NULL; static sqlite3 * urldb = NULL; /* Errors */ enum { ERROR_BAD_URL, ERROR_RESTRICTED_URL }; G_DEFINE_QUARK(url_dispatcher, url_dispatcher_error) /* Register our errors */ static void register_dbus_errors () { g_dbus_error_register_error(url_dispatcher_error_quark(), ERROR_BAD_URL, "com.canonical.URLDispatcher.BadURL"); g_dbus_error_register_error(url_dispatcher_error_quark(), ERROR_RESTRICTED_URL, "com.canonical.URLDispatcher.RestrictedURL"); return; } /* We should have the PID now so we can make sure to file the problem on the right package. */ static void recoverable_problem_file (GObject * obj, GAsyncResult * res, gpointer user_data) { gchar * badurl = (gchar *)user_data; GVariant * pid_tuple = NULL; GError * error = NULL; pid_tuple = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error); if (error != NULL) { g_warning("Unable to get PID for calling program with URL '%s': %s", badurl, error->message); g_free(badurl); g_error_free(error); return; } guint32 pid = 0; g_variant_get(pid_tuple, "(u)", &pid); g_variant_unref(pid_tuple); /* Popup the bad url dialog */ overlay_tracker_badurl(tracker, pid, badurl); /* Report recoverable error */ const gchar * additional[3] = { "BadURL", badurl, NULL }; whoopsie_report_recoverable_problem("url-dispatcher-bad-url", pid, FALSE, additional); g_free(badurl); return; } /* Error based on the fact that we're using a restricted launch but the package doesn't match */ /* NOTE: Only sending back the data we were given. We don't want people to be able to parse the error as an info leak */ static gboolean restricted_appid (GDBusMethodInvocation * invocation, const gchar * url, const gchar * package) { g_dbus_method_invocation_return_error(invocation, url_dispatcher_error_quark(), ERROR_RESTRICTED_URL, "URL '%s' does not have a handler in package '%s'", url, package); return TRUE; } /* Say that we have a bad URL and report a recoverable error on the process that sent it to us. */ static gboolean bad_url (GDBusMethodInvocation * invocation, const gchar * url) { const gchar * sender = g_dbus_method_invocation_get_sender(invocation); GDBusConnection * conn = g_dbus_method_invocation_get_connection(invocation); /* transfer: none */ g_dbus_connection_call(conn, "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetConnectionUnixProcessID", g_variant_new("(s)", sender), G_VARIANT_TYPE("(u)"), G_DBUS_CALL_FLAGS_NONE, -1, /* timeout */ NULL, /* cancellable */ recoverable_problem_file, g_strdup(url)); g_dbus_method_invocation_return_error(invocation, url_dispatcher_error_quark(), ERROR_BAD_URL, "URL '%s' is not handleable by the URL Dispatcher", url); return TRUE; } /* Print an error if we get one */ static void send_open_cb (GObject * object, GAsyncResult * res, gpointer user_data) { GError * error = NULL; g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), res, &error); if (error != NULL) { /* Mostly just to free the error, but printing for debugging */ g_warning("Unable to send Open to dash: %s", error->message); g_error_free(error); } } /* Sends the URL to the dash, which isn't an app, but just on the bus generally. */ gboolean send_to_dash (const gchar * url) { if (url == NULL) { g_warning("Can not send nothing to the dash"); return FALSE; } GDBusConnection * bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); g_return_val_if_fail(bus != NULL, FALSE); /* Kinda sucks that we need to do this, should probably find it's way into the libUAL API if it's needed outside */ g_dbus_connection_emit_signal(bus, NULL, /* destination */ "/", /* path */ "com.canonical.UbuntuAppLaunch", /* interface */ "UnityFocusRequest", /* signal */ g_variant_new("(s)", "unity8-dash"), NULL); GVariantBuilder opendata; g_variant_builder_init(&opendata, G_VARIANT_TYPE_TUPLE); g_variant_builder_open(&opendata, G_VARIANT_TYPE_ARRAY); g_variant_builder_add_value(&opendata, g_variant_new_string(url)); g_variant_builder_close(&opendata); g_variant_builder_add_value(&opendata, g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0)); /* Using the FD.o Application interface */ g_dbus_connection_call(bus, "com.canonical.UnityDash", "/unity8_2ddash", "org.freedesktop.Application", "Open", g_variant_builder_end(&opendata), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, send_open_cb, NULL); g_object_unref(bus); return TRUE; } /* Handles taking an application and an URL and sending them to Upstart */ gboolean dispatcher_send_to_app (const gchar * app_id, const gchar * url) { g_debug("Emitting 'application-start' for APP_ID='%s' and URLS='%s'", app_id, url); if (g_strcmp0(app_id, "unity8-dash") == 0) { return send_to_dash(url); } const gchar * urls[2] = { url, NULL }; if (!ubuntu_app_launch_start_application(app_id, urls)) { g_warning("Unable to start application '%s' with URL '%s'", app_id, url); } return TRUE; } unsigned int _get_pid_from_dbus(GDBusConnection * conn, const gchar * sender) { GError * error = NULL; unsigned int pid = 0; GVariant * callret = g_dbus_connection_call_sync(conn, "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetConnectionUnixProcessID", g_variant_new("(s)", sender), G_VARIANT_TYPE("(u)"), G_DBUS_CALL_FLAGS_NONE, -1, /* timeout */ NULL, /* cancellable */ &error); if (error != NULL) { g_warning("Unable to get PID for '%s' from dbus: %s", sender, error->message); g_clear_error(&error); } else { g_variant_get_child(callret, 0, "u", &pid); g_variant_unref(callret); } return pid; } /* Handles setting up the overlay with the URL */ gboolean dispatcher_send_to_overlay (const gchar * app_id, const gchar * url, GDBusConnection * conn, const gchar * sender) { unsigned int pid = _get_pid_from_dbus(conn, sender); if (pid == 0) { return FALSE; } /* If it is from a scope we need to overlay onto the dash instead */ if (scope_checker_is_scope_pid(checker, pid)) { pid = _get_pid_from_dbus(conn, "com.canonical.UnityDash"); } return overlay_tracker_add(tracker, app_id, pid, url); } /* Check to see if this is an overlay AppID */ gboolean dispatcher_is_overlay (const gchar * appid) { const gchar * systemdir = NULL; gboolean found = FALSE; gchar * desktopname = g_strdup_printf("%s.desktop", appid); /* First time, check the environment */ if (G_UNLIKELY(systemdir == NULL)) { systemdir = g_getenv("URL_DISPATCHER_OVERLAY_DIR"); if (systemdir == NULL) { systemdir = OVERLAY_SYSTEM_DIRECTORY; } } /* Check system dir */ if (!found) { gchar * sysdir = g_build_filename(systemdir, desktopname, NULL); found = g_file_test(sysdir, G_FILE_TEST_EXISTS); g_free(sysdir); } g_free(desktopname); return found; } /* Whether we should restrict this appid based on the package name */ gboolean dispatcher_appid_restrict (const gchar * appid, const gchar * package) { if (package == NULL || package[0] == '\0') { return FALSE; } gchar * appackage = NULL; gboolean match = FALSE; if (ubuntu_app_launch_app_id_parse(appid, &appackage, NULL, NULL)) { /* Click application */ match = (g_strcmp0(package, appackage) == 0); } else { /* Legacy application */ match = (g_strcmp0(package, appid) == 0); } g_free(appackage); return !match; } /* Get a URL off of the bus */ static gboolean dispatch_url_cb (GObject * skel, GDBusMethodInvocation * invocation, const gchar * url, const gchar * package, gpointer user_data) { /* Nice debugging message depending on whether the @package variable is valid from DBus */ if (package == NULL || package[0] == '\0') { g_debug("Dispatching URL: %s", url); } else { g_debug("Dispatching Restricted URL: %s", url); g_debug("Package restriction: %s", package); } /* Check to ensure the URL is valid coming from DBus */ if (url == NULL || url[0] == '\0') { return bad_url(invocation, url); } /* Actually do it */ gchar * appid = NULL; const gchar * outurl = NULL; /* Discover the AppID */ if (!dispatcher_url_to_appid(url, &appid, &outurl)) { return bad_url(invocation, url); } /* Check for the 'unconfined' app id which is causing problems */ if (g_strcmp0(appid, "unconfined") == 0) { g_free(appid); return bad_url(invocation, url); } /* Check to see if we're allowed to use it */ if (dispatcher_appid_restrict(appid, package)) { g_free(appid); return restricted_appid(invocation, url, package); } /* We're cleared to continue */ gboolean sent = FALSE; if (!dispatcher_is_overlay(appid)) { sent = dispatcher_send_to_app(appid, outurl); } else { sent = dispatcher_send_to_overlay( appid, outurl, g_dbus_method_invocation_get_connection(invocation), g_dbus_method_invocation_get_sender(invocation)); } g_free(appid); if (sent) { g_dbus_method_invocation_return_value(invocation, NULL); } else { bad_url(invocation, url); } return sent; } /* Test a URL to find it's AppID */ static gboolean test_url_cb (GObject * skel, GDBusMethodInvocation * invocation, const gchar * const * urls, gpointer user_data) { if (urls == NULL || urls[0] == NULL || urls[0][0] == '\0') { /* Right off the bat, let's deal with these */ return bad_url(invocation, NULL); } GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); int i; for (i = 0; urls[i] != NULL; i++) { const gchar * url = urls[i]; g_debug("Testing URL: %s", url); if (url == NULL || url[0] == '\0') { g_variant_builder_clear(&builder); return bad_url(invocation, url); } gchar * appid = NULL; const gchar * outurl = NULL; if (dispatcher_url_to_appid(url, &appid, &outurl)) { GVariant * vappid = g_variant_new_take_string(appid); g_variant_builder_add_value(&builder, vappid); } else { g_variant_builder_clear(&builder); return bad_url(invocation, url); } } GVariant * varray = g_variant_builder_end(&builder); GVariant * tuple = g_variant_new_tuple(&varray, 1); g_dbus_method_invocation_return_value(invocation, tuple); return TRUE; } /* Determine the domain for an intent using the package variable */ static gchar * intent_domain (const gchar * url) { gchar * domain = NULL; GMatchInfo * intentmatch = NULL; if (g_regex_match(intentre, url, 0, &intentmatch)) { domain = g_match_info_fetch(intentmatch, 1); g_match_info_free(intentmatch); } return domain; } /* The core of the URL handling */ gboolean dispatcher_url_to_appid (const gchar * url, gchar ** out_appid, const gchar ** out_url) { g_return_val_if_fail(url != NULL, FALSE); g_return_val_if_fail(out_appid != NULL, FALSE); /* Special case the app id */ GMatchInfo * appidmatch = NULL; if (g_regex_match(appidre, url, 0, &appidmatch)) { gchar * package = g_match_info_fetch(appidmatch, 1); gchar * app = g_match_info_fetch(appidmatch, 2); gchar * version = g_match_info_fetch(appidmatch, 3); gboolean retval = FALSE; *out_appid = ubuntu_app_launch_triplet_to_app_id(package, app, version); if (*out_appid != NULL) { /* Look at the current version of the app and ensure we're not asking for an older version */ gchar * testappid = ubuntu_app_launch_triplet_to_app_id(package, app, NULL); if (g_strcmp0(*out_appid, testappid) != 0) { retval = FALSE; g_clear_pointer(out_appid, g_free); } else { retval = TRUE; } g_free(testappid); } g_free(package); g_free(app); g_free(version); g_match_info_free(appidmatch); return retval; } /* Special case the application URL */ GMatchInfo * appmatch = NULL; if (g_regex_match(applicationre, url, 0, &appmatch)) { *out_appid = g_match_info_fetch(appmatch, 1); g_match_info_free(appmatch); return TRUE; } g_match_info_free(appmatch); /* Check the URL db */ GMatchInfo * genericmatch = NULL; if (g_regex_match(genericre, url, 0, &genericmatch)) { gboolean found = FALSE; gchar * protocol = g_match_info_fetch(genericmatch, 1); gchar * domain = NULL; /* We special case the intent domain (further comment there) */ if (g_strcmp0(protocol, "intent") == 0) { domain = intent_domain(url); } else { domain = g_match_info_fetch(genericmatch, 2); } *out_appid = url_db_find_url(urldb, protocol, domain); g_debug("Protocol '%s' for domain '%s' resulting in app id '%s'", protocol, domain, *out_appid); if (*out_appid != NULL) { found = TRUE; if (out_url != NULL) { *out_url = url; } } if (g_strcmp0(protocol, "scope") == 0) { /* Add a check for the scope if we can do that, since it is a system URL that we can do more checking on */ if (!scope_checker_is_scope(checker, domain)) { found = FALSE; g_clear_pointer(out_appid, g_free); if (out_url != NULL) g_clear_pointer(out_url, g_free); } } g_free(protocol); g_free(domain); g_match_info_free(genericmatch); return found; } g_match_info_free(genericmatch); return FALSE; } /* We're goin' down cap'n */ static void name_lost (GDBusConnection * con, const gchar * name, gpointer user_data) { GMainLoop * mainloop = (GMainLoop *)user_data; g_warning("Unable to get name '%s'", name); g_main_loop_quit(mainloop); return; } /* Callback when we're connected to dbus */ static void bus_got (GObject * obj, GAsyncResult * res, gpointer user_data) { GMainLoop * mainloop = (GMainLoop *)user_data; GDBusConnection * bus = NULL; GError * error = NULL; bus = g_bus_get_finish(res, &error); if (error != NULL) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_error("Unable to connect to D-Bus"); g_main_loop_quit(mainloop); } g_error_free(error); return; } register_dbus_errors(); g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(skel), bus, "/com/canonical/URLDispatcher", &error); if (error != NULL) { g_error("Unable to export interface skeleton: %s", error->message); g_main_loop_quit(mainloop); return; } g_bus_own_name_on_connection(bus, "com.canonical.URLDispatcher", G_BUS_NAME_OWNER_FLAGS_NONE, /* flags */ NULL, /* name acquired */ name_lost, user_data, NULL); /* user data */ g_object_unref(bus); return; } /* Initialize all the globals */ gboolean dispatcher_init (GMainLoop * mainloop, OverlayTracker * intracker, ScopeChecker * inchecker) { tracker = intracker; checker = inchecker; cancellable = g_cancellable_new(); urldb = url_db_create_database(); g_return_val_if_fail(urldb != NULL, FALSE); applicationre = g_regex_new("^application:///([a-zA-Z0-9_\\.-]*)\\.desktop$", 0, 0, NULL); appidre = g_regex_new("^appid://([a-z0-9\\.-]*)/([a-zA-Z0-9-\\.]*)/([a-zA-Z0-9\\.-]*)$", 0, 0, NULL); genericre = g_regex_new("^([a-z][a-z0-9]*):(?://(?:.*@)?([a-zA-Z0-9\\.-_]*)(?::[0-9]*)?/?)?(.*)?$", 0, 0, NULL); intentre = g_regex_new("^intent://.*package=([a-zA-Z0-9\\.]*);.*$", 0, 0, NULL); g_bus_get(G_BUS_TYPE_SESSION, cancellable, bus_got, mainloop); skel = service_iface_com_canonical_urldispatcher_skeleton_new(); g_signal_connect(skel, "handle-dispatch-url", G_CALLBACK(dispatch_url_cb), NULL); g_signal_connect(skel, "handle-test-url", G_CALLBACK(test_url_cb), NULL); return TRUE; } /* Clean up all the globals */ gboolean dispatcher_shutdown () { g_cancellable_cancel(cancellable); g_object_unref(cancellable); g_object_unref(skel); g_regex_unref(applicationre); g_regex_unref(appidre); g_regex_unref(genericre); g_regex_unref(intentre); sqlite3_close(urldb); return TRUE; } ./service/glib-thread.h0000644000004100000410000000515513066525443015254 0ustar www-datawww-data/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Ted Gould */ #include #include #include namespace GLib { class ContextThread { std::thread _thread; std::shared_ptr _context; std::shared_ptr _loop; std::shared_ptr _cancel; public: ContextThread (std::function beforeLoop = [] {}, std::function afterLoop = [] {}); ~ContextThread (); void quit (); bool isCancelled (); std::shared_ptr getCancellable (); void executeOnThread (std::function work); template auto executeOnThread (std::function work) -> T { if (std::this_thread::get_id() == _thread.get_id()) { /* Don't block if we're on the same thread */ return work(); } std::promise promise; std::function magicFunc = [&promise, &work] () -> void { promise.set_value(work()); }; executeOnThread(magicFunc); auto future = promise.get_future(); future.wait(); return future.get(); } void timeout (const std::chrono::milliseconds& length, std::function work); template void timeout (const std::chrono::duration& length, std::function work) { return timeout(std::chrono::duration_cast(length), work); } void timeoutSeconds (const std::chrono::seconds& length, std::function work); template void timeoutSeconds (const std::chrono::duration& length, std::function work) { return timeoutSeconds(std::chrono::duration_cast(length), work); } private: void simpleSource (std::function srcBuilder, std::function work); }; } ./service/scope-checker.h0000644000004100000410000000177013066525443015604 0ustar www-datawww-data/** * Copyright © 2016 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #pragma once #include #include typedef struct _ScopeChecker ScopeChecker; ScopeChecker * scope_checker_new (); void scope_checker_delete (ScopeChecker * checker); bool scope_checker_is_scope (ScopeChecker * checker, const char * appid); bool scope_checker_is_scope_pid (ScopeChecker * checker, pid_t pid); ./service/create-db.sql0000644000004100000410000000050213066525443015257 0ustar www-datawww-datapragma journal_mode = WAL; begin transaction; create table if not exists configfiles (name text unique, timestamp bigint); create table if not exists urls (sourcefile integer, protocol text, domainsuffix text); create unique index if not exists urls_index on urls (sourcefile, protocol, domainsuffix); commit transaction; ./service/bad-url/0000755000004100000410000000000013066525443014241 5ustar www-datawww-data./service/bad-url/CMakeLists.txt0000644000004100000410000000077313066525443017010 0ustar www-datawww-data########################### # Bad URL Exec Tool ########################### add_definitions( -DQML_BAD_URL="${CMAKE_INSTALL_FULL_DATADIR}/url-dispatcher/bad-url.qml" ) add_executable(bad-url-exec-tool exec-tool.cpp) set_target_properties(bad-url-exec-tool PROPERTIES OUTPUT_NAME "exec-tool") target_link_libraries(bad-url-exec-tool ${GIO2_LIBRARIES} ${UBUNTU_APP_LAUNCH_LIBRARIES}) install( TARGETS bad-url-exec-tool RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_LIBEXECDIR}/ubuntu-app-launch/bad-url" ) ./service/bad-url/exec-tool.cpp0000644000004100000410000000220013066525443016636 0ustar www-datawww-data/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Ted Gould */ #include #include int main (int argc, char * argv[]) { try { ubuntu::app_launch::Helper::setExec({"qmlscene", QML_BAD_URL}); return EXIT_SUCCESS; } catch (std::runtime_error &e) { g_warning("Unable to set helper: %s", e.what()); return EXIT_FAILURE; } catch (...) { g_warning("Unknown failure setting exec line"); return EXIT_FAILURE; } } ./service/scope-checker-facade.h0000644000004100000410000000155413066525443017005 0ustar www-datawww-data/** * Copyright © 2016 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #pragma once #include class RuntimeFacade { public: RuntimeFacade () = default; virtual ~RuntimeFacade () = default; virtual unity::scopes::RegistryProxy registry () = 0; }; ./service/overlay-tracker-mir.h0000644000004100000410000000443513066525443016771 0ustar www-datawww-data/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Ted Gould */ #pragma once #include #include #include #include #include #include "glib-thread.h" #include "overlay-tracker-iface.h" class OverlayTrackerMir : public OverlayTrackerIface { private: struct OverlayData { std::string appid; std::string instanceid; std::shared_ptr session; }; GLib::ContextThread thread; std::shared_ptr mir; std::map> ongoingSessions; public: OverlayTrackerMir (); ~OverlayTrackerMir (); bool addOverlay (const char * appid, unsigned long pid, const char * url) override; bool badUrl (unsigned long pid, const char * url) override; private: /* Overlay Functions */ void removeSession (const std::string &type, MirPromptSession * session); static void badUrlSessionStateChangedStatic (MirPromptSession * session, MirPromptSessionState state, void * user_data); static void overlaySessionStateChangedStatic (MirPromptSession * session, MirPromptSessionState state, void * user_data); void sessionStateChanged (MirPromptSession * session, MirPromptSessionState state, const std::string &type); static void overlayHelperStoppedStatic (const gchar * appid, const gchar * instanceid, const gchar * helpertype, gpointer user_data); void overlayHelperStopped(const gchar * appid, const gchar * instanceid, const gchar * helpertype); bool addOverlayCore (const gchar * helperid, const gchar * appid, unsigned long pid, const gchar * url, void (*stateChangedFunction) (MirPromptSession*, MirPromptSessionState, void *)); }; ./service/url-overlay/0000755000004100000410000000000013066525473015177 5ustar www-datawww-data./service/url-overlay/CMakeLists.txt0000644000004100000410000000071413066525473017741 0ustar www-datawww-data ########################### # URL Overlay Exec Tool ########################### add_executable(url-overlay-exec-tool exec-tool.c) set_target_properties(url-overlay-exec-tool PROPERTIES OUTPUT_NAME "exec-tool") target_link_libraries(url-overlay-exec-tool ${GIO2_LIBRARIES} ${UBUNTU_APP_LAUNCH_LIBRARIES} ${WHOOPSIE_LIBRARIES}) install( TARGETS url-overlay-exec-tool RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_LIBEXECDIR}/ubuntu-app-launch/url-overlay" ) ./service/url-overlay/exec-tool.c0000644000004100000410000000650713066525473017252 0ustar www-datawww-data/* * Copyright © 2014 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Ted Gould */ #include #include #include gchar * build_exec (const gchar * appid, const gchar * directory) { gchar * appid_desktop = g_strdup_printf("%s.desktop", appid); gchar * desktopfilepath = g_build_filename(directory, appid_desktop, NULL); g_free(appid_desktop); if (!g_file_test(desktopfilepath, G_FILE_TEST_EXISTS)) { g_free(desktopfilepath); return NULL; } GError * error = NULL; GKeyFile * keyfile = g_key_file_new(); g_key_file_load_from_file(keyfile, desktopfilepath, G_KEY_FILE_NONE, &error); if (error != NULL) { g_error("Unable to read url-overlay desktop file '%s': %s", desktopfilepath, error->message); g_free(desktopfilepath); g_key_file_free(keyfile); g_error_free(error); return NULL; } g_free(desktopfilepath); if (!g_key_file_has_key(keyfile, "Desktop Entry", "Exec", NULL)) { g_error("Desktop file for '%s' in '%s' does not have 'Exec' key", appid, directory); g_key_file_free(keyfile); return NULL; } gchar * exec = g_key_file_get_string(keyfile, "Desktop Entry", "Exec", NULL); g_key_file_free(keyfile); return exec; } gchar * build_dir (const gchar * appid) { gchar * package = NULL; /* 'Parse' the App ID */ if (!ubuntu_app_launch_app_id_parse(appid, &package, NULL, NULL)) { g_warning("Unable to parse App ID: '%s'", appid); return NULL; } return NULL; } int main (int argc, char * argv[]) { /* Build up our exec */ const gchar * appid = g_getenv("APP_ID"); if (appid == NULL) { whoopsie_report_recoverable_problem("url-dispatcher-url-overlay-no-appid", 0, TRUE, NULL); return -1; } gchar * exec = NULL; /* Allow for environment override */ const gchar * envdir = g_getenv("URL_DISPATCHER_OVERLAY_DIR"); if (G_UNLIKELY(envdir != NULL)) { /* Mostly for testing */ exec = build_exec(appid, envdir); } /* Try the system directory */ if (exec == NULL) { exec = build_exec(appid, OVERLAY_SYSTEM_DIRECTORY); } if (exec == NULL) { const gchar * props[3] = { "AppID", appid, NULL }; whoopsie_report_recoverable_problem("url-dispatcher-url-overlay-bad-appid", 0, TRUE, props); return -1; } gchar * dir = build_dir(appid); /* NOTE: Dir will be NULL for system apps */ GDBusConnection * bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); g_return_val_if_fail(bus != NULL, -1); gboolean sended = ubuntu_app_launch_helper_set_exec(exec, dir); g_free(exec); g_free(dir); /* Ensuring the messages get on the bus before we quit */ g_dbus_connection_flush_sync(bus, NULL, NULL); g_clear_object(&bus); if (sended) { return 0; } else { g_critical("Unable to send exec to Upstart"); return -1; } } ./service/url-db.c0000644000004100000410000002335413066525443014253 0ustar www-datawww-data/** * Copyright © 2014 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Author: Ted Gould * */ #include #include "url-db.h" #include "create-db-sql.h" #define DB_SCHEMA_VERSION "1" sqlite3 * url_db_create_database () { const gchar * cachedir = g_getenv("URL_DISPATCHER_CACHE_DIR"); /* Mostly for testing */ if (G_LIKELY(cachedir == NULL)) { cachedir = g_get_user_cache_dir(); } gchar * urldispatchercachedir = g_build_filename(cachedir, "url-dispatcher", NULL); if (!g_file_test(urldispatchercachedir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { gint cachedirokay = g_mkdir_with_parents(urldispatchercachedir, 1 << 6 | 1 << 7 | 1 << 8); // 700 if (cachedirokay != 0) { g_warning("Unable to make or find cache directory '%s'", urldispatchercachedir); g_free(urldispatchercachedir); return NULL; } } gchar * dbfilename = g_build_filename(urldispatchercachedir, "urls-" DB_SCHEMA_VERSION ".db", NULL); g_free(urldispatchercachedir); int open_status = SQLITE_ERROR; sqlite3 * db = NULL; open_status = sqlite3_open(dbfilename, &db); if (open_status != SQLITE_OK) { g_warning("Unable to open URL database: %s", sqlite3_errmsg(db)); g_free(dbfilename); if (db != NULL) { sqlite3_close(db); } return NULL; } g_free(dbfilename); int exec_status = SQLITE_ERROR; char * failstring = NULL; /* If the tables already exist, this command does nothing, because * the SQL says to create "if not exists". We run it always to * make this robust against the case where we are killed between * creating the db file and creating the tables. */ exec_status = sqlite3_exec(db, create_db_sql, NULL, NULL, &failstring); if (exec_status != SQLITE_OK) { g_warning("Unable to create tables: %s", failstring); sqlite3_free(failstring); sqlite3_close(db); return NULL; } return db; } gboolean url_db_get_file_motification_time (sqlite3 * db, const gchar * filename, GTimeVal * timeval) { g_return_val_if_fail(db != NULL, FALSE); g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(timeval != NULL, FALSE); timeval->tv_sec = 0; timeval->tv_usec = 0; sqlite3_stmt * stmt; if (sqlite3_prepare_v2(db, "select timestamp from configfiles where name = ?1", -1, /* length */ &stmt, NULL) != SQLITE_OK) { g_warning("Unable to parse SQL to get file times: %s", sqlite3_errmsg(db)); return FALSE; } sqlite3_bind_text(stmt, 1, filename, -1, SQLITE_TRANSIENT); gboolean valueset = FALSE; int exec_status = SQLITE_ROW; while ((exec_status = sqlite3_step(stmt)) == SQLITE_ROW) { if (timeval->tv_sec != 0) { g_warning("Seemingly two timestamps for the same file"); } timeval->tv_sec = sqlite3_column_int(stmt, 0); valueset = TRUE; } sqlite3_finalize(stmt); if (exec_status != SQLITE_DONE) { g_warning("Unable to execute insert"); return FALSE; } return valueset; } gboolean url_db_set_file_motification_time (sqlite3 * db, const gchar * filename, GTimeVal * timeval) { g_return_val_if_fail(db != NULL, FALSE); g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(timeval != NULL, FALSE); sqlite3_stmt * stmt; if (sqlite3_prepare_v2(db, "insert or replace into configfiles values (?1, ?2)", -1, /* length */ &stmt, NULL) != SQLITE_OK) { g_warning("Unable to parse SQL to set file times: %s", sqlite3_errmsg(db)); return FALSE; } sqlite3_bind_text(stmt, 1, filename, -1, SQLITE_TRANSIENT); sqlite3_bind_int(stmt, 2, timeval->tv_sec); int exec_status = SQLITE_ROW; while ((exec_status = sqlite3_step(stmt)) == SQLITE_ROW) {} sqlite3_finalize(stmt); if (exec_status != SQLITE_DONE) { g_warning("Unable to execute insert"); return FALSE; } return TRUE; } gboolean url_db_insert_url (sqlite3 * db, const gchar * filename, const gchar * protocol, const gchar * domainsuffix) { g_return_val_if_fail(db != NULL, FALSE); g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(protocol != NULL, FALSE); if (domainsuffix == NULL) { domainsuffix = ""; } sqlite3_stmt * stmt; if (sqlite3_prepare_v2(db, "insert or replace into urls select rowid, ?2, ?3 from configfiles where name = ?1", -1, /* length */ &stmt, NULL) != SQLITE_OK) { g_warning("Unable to parse SQL to insert"); return FALSE; } sqlite3_bind_text(stmt, 1, filename, -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, protocol, -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 3, domainsuffix, -1, SQLITE_TRANSIENT); int exec_status = SQLITE_ROW; while ((exec_status = sqlite3_step(stmt)) == SQLITE_ROW) {} sqlite3_finalize(stmt); if (exec_status != SQLITE_DONE) { g_warning("Unable to execute insert: %s", sqlite3_errmsg(db)); return FALSE; } return TRUE; } gchar * url_db_find_url (sqlite3 * db, const gchar * protocol, const gchar * domainsuffix) { g_return_val_if_fail(db != NULL, NULL); g_return_val_if_fail(protocol != NULL, NULL); if (domainsuffix == NULL) { domainsuffix = ""; } sqlite3_stmt * stmt; if (sqlite3_prepare_v2(db, "select configfiles.name from configfiles, urls where urls.sourcefile = configfiles.rowid and urls.protocol = ?1 and ?2 like '%' || urls.domainsuffix order by length(urls.domainsuffix) desc limit 1", -1, /* length */ &stmt, NULL) != SQLITE_OK) { g_warning("Unable to parse SQL to find url: %s", sqlite3_errmsg(db)); return NULL; } sqlite3_bind_text(stmt, 1, protocol, -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, domainsuffix, -1, SQLITE_TRANSIENT); gchar * filename = NULL; int exec_status = SQLITE_ROW; while ((exec_status = sqlite3_step(stmt)) == SQLITE_ROW && filename == NULL) { filename = g_strdup((const gchar *)sqlite3_column_text(stmt, 0)); } gchar * output = NULL; if (filename != NULL) { g_debug("Found file: '%s'", filename); gchar * basename = g_path_get_basename(filename); gchar * suffix = g_strrstr(basename, ".url-dispatcher"); if (suffix != NULL) /* This should never not happen, but it's too scary not to throw this 'if' in */ suffix[0] = '\0'; output = g_strdup(basename); g_free(basename); g_free(filename); } sqlite3_finalize(stmt); if (exec_status != SQLITE_DONE) { g_warning("Unable to execute insert: %s", sqlite3_errmsg(db)); g_free(output); return NULL; } return output; } GList * url_db_files_for_dir (sqlite3 * db, const gchar * dir) { g_return_val_if_fail(db != NULL, NULL); if (dir == NULL) { dir = ""; } sqlite3_stmt * stmt; if (sqlite3_prepare_v2(db, "select name from configfiles where name like ?1", -1, /* length */ &stmt, NULL) != SQLITE_OK) { g_warning("Unable to parse SQL to find files: %s", sqlite3_errmsg(db)); return NULL; } gchar * dir_search = g_strdup_printf("%s%%", dir); sqlite3_bind_text(stmt, 1, dir_search, -1, SQLITE_TRANSIENT); GList * filelist = NULL; int exec_status = SQLITE_ROW; while ((exec_status = sqlite3_step(stmt)) == SQLITE_ROW) { gchar * name = g_strdup((const gchar *)sqlite3_column_text(stmt, 0)); filelist = g_list_prepend(filelist, name); } sqlite3_finalize(stmt); g_free(dir_search); if (exec_status != SQLITE_DONE) { g_warning("Unable to execute insert: %s", sqlite3_errmsg(db)); g_list_free_full(filelist, g_free); return NULL; } return filelist; } /* Remove a file from the database along with all URLs that were built because of it. */ gboolean url_db_remove_file (sqlite3 * db, const gchar * path) { g_return_val_if_fail(db != NULL, FALSE); g_return_val_if_fail(path != NULL, FALSE); /* Start a transaction so the database doesn't end up in an inconsistent state */ if (sqlite3_exec(db, "begin", NULL, NULL, NULL) != SQLITE_OK) { g_warning("Unable to start transaction to delete: %s", sqlite3_errmsg(db)); return FALSE; } /* Remove all URLs for file */ sqlite3_stmt * stmt; if (sqlite3_prepare_v2(db, "delete from urls where sourcefile in (select rowid from configfiles where name = ?1);", -1, /* length */ &stmt, NULL) != SQLITE_OK) { g_warning("Unable to parse SQL to remove urls: %s", sqlite3_errmsg(db)); goto rollback; } sqlite3_bind_text(stmt, 1, path, -1, SQLITE_TRANSIENT); int exec_status = SQLITE_ROW; while ((exec_status = sqlite3_step(stmt)) == SQLITE_ROW) { } sqlite3_finalize(stmt); if (exec_status != SQLITE_DONE) { g_warning("Unable to execute removal of URLs: %s", sqlite3_errmsg(db)); goto rollback; } /* Remove references to the file */ stmt = NULL; if (sqlite3_prepare_v2(db, "delete from configfiles where name = ?1;", -1, /* length */ &stmt, NULL) != SQLITE_OK) { g_warning("Unable to parse SQL to remove urls: %s", sqlite3_errmsg(db)); goto rollback; } sqlite3_bind_text(stmt, 1, path, -1, SQLITE_TRANSIENT); while ((exec_status = sqlite3_step(stmt)) == SQLITE_ROW) { } sqlite3_finalize(stmt); if (exec_status != SQLITE_DONE) { g_warning("Unable to execute removal of file: %s", sqlite3_errmsg(db)); goto rollback; } /* Commit the full transaction */ if (sqlite3_exec(db, "commit", NULL, NULL, NULL) != SQLITE_OK) { g_warning("Unable to commit transaction to delete: %s", sqlite3_errmsg(db)); goto rollback; } return TRUE; rollback: if (sqlite3_exec(db, "rollback", NULL, NULL, NULL) != SQLITE_OK) { g_warning("Unable to rollback transaction: %s", sqlite3_errmsg(db)); } return FALSE; } ./service/scope-checker.cpp0000644000004100000410000000514113066525443016133 0ustar www-datawww-data/** * Copyright © 2016 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ extern "C" { #include "scope-checker.h" #include #include } #include #include "scope-checker-facade.h" class RuntimeReal : public RuntimeFacade { public: unity::scopes::Runtime::UPtr runtime; RuntimeReal () { runtime = unity::scopes::Runtime::create(); } unity::scopes::RegistryProxy registry () override { return runtime->registry(); } }; ScopeChecker * scope_checker_new () { auto fu_runtime = new RuntimeReal(); return reinterpret_cast(fu_runtime); } void scope_checker_delete (ScopeChecker * checker) { auto runtime = reinterpret_cast(checker); delete runtime; } bool scope_checker_is_scope (ScopeChecker * checker, const char * appid) { if (checker == nullptr) { g_warning("%s:%d: Checker is '(null)'", __FILE__, __LINE__); return false; } if (appid == nullptr) { g_warning("%s:%d: appid is '(null)'", __FILE__, __LINE__); return false; } auto runtime = reinterpret_cast(checker); try { auto registry = runtime->registry(); registry->get_metadata(appid); return true; } catch (unity::scopes::NotFoundException e) { return false; } catch (...) { g_warning("Unable to read the Unity Scopes Registry"); return false; } } bool scope_checker_is_scope_pid (ScopeChecker * checker, pid_t pid) { if (pid == 0) return false; char * aa = nullptr; if (aa_gettaskcon(pid, &aa, nullptr) != 0) { return false; } if (aa == nullptr) return false; std::string appid(aa); free(aa); if (appid == "unconfined") { /* We're not going to support unconfined scopes, too hard */ return false; } gchar * pkg = nullptr; gchar * app = nullptr; if (ubuntu_app_launch_app_id_parse(appid.c_str(), &pkg, &app, nullptr)) { appid= pkg; appid += "_"; appid += app; g_free(pkg); g_free(app); } g_debug("PID %d is short App ID %s", pid, appid.c_str()); return scope_checker_is_scope(checker, appid.c_str()); } ./service/glib-thread.cpp0000644000004100000410000001244713066525443015611 0ustar www-datawww-data/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Ted Gould */ #include "glib-thread.h" namespace GLib { ContextThread::ContextThread (std::function beforeLoop, std::function afterLoop) : _context(nullptr) , _loop(nullptr) { _cancel = std::shared_ptr(g_cancellable_new(), [](GCancellable * cancel) { if (cancel != nullptr) { g_cancellable_cancel(cancel); g_object_unref(cancel); } }); std::promise, std::shared_ptr>> context_promise; /* NOTE: We copy afterLoop but reference beforeLoop. We're blocking so we know that beforeLoop will stay valid long enough, but we can't say the same for afterLoop */ _thread = std::thread([&context_promise, &beforeLoop, afterLoop, this]() -> void { /* Build up the context and loop for the async events and a place for GDBus to send its events back to */ auto context = std::shared_ptr(g_main_context_new(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); }); auto loop = std::shared_ptr(g_main_loop_new(context.get(), FALSE), [](GMainLoop * loop) { g_clear_pointer(&loop, g_main_loop_unref); }); g_main_context_push_thread_default(context.get()); beforeLoop(); /* Free's the constructor to continue */ auto pair = std::pair, std::shared_ptr>(context, loop); context_promise.set_value(pair); if (!g_cancellable_is_cancelled(_cancel.get())) { g_main_loop_run(loop.get()); } afterLoop(); }); /* We need to have the context and the mainloop ready before other functions on this object can work properly. So we wait for them and set them on this thread. */ auto context_future = context_promise.get_future(); context_future.wait(); auto context_value = context_future.get(); _context = context_value.first; _loop = context_value.second; if (_context == nullptr || _loop == nullptr) { throw std::runtime_error("Unable to create GLib Thread"); } } ContextThread::~ContextThread () { quit(); } void ContextThread::quit () { g_cancellable_cancel(_cancel.get()); /* Force the cancellation on ongoing tasks */ if (_loop != nullptr) { g_main_loop_quit(_loop.get()); /* Quit the loop */ } /* Joining here because we want to ensure that the final afterLoop() function is run before returning */ if (std::this_thread::get_id() != _thread.get_id()) { if (_thread.joinable()) { _thread.join(); } } } bool ContextThread::isCancelled () { return g_cancellable_is_cancelled(_cancel.get()) == TRUE; } std::shared_ptr ContextThread::getCancellable () { return _cancel; } void ContextThread::simpleSource (std::function srcBuilder, std::function work) { if (isCancelled()) { throw std::runtime_error("Trying to execute work on a GLib thread that is shutting down."); } /* Copy the work so that we can reuse it */ /* Lifecycle is handled with the source pointer when we attach it to the context. */ auto heapWork = new std::function(work); auto source = std::shared_ptr(srcBuilder(), [](GSource * src) { g_clear_pointer(&src, g_source_unref); } ); g_source_set_callback(source.get(), [](gpointer data) -> gboolean { std::function* heapWork = reinterpret_cast *>(data); (*heapWork)(); return G_SOURCE_REMOVE; }, heapWork, [](gpointer data) { std::function* heapWork = reinterpret_cast *>(data); delete heapWork; }); g_source_attach(source.get(), _context.get()); } void ContextThread::executeOnThread (std::function work) { simpleSource(g_idle_source_new, work); } void ContextThread::timeout (const std::chrono::milliseconds& length, std::function work) { simpleSource([length]() { return g_timeout_source_new(length.count()); }, work); } void ContextThread::timeoutSeconds (const std::chrono::seconds& length, std::function work) { simpleSource([length]() { return g_timeout_source_new_seconds(length.count()); }, work); } } // ns GLib ./service/update-directory.c0000644000004100000410000001556613066525473016363 0ustar www-datawww-data/** * Copyright © 2014 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Author: Ted Gould * */ #include #include #include #include "url-db.h" typedef struct { const gchar * filename; sqlite3 * db; } urldata_t; static void each_url (JsonArray * array, guint index, JsonNode * value, gpointer user_data) { urldata_t * urldata = (urldata_t *)user_data; if (!JSON_NODE_HOLDS_OBJECT(value)) { g_warning("File %s: Array entry %d not an object", urldata->filename, index); return; } JsonObject * obj = json_node_get_object(value); const gchar * protocol = NULL; const gchar * suffix = NULL; if (json_object_has_member(obj, "protocol")) { protocol = json_object_get_string_member(obj, "protocol"); } if (json_object_has_member(obj, "domain-suffix")) { suffix = json_object_get_string_member(obj, "domain-suffix"); } if (protocol == NULL) { g_warning("File %s: Array entry %d doesn't contain a 'protocol'", urldata->filename, index); return; } if (g_strcmp0(protocol, "intent") == 0) { /* Special handling for intent, we have to have a domain suffix there because otherwise things will get crazy as we're handling it by package lookup in the service. */ if (suffix == NULL) { g_warning("File %s: Array entry %d is an 'intent' protocol but doesn't have a package name", urldata->filename, index); return; } } if (!url_db_insert_url(urldata->db, urldata->filename, protocol, suffix)) { const gchar * additional[7] = { "Filename", NULL, "Protocol", NULL, "Suffix", NULL, NULL }; additional[1] = urldata->filename; additional[3] = protocol; additional[5] = suffix; whoopsie_report_recoverable_problem("url-dispatcher-update-sqlite-insert-error", 0, TRUE, additional); } } static void insert_urls_from_file (const gchar * filename, sqlite3 * db) { GError * error = NULL; JsonParser * parser = json_parser_new(); json_parser_load_from_file(parser, filename, &error); if (error != NULL) { g_warning("Unable to parse JSON in '%s': %s", filename, error->message); g_object_unref(parser); g_error_free(error); return; } JsonNode * rootnode = json_parser_get_root(parser); if (!JSON_NODE_HOLDS_ARRAY(rootnode)) { g_warning("File '%s' does not have an array as its root node", filename); g_object_unref(parser); return; } JsonArray * rootarray = json_node_get_array(rootnode); urldata_t urldata = { .filename = filename, .db = db }; json_array_foreach_element(rootarray, each_url, &urldata); g_object_unref(parser); } static gboolean check_file_outofdate (const gchar * filename, sqlite3 * db) { g_debug("Processing file: %s", filename); GTimeVal dbtime = {0}; GTimeVal filetime = {0}; GFile * file = g_file_new_for_path(filename); g_return_val_if_fail(file != NULL, FALSE); GFileInfo * info = g_file_query_info(file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL); g_file_info_get_modification_time(info, &filetime); g_object_unref(info); g_object_unref(file); if (url_db_get_file_motification_time(db, filename, &dbtime)) { if (filetime.tv_sec <= dbtime.tv_sec) { g_debug("\tup-to-date: %s", filename); return FALSE; } } if (!url_db_set_file_motification_time(db, filename, &filetime)) { const gchar * additional[7] = { "Filename", NULL, NULL }; additional[1] = filename; whoopsie_report_recoverable_problem("url-dispatcher-update-sqlite-fileupdate-error", 0, TRUE, additional); return FALSE; } return TRUE; } /* Remove a file from the database */ static void remove_file (gpointer key, gpointer value, gpointer user_data) { const gchar * filename = (const gchar *)key; g_debug(" Removing file: %s", filename); if (!url_db_remove_file((sqlite3*)user_data, filename)) { g_warning("Unable to remove file: %s", filename); const gchar * additional[3] = { "Filename", NULL, NULL }; additional[1] = filename; whoopsie_report_recoverable_problem("url-dispatcher-update-remove-file-error", 0, TRUE, additional); } } /* In the beginning, there was main, and that was good */ int main (int argc, char * argv[]) { if (argc != 2) { g_printerr("Usage: %s \n", argv[0]); return 1; } sqlite3 * db = url_db_create_database(); g_return_val_if_fail(db != NULL, -1); /* Check out what we got and recover */ gchar * dirname = g_strdup(argv[1]); if (!g_file_test(dirname, G_FILE_TEST_IS_DIR) && !g_str_has_suffix(dirname, "/")) { gchar * upone = g_path_get_dirname(dirname); /* Upstart will give us filenames a bit, let's handle them */ if (g_file_test(upone, G_FILE_TEST_IS_DIR)) { g_free(dirname); dirname = upone; } else { /* If the dirname function doesn't help, stick with what we were given, the whole thing coulda been deleted */ g_free(upone); } } /* Get the current files in the directory in the DB so we know if any got dropped */ GHashTable * startingdb = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); GList * files = url_db_files_for_dir(db, dirname); GList * cur; for (cur = files; cur != NULL; cur = g_list_next(cur)) { g_hash_table_add(startingdb, cur->data); } g_list_free(files); /* Open the directory on the file system and start going through it */ if (g_file_test(dirname, G_FILE_TEST_IS_DIR)) { GDir * dir = g_dir_open(dirname, 0, NULL); g_return_val_if_fail(dir != NULL, -1); const gchar * name = NULL; while ((name = g_dir_read_name(dir)) != NULL) { if (g_str_has_suffix(name, ".url-dispatcher")) { gchar * fullname = g_build_filename(dirname, name, NULL); if (check_file_outofdate(fullname, db)) { insert_urls_from_file(fullname, db); } g_hash_table_remove(startingdb, fullname); g_free(fullname); } } g_dir_close(dir); } /* Remove deleted files */ g_hash_table_foreach(startingdb, remove_file, db); g_hash_table_destroy(startingdb); int close_status = sqlite3_close(db); if (close_status != SQLITE_OK) { const gchar * additional[3] = { "SQLiteStatus", NULL, NULL }; gchar * status = g_strdup_printf("%d", close_status); additional[1] = status; whoopsie_report_recoverable_problem("url-dispatcher-sqlite-close-error", 0, TRUE, additional); g_free(status); } g_debug("Directory '%s' is up-to-date", dirname); g_free(dirname); return 0; } ./liburl-dispatcher/0000755000004100000410000000000013066525443014670 5ustar www-datawww-data./liburl-dispatcher/CMakeLists.txt0000644000004100000410000000304413066525443017431 0ustar www-datawww-data ########################### # Version Information ########################### set(API_VERSION 1) set(ABI_VERSION 1) ########################### # Lib ########################### set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(DISPATCHER_HEADERS url-dispatcher.h ) set(DISPATCHER_SOURCES url-dispatcher.c ) add_library(dispatcher SHARED ${DISPATCHER_SOURCES}) set_target_properties(dispatcher PROPERTIES VERSION ${API_VERSION}.0.0 SOVERSION ${ABI_VERSION} OUTPUT_NAME "url-dispatcher" ) target_link_libraries(dispatcher service-generated ${GLIB2_LIBRARIES} ${GOBJECT2_LIBRARIES} -Wl,--no-undefined ) ########################### # Coverage ########################### set_property(GLOBAL APPEND PROPERTY COVERAGE_TARGETS dispatcher) ########################### # Pkg Config ########################### set(DISPATCHER_PC "url-dispatcher-${API_VERSION}.pc") set(apiversion "${API_VERSION}") set(libdir "${CMAKE_INSTALL_FULL_LIBDIR}") set(includedir "${CMAKE_INSTALL_FULL_INCLUDEDIR}") set(VERSION "${ABI_VERSION}") configure_file("url-dispatcher.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/${DISPATCHER_PC}" @ONLY) ########################### # Installation ########################### install( FILES ${DISPATCHER_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/liburl-dispatcher-${API_VERSION}" ) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/${DISPATCHER_PC}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" ) install( TARGETS dispatcher LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) ./liburl-dispatcher/url-dispatcher.h0000644000004100000410000000564413066525443020000 0ustar www-datawww-data/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include #ifndef URL_DISPATCH_H #define URL_DISPATCH_H 1 #pragma GCC visibility push(default) G_BEGIN_DECLS typedef void (*URLDispatchCallback) (const gchar * url, gboolean success, gpointer user_data); /** * url_dispatch_send: * @url: URL to send to the dispatcher * @cb: Function to call with the result of the URL processing * @user_data: data pointer for @cb * * Sends a URL to the dispatcher for processing. Most of the time, * things will work out and that URL will result in an application * being opened with the URL as requested. In some cases the URL * may not have a valid handler and an error will be returned. In * that case a bug will be filled on this package. */ void url_dispatch_send (const gchar * url, URLDispatchCallback cb, gpointer user_data); /** * url_dispatch_send_restricted: * @url: URL to send to the dispatcher * @cb: Function to call with the result of the URL processing * @user_data: data pointer for @cb * * Very much like url_dispatch_send() except that it also says which * package is allowed to have the URL. This should be checked with the * url_dispatch_url_appid() function ahead of time, but is used to avoid * races that can occur between the test and the processing. */ void url_dispatch_send_restricted (const gchar * url, const gchar * package, URLDispatchCallback cb, gpointer user_data); /** * url_dispatch_url_appid: * @urls: URLs to check the AppIDs for * * Takes a list of URLs and return which AppIDs will be launched to * handle those URLs. * * NOTE: This function is *not* available for confined applications and * only for trusted helpers. It could result in discovery of which * applications are installed on the system if exposed. * * Return value: (transfer full): Returns the AppIDs that match the * same order as @urls. Full transfer, free with g_strfreev(). */ gchar ** url_dispatch_url_appid (const gchar ** urls); G_END_DECLS #pragma GCC visibility pop #endif /* URL_DISPATCH_H */ ./liburl-dispatcher/url-dispatcher.pc.in0000644000004100000410000000040013066525443020541 0ustar www-datawww-datalibdir=@libdir@ includedir=@includedir@ Cflags: -I${includedir}/liburl-dispatcher-@apiversion@ Requires: glib-2.0 gio-2.0 Libs: -L${libdir} -lurl-dispatcher Name: liburl-dispatcher Description: A library to request a URL to be opened Version: @VERSION@ ./liburl-dispatcher/url-dispatcher.c0000644000004100000410000001060413066525443017763 0ustar www-datawww-data/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include "url-dispatcher.h" #include typedef struct _dispatch_data_t dispatch_data_t; struct _dispatch_data_t { URLDispatchCallback cb; gpointer user_data; gchar * url; gchar * package; }; static void url_dispatched (GObject * obj, GAsyncResult * res, gpointer user_data) { GError * error = NULL; dispatch_data_t * dispatch_data = (dispatch_data_t *)user_data; g_dbus_connection_call_finish( G_DBUS_CONNECTION(obj), res, &error); if (error != NULL) { g_warning("Unable to dispatch url '%s':%s", dispatch_data->url, error->message); g_error_free(error); if (dispatch_data->cb != NULL) { dispatch_data->cb(dispatch_data->url, FALSE, dispatch_data->user_data); } } else { if (dispatch_data->cb != NULL) { dispatch_data->cb(dispatch_data->url, TRUE, dispatch_data->user_data); } } g_free(dispatch_data->url); g_free(dispatch_data->package); g_free(dispatch_data); return; } void url_dispatch_send (const gchar * url, URLDispatchCallback cb, gpointer user_data) { url_dispatch_send_restricted(url, NULL, cb, user_data); } void url_dispatch_send_restricted (const gchar * url, const gchar * package, URLDispatchCallback cb, gpointer user_data) { GError * error = NULL; GDBusConnection * bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error); if (error != NULL) { g_warning("Unable to get session bus: %s", error->message); g_error_free(error); return; } dispatch_data_t * dispatch_data = NULL; if (cb != NULL) { dispatch_data = g_new0(dispatch_data_t, 1); dispatch_data->cb = cb; dispatch_data->user_data = user_data; dispatch_data->url = g_strdup(url); dispatch_data->package = g_strdup(package); } g_dbus_connection_call(bus, "com.canonical.URLDispatcher", "/com/canonical/URLDispatcher", "com.canonical.URLDispatcher", "DispatchURL", g_variant_new("(ss)", url, package ? package : ""), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout */ NULL, /* cancelable */ cb != NULL ? url_dispatched : NULL, dispatch_data); if (cb == NULL) { g_dbus_connection_flush_sync(bus, NULL, NULL); } g_object_unref(bus); return; } gchar ** url_dispatch_url_appid (const gchar ** urls) { GError * error = NULL; GDBusConnection * bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error); if (error != NULL) { g_warning("Unable to get session bus: %s", error->message); g_error_free(error); return NULL; } GVariant * vurls = g_variant_new_strv(urls, -1); GVariant * vparam = g_variant_new_tuple(&vurls, 1); GVariant * retval = NULL; retval = g_dbus_connection_call_sync(bus, "com.canonical.URLDispatcher", "/com/canonical/URLDispatcher", "com.canonical.URLDispatcher", "TestURL", vparam, G_VARIANT_TYPE("(as)"), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, /* timeout */ NULL, /* cancelable */ &error); if (error != NULL) { g_warning("Unable to test URL with URL Dispatcher: %s", error->message); g_error_free(error); g_object_unref(bus); return NULL; } GVariant * varstr = g_variant_get_child_value(retval, 0); gchar ** appids = g_variant_dup_strv(varstr, NULL); g_variant_unref(varstr); g_variant_unref(retval); g_object_unref(bus); return appids; } ./README0000644000004100000410000000064613066525443012141 0ustar www-datawww-dataIntroduction ------------ This is a small handler to take URLs and do what is appropriate with them. That could be anything from launching a web browser to just starting an application. This is done over DBus because application confinement doesn't allow for doing it from a confined application otherwise. It's important the that applications can't know about each other, so this is a fire and forget type operation. ./docs/0000755000004100000410000000000013066525443012203 5ustar www-datawww-data./docs/URL Dispatcher Architecture.svg0000644000004100000410000004511013066525443020041 0ustar www-datawww-data image/svg+xml URL DispatcherUser Service DBus URL DispatcherUpdate Tool SQLiteURLCache Upstart Click Click PackageInstall orRemoval Session InitRefresh Configurationfile change Readonly Readwrite Upstart AppLaunch ./COPYING0000644000004100000410000001674313066525443012321 0ustar www-datawww-data GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ./cmake/0000755000004100000410000000000013066525443012333 5ustar www-datawww-data./cmake/ConstantBuilderTemplates.cmake0000644000004100000410000000073213066525443020316 0ustar www-datawww-data file(READ "${input}" input_contents) string(REGEX REPLACE "\n" " " input_on_one_line "${input_contents}") set(new_contents "\#include \"${name}.h\"\nconst char * ${const_name} = \"${input_on_one_line}\";\n") if (EXISTS "${file_target}") file(READ "${file_target}" old_contents) if(NOT new_contents EQUAL old_contents) file(WRITE "${file_target}" "${new_contents}") endif() else() file(WRITE "${file_target}" "${new_contents}") endif() ./cmake/UseGlibGeneration.cmake0000644000004100000410000000544513066525443016713 0ustar www-datawww-datacmake_minimum_required(VERSION 2.6) if(POLICY CMP0011) cmake_policy(SET CMP0011 NEW) endif(POLICY CMP0011) find_program(GLIB_MKENUMS glib-mkenums) find_program(GLIB_GENMARSHAL glib-genmarshal) macro(add_glib_marshal outfiles name prefix otherinclude) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" COMMAND ${GLIB_GENMARSHAL} --header "--prefix=${prefix}" "${CMAKE_CURRENT_SOURCE_DIR}/${name}.list" > "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${name}.list" ) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.c" COMMAND echo "\\#include \\\"${otherinclude}\\\"" > "${CMAKE_CURRENT_BINARY_DIR}/${name}.c" COMMAND echo "\\#include \\\"glib-object.h\\\"" >> "${CMAKE_CURRENT_BINARY_DIR}/${name}.c" COMMAND echo "\\#include \\\"${name}.h\\\"" >> "${CMAKE_CURRENT_BINARY_DIR}/${name}.c" COMMAND ${GLIB_GENMARSHAL} --body "--prefix=${prefix}" "${CMAKE_CURRENT_SOURCE_DIR}/${name}.list" >> "${CMAKE_CURRENT_BINARY_DIR}/${name}.c" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${name}.list" "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" ) list(APPEND ${outfiles} "${CMAKE_CURRENT_BINARY_DIR}/${name}.c") endmacro(add_glib_marshal) macro(add_glib_enumtypes_t outfiles name htemplate ctemplate) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" COMMAND ${GLIB_MKENUMS} --template "${htemplate}" ${ARGN} > "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${ARGN} "${htemplate}" ) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.c" COMMAND ${GLIB_MKENUMS} --template "${ctemplate}" ${ARGN} > "${CMAKE_CURRENT_BINARY_DIR}/${name}.c" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${ARGN} ${ctemplate} "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" ) list(APPEND ${outfiles} "${CMAKE_CURRENT_BINARY_DIR}/${name}.c") endmacro(add_glib_enumtypes_t) macro(add_glib_enumtypes outfiles name namespace includeguard) set(htemplate "${CMAKE_CURRENT_BINARY_DIR}/${name}.h.template") set(ctemplate "${CMAKE_CURRENT_BINARY_DIR}/${name}.c.template") # Write the .h template add_custom_command( OUTPUT ${htemplate} ${ctemplate} COMMAND ${CMAKE_COMMAND} "-Dctemplate=${ctemplate}" "-Dhtemplate=${htemplate}" "-Dname=${name}" "-Dnamespace=${namespace}" "-Dincludeguard=${includeguard}" "\"-Dheaders=${ARGN}\"" -P "${CMAKE_SOURCE_DIR}/cmake/GlibGenerationTemplates.cmake" DEPENDS "${CMAKE_SOURCE_DIR}/cmake/GlibGenerationTemplates.cmake" ${headers} ) add_glib_enumtypes_t(${outfiles} ${name} ${htemplate} ${ctemplate} ${ARGN}) endmacro(add_glib_enumtypes) ./cmake/UseConstantBuilder.cmake0000644000004100000410000000123213066525443017110 0ustar www-datawww-datacmake_minimum_required(VERSION 2.6) if(POLICY CMP0011) cmake_policy(SET CMP0011 NEW) endif(POLICY CMP0011) macro(add_constant_template outfiles name const_name input) set(file_target "${CMAKE_CURRENT_BINARY_DIR}/${name}.c") add_custom_command( OUTPUT ${file_target} COMMAND ${CMAKE_COMMAND} "-Dname=${name}" "-Dfile_target=${file_target}" "-Dconst_name=${const_name}" "-Dinput=${input}" -P "${CMAKE_SOURCE_DIR}/cmake/ConstantBuilderTemplates.cmake" DEPENDS "${CMAKE_SOURCE_DIR}/cmake/ConstantBuilderTemplates.cmake" "${input}" ) list(APPEND ${outfiles} "${file_target}") endmacro(add_constant_template) ./gui/0000755000004100000410000000000013066525443012037 5ustar www-datawww-data./gui/url-dispatcher-gui.svg0000644000004100000410000004010213066525443016265 0ustar www-datawww-data image/svg+xml ./gui/CMakeLists.txt0000644000004100000410000000052113066525443014575 0ustar www-datawww-data install(FILES url-dispatcher-gui.qml url-dispatcher-gui.svg DESTINATION "${CMAKE_INSTALL_DATADIR}/url-dispatcher/gui/") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/url-dispatcher-gui.desktop DESTINATION "${CMAKE_INSTALL_DATADIR}/applications/") configure_file( "url-dispatcher-gui.desktop.in" "url-dispatcher-gui.desktop" @ONLY) ./gui/url-dispatcher-gui.desktop.in0000644000004100000410000000046013066525443017547 0ustar www-datawww-data[Desktop Entry] Name=URL Dispatcher GUI Icon=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_DATADIR@/url-dispatcher/gui/url-dispatcher-gui.svg Type=Application Exec=qmlscene @CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_DATADIR@/url-dispatcher/gui/url-dispatcher-gui.qml X-Ubuntu-Touch=true X-Ubuntu-Single-Instance=true ./gui/url-dispatcher-gui.qml0000644000004100000410000000176213066525443016270 0ustar www-datawww-dataimport QtQuick 2.4 import Ubuntu.Components 1.3 import Ubuntu.Components.ListItems 1.3 MainView { applicationName: "url-dispatcher-gui" Page { header: PageHeader { title: "URL Dispatcher GUI" flickable: flickme } Flickable { id: flickme anchors.fill: parent Column { anchors.fill: parent ListItem { contentItem.anchors { leftMargin: units.gu(2) rightMargin: units.gu(2) topMargin: units.gu(1) bottomMargin: units.gu(1) } TextField { id: textbox anchors.fill: parent placeholderText: "URL (e.g. 'http://ubuntu.com')" } } ListItem { contentItem.anchors { leftMargin: units.gu(2) rightMargin: units.gu(2) topMargin: units.gu(1) bottomMargin: units.gu(1) } Button { anchors.fill: parent text: "Send URL" onClicked: { console.log("Sending URL: " + textbox.text) Qt.openUrlExternally(textbox.text) } } } } } } } ./MERGE-REVIEW0000644000004100000410000000102013066525443013025 0ustar www-datawww-data This documents the expections that the project has on what both submitters and reviewers should ensure that they've done for a merge into the project. == Submitter Responsibilities == * Ensure the project compiles and the test suite executes without error * Ensure that non-obvious code has comments explaining it == Reviewer Responsibilities == * Did the Jenkins build compile? Pass? Run unit tests successfully? * Are there appropriate tests to cover any new functionality? * Run all the appropriate manual tests ./tests/0000755000004100000410000000000013066525473012420 5ustar www-datawww-data./tests/CMakeLists.txt0000644000004100000410000000604513066525443015162 0ustar www-datawww-datafind_package(GMock) set(APP_ID_TEST_URI 1) configure_file(test-config.h.in test-config.h) include_directories(${CMAKE_CURRENT_BINARY_DIR}) ########################### # Mock Lib ########################### add_library(mock-lib STATIC apparmor-mock.h apparmor-mock.c recoverable-problem-mock.c ubuntu-app-launch-mock.h ubuntu-app-launch-mock.c) target_link_libraries(mock-lib ${GLIB2_LIBRARIES} ) ########################### # Mir Mock Lib ########################### add_library(mir-mock-lib SHARED mir-mock.h mir-mock.cpp) target_link_libraries(mir-mock-lib -pthread ${GLIB2_LIBRARIES} ) set_target_properties(mir-mock-lib PROPERTIES OUTPUT_NAME "mir-mock" ) ########################### # Dispatcher test ########################### include_directories("${CMAKE_SOURCE_DIR}/service") add_executable (dispatcher-test dispatcher-test.cc) target_link_libraries (dispatcher-test dispatcher-lib mock-lib gtest ${UBUNTU_APP_LAUNCH_LIBRARIES} ${DBUSTEST_LIBRARIES} ${GMOCK_LIBRARIES}) add_test (dispatcher-test dispatcher-test) ########################### # lib test ########################### add_executable (lib-test-no-main lib-test-no-main.c) target_link_libraries (lib-test-no-main dispatcher) add_executable (lib-test lib-test.cc) target_link_libraries (lib-test dispatcher gtest ${DBUSTEST_LIBRARIES} ${GMOCK_LIBRARIES}) add_test (lib-test lib-test) ########################### # service test ########################### add_executable (service-test service-test.cc ${CMAKE_SOURCE_DIR}/service/glib-thread.cpp) target_link_libraries (service-test url-db-lib dispatcher gtest ${DBUSTEST_LIBRARIES} ${UBUNTU_APP_LAUNCH_LIBRARIES} ${GMOCK_LIBRARIES}) add_test (service-test service-test) ########################### # create db test ########################### add_test (NAME create-db-test COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/test-sql.sh" "${CMAKE_SOURCE_DIR}/service/create-db.sql") ########################### # update directory test ########################### add_executable (directory-update-test directory-update-test.cc) target_link_libraries (directory-update-test url-db-lib gtest ${GMOCK_LIBRARIES}) add_test (directory-update-test directory-update-test) ########################### # url db test ########################### add_executable (url-db-test url-db-test.cc) target_link_libraries (url-db-test url-db-lib gtest ${GMOCK_LIBRARIES}) add_test (url-db-test url-db-test) add_subdirectory(url_dispatcher_testability) ########################### # overlay tracker test ########################### add_executable (overlay-tracker-test overlay-tracker-test.cpp) target_link_libraries (overlay-tracker-test dispatcher-lib mir-mock-lib mock-lib gtest ${GMOCK_LIBRARIES} ${GIO2_LIBRARIES} ${DBUSTEST_LIBRARIES}) add_test (overlay-tracker-test overlay-tracker-test) ########################### # Coverage ########################### set_property( GLOBAL APPEND PROPERTY COVERAGE_TESTS directory-update-test dispatcher-test exec-tool-test lib-test overlay-tracker-test service-test url-db-test ) ./tests/mir-mock.h0000644000004100000410000000241213066525443014303 0ustar www-datawww-data/** * Copyright © 2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * */ #ifndef MIR_MOCK_H #define MIR_MOCK_H 1 #include #include #include #include void mir_mock_connect_return_valid (bool valid); std::pair mir_mock_connect_last_connect (); void mir_mock_set_trusted_fd (int fd); extern MirPromptSession * mir_mock_valid_trust_session; extern MirPromptSession * mir_mock_last_released_session; extern pid_t mir_mock_last_trust_pid; extern void (*mir_mock_last_trust_func)(MirPromptSession *, MirPromptSessionState, void*data); extern void * mir_mock_last_trust_data; #endif // MIR_MOCK_H ./tests/ual-link-farm/0000755000004100000410000000000013066525443015054 5ustar www-datawww-data./tests/ual-link-farm/com.test.multiple_app-first_1.2.3.desktop0000644000004100000410000000001713066525443024541 0ustar www-datawww-dataNeeds to exist ./tests/ual-link-farm/com.test.good_app1_1.2.3.desktop0000644000004100000410000000001713066525443022572 0ustar www-datawww-dataNeeds to exist ./tests/overlay-dir/0000755000004100000410000000000013066525443014652 5ustar www-datawww-data./tests/overlay-dir/com.test.good_application_1.2.3.desktop0000644000004100000410000000003413066525443024031 0ustar www-datawww-data[Desktop Entry] Exec=foobar ./tests/test-urls-intent/0000755000004100000410000000000013066525443015656 5ustar www-datawww-data./tests/test-urls-intent/intent-single.url-dispatcher0000644000004100000410000000010513066525443023302 0ustar www-datawww-data[ { "protocol": "intent", "domain-suffix": "intent.single" } ] ./tests/test-urls-intent/intent-mixed.url-dispatcher0000644000004100000410000000025113066525443023131 0ustar www-datawww-data[ { "protocol": "intent", "domain-suffix": "intent.mixed" }, { "protocol": "intent" }, { "protocol": "intent", "domain-suffix": "intent.mixed.again" } ] ./tests/test-urls-intent/intent-no-good.url-dispatcher0000644000004100000410000000007713066525443023373 0ustar www-datawww-data[ { "protocol": "intent" }, { "protocol": "intent" } ] ./tests/test-sql.sh0000755000004100000410000000036013066525443014527 0ustar www-datawww-data#!/bin/bash TEMPFILE=`mktemp` SQLSTATUS=`sqlite3 -bail -echo -init $1 $TEMPFILE .quit 3>&1 1>&2- 2>&3- | grep ^Error` rm $TEMPFILE if [ "x$SQLSTATUS" == "x" ] ; then exit 0 else echo "SQL Parsing Error: $1" echo $SQLSTATUS exit 1 fi ./tests/directory-update-test.cc0000644000004100000410000002644113066525443017174 0ustar www-datawww-data/** * Copyright © 2014 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include "test-config.h" #include #include "url-db.h" #include class DirectoryUpdateTest : public ::testing::Test { protected: gchar * cachedir = nullptr; virtual void SetUp() { cachedir = g_build_filename(CMAKE_BINARY_DIR, "url-db-test-cache", nullptr); g_setenv("URL_DISPATCHER_CACHE_DIR", cachedir, TRUE); } virtual void TearDown() { gchar * cmdline = g_strdup_printf("rm -rf \"%s\"", cachedir); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); g_free(cachedir); } int get_file_count (sqlite3 * db) { sqlite3_stmt * stmt; if (sqlite3_prepare_v2(db, "select count(*) from configfiles", -1, /* length */ &stmt, nullptr) != SQLITE_OK) { g_warning("Unable to parse SQL to get file times"); return 0; } int retval = 0; int exec_status = SQLITE_ROW; while ((exec_status = sqlite3_step(stmt)) == SQLITE_ROW) { retval = sqlite3_column_int(stmt, 0); } sqlite3_finalize(stmt); if (exec_status != SQLITE_DONE) { g_warning("Unable to execute insert"); return 0; } return retval; } int get_url_count (sqlite3 * db) { sqlite3_stmt * stmt; if (sqlite3_prepare_v2(db, "select count(*) from urls", -1, /* length */ &stmt, nullptr) != SQLITE_OK) { g_warning("Unable to parse SQL to get file times"); return 0; } int retval = 0; int exec_status = SQLITE_ROW; while ((exec_status = sqlite3_step(stmt)) == SQLITE_ROW) { retval = sqlite3_column_int(stmt, 0); } sqlite3_finalize(stmt); if (exec_status != SQLITE_DONE) { g_warning("Unable to execute insert"); return 0; } return retval; } bool has_file (sqlite3 * db, const char * filename) { sqlite3_stmt * stmt; if (sqlite3_prepare_v2(db, "select count(*) from configfiles where name = ?1", -1, /* length */ &stmt, nullptr) != SQLITE_OK) { g_warning("Unable to parse SQL to get file times"); return false; } sqlite3_bind_text(stmt, 1, filename, -1, SQLITE_TRANSIENT); int retval = 0; int exec_status = SQLITE_ROW; while ((exec_status = sqlite3_step(stmt)) == SQLITE_ROW) { retval = sqlite3_column_int(stmt, 0); } sqlite3_finalize(stmt); if (exec_status != SQLITE_DONE) { g_warning("Unable to execute insert"); return false; } if (retval > 1) { g_warning("Database contains more than one instance of '%s'", filename); return false; } return retval == 1; } bool has_url (sqlite3 * db, const char * protocol, const char * domainsuffix) { sqlite3_stmt * stmt; if (sqlite3_prepare_v2(db, "select count(*) from urls where protocol = ?1 and domainsuffix = ?2", -1, /* length */ &stmt, nullptr) != SQLITE_OK) { g_warning("Unable to parse SQL to get file times"); return false; } sqlite3_bind_text(stmt, 1, protocol, -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, domainsuffix, -1, SQLITE_TRANSIENT); int retval = 0; int exec_status = SQLITE_ROW; while ((exec_status = sqlite3_step(stmt)) == SQLITE_ROW) { retval = sqlite3_column_int(stmt, 0); } sqlite3_finalize(stmt); if (exec_status != SQLITE_DONE) { g_warning("Unable to execute insert"); return false; } if (retval > 1) { g_warning("Database contains more than one instance of prtocol '%s'", protocol); return false; } return retval == 1; } }; TEST_F(DirectoryUpdateTest, DirDoesntExist) { sqlite3 * db = url_db_create_database(); gchar * cmdline = g_strdup_printf("%s \"%s\"", UPDATE_DIRECTORY_TOOL, CMAKE_SOURCE_DIR "/this-does-not-exist"); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); EXPECT_EQ(0, get_file_count(db)); EXPECT_EQ(0, get_url_count(db)); sqlite3_close(db); } TEST_F(DirectoryUpdateTest, SingleGoodItem) { sqlite3 * db = url_db_create_database(); gchar * cmdline = g_strdup_printf("%s \"%s\"", UPDATE_DIRECTORY_TOOL, UPDATE_DIRECTORY_URLS); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); EXPECT_EQ(1, get_file_count(db)); EXPECT_EQ(1, get_url_count(db)); EXPECT_TRUE(has_file(db, UPDATE_DIRECTORY_URLS "/single-good.url-dispatcher")); EXPECT_TRUE(has_url(db, "http", "ubuntu.com")); sqlite3_close(db); } TEST_F(DirectoryUpdateTest, RerunAgain) { gchar * cmdline = nullptr; sqlite3 * db = url_db_create_database(); cmdline = g_strdup_printf("%s \"%s\"", UPDATE_DIRECTORY_TOOL, UPDATE_DIRECTORY_URLS); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); EXPECT_EQ(1, get_file_count(db)); EXPECT_EQ(1, get_url_count(db)); cmdline = g_strdup_printf("%s \"%s\"", UPDATE_DIRECTORY_TOOL, UPDATE_DIRECTORY_URLS); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); EXPECT_EQ(1, get_file_count(db)); EXPECT_EQ(1, get_url_count(db)); cmdline = g_strdup_printf("%s \"%s\"", UPDATE_DIRECTORY_TOOL, UPDATE_DIRECTORY_URLS); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); EXPECT_EQ(1, get_file_count(db)); EXPECT_EQ(1, get_url_count(db)); sqlite3_close(db); } TEST_F(DirectoryUpdateTest, VariedItems) { sqlite3 * db = url_db_create_database(); gchar * cmdline = g_strdup_printf("%s \"%s\"", UPDATE_DIRECTORY_TOOL, UPDATE_DIRECTORY_VARIED); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); EXPECT_EQ(6, get_file_count(db)); EXPECT_EQ(13, get_url_count(db)); /* object-base.url-dispatcher */ EXPECT_TRUE(has_file(db, UPDATE_DIRECTORY_VARIED "/object-base.url-dispatcher")); EXPECT_FALSE(has_url(db, "object", "object-base.com")); /* bad-filename-suffix.url-launcher */ EXPECT_FALSE(has_file(db, UPDATE_DIRECTORY_VARIED "/bad-filename-suffix.url-launcher")); EXPECT_FALSE(has_url(db, "badsuffix", "bad.suffix.com")); /* not-json.url-dispatcher */ EXPECT_TRUE(has_file(db, UPDATE_DIRECTORY_VARIED "/not-json.url-dispatcher")); EXPECT_FALSE(has_url(db, "notjson", "not.json.com")); /* lots-o-entries.url-dispatcher */ EXPECT_TRUE(has_file(db, UPDATE_DIRECTORY_VARIED "/lots-o-entries.url-dispatcher")); EXPECT_TRUE(has_url(db, "lots0", "lots.com")); EXPECT_TRUE(has_url(db, "lots1", "lots.com")); EXPECT_TRUE(has_url(db, "lots2", "lots.com")); EXPECT_TRUE(has_url(db, "lots3", "lots.com")); EXPECT_TRUE(has_url(db, "lots4", "lots.com")); EXPECT_TRUE(has_url(db, "lots5", "lots.com")); EXPECT_TRUE(has_url(db, "lots6", "lots.com")); EXPECT_TRUE(has_url(db, "lots7", "lots.com")); EXPECT_TRUE(has_url(db, "lots8", "lots.com")); EXPECT_TRUE(has_url(db, "lots9", "lots.com")); /* duplicate.url-dispatcher */ EXPECT_TRUE(has_file(db, UPDATE_DIRECTORY_VARIED "/duplicate.url-dispatcher")); EXPECT_TRUE(has_url(db, "duplicate", "dup.licate.com")); /* dup-file-1.url-dispatcher */ /* dup-file-2.url-dispatcher */ EXPECT_TRUE(has_file(db, UPDATE_DIRECTORY_VARIED "/dup-file-1.url-dispatcher")); EXPECT_TRUE(has_file(db, UPDATE_DIRECTORY_VARIED "/dup-file-2.url-dispatcher")); EXPECT_FALSE(has_url(db, "dupfile", "this.is.in.two.file.org")); sqlite3_close(db); } TEST_F(DirectoryUpdateTest, RemoveFile) { gchar * cmdline; sqlite3 * db = url_db_create_database(); /* A temporary directory to put files in */ gchar * datadir = g_build_filename(CMAKE_BINARY_DIR, "remove-file-data", nullptr); g_mkdir_with_parents(datadir, 1 << 6 | 1 << 7 | 1 << 8); // 700 ASSERT_TRUE(g_file_test(datadir, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))); /* Copy the files */ cmdline = g_strdup_printf("cp \"%s/%s\" \"%s\"", UPDATE_DIRECTORY_URLS, "single-good.url-dispatcher", datadir); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); /* Run the tool */ cmdline = g_strdup_printf("%s \"%s\"", UPDATE_DIRECTORY_TOOL, datadir); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); EXPECT_EQ(1, get_file_count(db)); EXPECT_EQ(1, get_url_count(db)); /* Kill the files */ cmdline = g_strdup_printf("rm \"%s/%s\"", datadir, "single-good.url-dispatcher"); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); /* Run the tool */ cmdline = g_strdup_printf("%s \"%s\"", UPDATE_DIRECTORY_TOOL, datadir); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); EXPECT_EQ(0, get_file_count(db)); EXPECT_EQ(0, get_url_count(db)); /* Cleanup */ cmdline = g_strdup_printf("rm -rf \"%s\"", datadir); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); sqlite3_close(db); } TEST_F(DirectoryUpdateTest, RemoveDirectory) { gchar * cmdline; sqlite3 * db = url_db_create_database(); /* A temporary directory to put files in */ gchar * datadir = g_build_filename(CMAKE_BINARY_DIR, "remove-directory-data", nullptr); g_mkdir_with_parents(datadir, 1 << 6 | 1 << 7 | 1 << 8); // 700 ASSERT_TRUE(g_file_test(datadir, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))); /* Copy the files */ cmdline = g_strdup_printf("cp \"%s/%s\" \"%s\"", UPDATE_DIRECTORY_URLS, "single-good.url-dispatcher", datadir); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); /* Run the tool */ cmdline = g_strdup_printf("%s \"%s/\"", UPDATE_DIRECTORY_TOOL, datadir); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); EXPECT_EQ(1, get_file_count(db)); EXPECT_EQ(1, get_url_count(db)); /* Kill the dir */ cmdline = g_strdup_printf("rm -rf \"%s\"", datadir); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); /* Run the tool */ cmdline = g_strdup_printf("%s \"%s/\"", UPDATE_DIRECTORY_TOOL, datadir); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); EXPECT_EQ(0, get_file_count(db)); EXPECT_EQ(0, get_url_count(db)); /* Cleanup */ sqlite3_close(db); } TEST_F(DirectoryUpdateTest, IntentTest) { sqlite3 * db = url_db_create_database(); gchar * cmdline = g_strdup_printf("%s \"%s\"", UPDATE_DIRECTORY_TOOL, UPDATE_DIRECTORY_INTENT); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); EXPECT_EQ(3, get_file_count(db)); EXPECT_EQ(3, get_url_count(db)); EXPECT_TRUE(has_file(db, UPDATE_DIRECTORY_INTENT "/intent-single.url-dispatcher")); EXPECT_TRUE(has_file(db, UPDATE_DIRECTORY_INTENT "/intent-mixed.url-dispatcher")); EXPECT_TRUE(has_file(db, UPDATE_DIRECTORY_INTENT "/intent-no-good.url-dispatcher")); EXPECT_TRUE(has_url(db, "intent", "intent.single")); EXPECT_TRUE(has_url(db, "intent", "intent.mixed")); EXPECT_TRUE(has_url(db, "intent", "intent.mixed.again")); sqlite3_close(db); } ./tests/mir-mock.cpp0000644000004100000410000000647313066525460014650 0ustar www-datawww-data/** * Copyright © 2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 "mir-mock.h" #include #include #include MirPromptSession * mir_mock_valid_trust_session = (MirPromptSession *)"In the circle of trust"; static bool valid_trust_connection = true; static int trusted_fd = 1234; MirPromptSession * mir_mock_last_released_session = NULL; pid_t mir_mock_last_trust_pid = 0; void (*mir_mock_last_trust_func)(MirPromptSession *, MirPromptSessionState, void*data) = NULL; void * mir_mock_last_trust_data = NULL; MirPromptSession * mir_connection_create_prompt_session_sync(MirConnection *, pid_t pid, void (*func)(MirPromptSession *, MirPromptSessionState, void*data), void * context) { mir_mock_last_trust_pid = pid; mir_mock_last_trust_func = func; mir_mock_last_trust_data = context; if (valid_trust_connection) { return mir_mock_valid_trust_session; } else { return nullptr; } } void mir_prompt_session_release_sync (MirPromptSession * session) { mir_mock_last_released_session = session; if (session != mir_mock_valid_trust_session) { std::cerr << "Releasing a Mir Trusted Prompt that isn't valid" << std::endl; exit(1); } } MirWaitHandle * mir_prompt_session_new_fds_for_prompt_providers (MirPromptSession * session, unsigned int numfds, MirClientFdCallback cb, void * data) { if (session != mir_mock_valid_trust_session) { std::cerr << "Releasing a Mir Trusted Prompt that isn't valid" << std::endl; exit(1); } std::thread * thread = new std::thread([session, numfds, cb, data]() { std::vector fdlist(numfds); for (unsigned int i = 0; i < numfds; i++) fdlist[i] = trusted_fd; cb(session, numfds, fdlist.data(), data); }); return reinterpret_cast(thread); } void mir_wait_for (MirWaitHandle * wait) { auto thread = reinterpret_cast(wait); if (thread->joinable()) thread->join(); delete thread; } static const char * valid_connection_str = "Valid Mir Connection"; static std::pair last_connection; static bool valid_connection = true; void mir_mock_connect_return_valid (bool valid) { valid_connection = valid; } std::pair mir_mock_connect_last_connect () { return last_connection; } MirConnection * mir_connect_sync (char const * server, char const * appname) { last_connection = std::pair(server, appname); if (valid_connection) { return (MirConnection *)(valid_connection_str); } else { return nullptr; } } void mir_connection_release (MirConnection * con) { if (reinterpret_cast(con) != valid_connection_str) { std::cerr << "Releasing a Mir Connection that isn't valid" << std::endl; exit(1); } } void mir_mock_set_trusted_fd (int fd) { trusted_fd = fd; } ./tests/xdg-cache/0000755000004100000410000000000013066525443014240 5ustar www-datawww-data./tests/xdg-cache/libertine-container/0000755000004100000410000000000013066525443020175 5ustar www-datawww-data./tests/xdg-cache/libertine-container/container-id/0000755000004100000410000000000013066525443022551 5ustar www-datawww-data./tests/xdg-cache/libertine-container/container-id/rootfs/0000755000004100000410000000000013066525443024065 5ustar www-datawww-data./tests/xdg-cache/libertine-container/container-id/rootfs/usr/0000755000004100000410000000000013066525443024676 5ustar www-datawww-data./tests/xdg-cache/libertine-container/container-id/rootfs/usr/share/0000755000004100000410000000000013066525443026000 5ustar www-datawww-data./tests/xdg-cache/libertine-container/container-id/rootfs/usr/share/applications/0000755000004100000410000000000013066525443030466 5ustar www-datawww-data././@LongLink0000644000000000000000000000015400000000000011603 Lustar rootroot./tests/xdg-cache/libertine-container/container-id/rootfs/usr/share/applications/org.canonical.app1.desktop./tests/xdg-cache/libertine-container/container-id/rootfs/usr/share/applications/org.canonical.app1.0000644000004100000410000000006113066525443034041 0ustar www-datawww-data[Desktop File] Name=App1 Exec=app1 Icon=app1.png ./tests/ubuntu-app-launch-mock.c0000644000004100000410000001103513066525443017060 0ustar www-datawww-data/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include #include "ubuntu-app-launch-mock.h" #include static gchar * last_appid = NULL; static gchar * last_version = NULL; gchar * ubuntu_app_launch_triplet_to_app_id (const gchar * pkg, const gchar * app, const gchar * version) { g_return_val_if_fail(pkg != NULL, NULL); g_return_val_if_fail(app != NULL, NULL); if (version != NULL && strlen(version) != 0) { ubuntu_app_launch_mock_clear_last_version(); last_version = g_strdup(version); } else if (last_version == NULL) { last_version = g_strdup("current-user-version"); } return g_strdup_printf("%s_%s_%s", pkg, app, last_version); } void ubuntu_app_launch_mock_clear_last_version () { g_free(last_version); last_version = NULL; } gboolean ubuntu_app_launch_start_application (const gchar * appid, const gchar * const * uris) { ubuntu_app_launch_mock_clear_last_app_id(); last_appid = g_strdup(appid); return TRUE; } void ubuntu_app_launch_mock_clear_last_app_id () { g_free(last_appid); last_appid = NULL; return; } gchar * ubuntu_app_launch_mock_get_last_app_id () { return last_appid; } UbuntuAppLaunchHelperObserver ubuntu_app_launch_mock_observer_helper_stop_func = NULL; gchar * ubuntu_app_launch_mock_observer_helper_stop_type = NULL; void * ubuntu_app_launch_mock_observer_helper_stop_user_data = NULL; gboolean ubuntu_app_launch_observer_add_helper_stop (UbuntuAppLaunchHelperObserver func, const gchar * type, gpointer user_data) { ubuntu_app_launch_mock_observer_helper_stop_func = func; ubuntu_app_launch_mock_observer_helper_stop_type = g_strdup(type); ubuntu_app_launch_mock_observer_helper_stop_user_data = user_data; return TRUE; } gboolean ubuntu_app_launch_observer_delete_helper_stop (UbuntuAppLaunchHelperObserver func, const gchar * type, gpointer user_data) { gboolean same = ubuntu_app_launch_mock_observer_helper_stop_func == func && g_strcmp0(ubuntu_app_launch_mock_observer_helper_stop_type, type) == 0 && ubuntu_app_launch_mock_observer_helper_stop_user_data == user_data; ubuntu_app_launch_mock_observer_helper_stop_func = NULL; g_clear_pointer(&ubuntu_app_launch_mock_observer_helper_stop_type, g_free); ubuntu_app_launch_mock_observer_helper_stop_user_data = NULL; return same; } gchar * ubuntu_app_launch_mock_last_start_session_helper = NULL; MirPromptSession * ubuntu_app_launch_mock_last_start_session_session = NULL; gchar * ubuntu_app_launch_mock_last_start_session_appid = NULL; gchar ** ubuntu_app_launch_mock_last_start_session_uris = NULL; gchar * ubuntu_app_launch_start_session_helper (const gchar * type, MirPromptSession * session, const gchar * appid, const gchar * const * uris) { g_clear_pointer(&ubuntu_app_launch_mock_last_start_session_helper, g_free); g_clear_pointer(&ubuntu_app_launch_mock_last_start_session_appid, g_free); g_clear_pointer(&ubuntu_app_launch_mock_last_start_session_uris, g_strfreev); ubuntu_app_launch_mock_last_start_session_helper = g_strdup(type); ubuntu_app_launch_mock_last_start_session_session = session; ubuntu_app_launch_mock_last_start_session_appid = g_strdup(appid); ubuntu_app_launch_mock_last_start_session_uris = g_strdupv((gchar **)uris); return g_strdup("instance"); } gchar * ubuntu_app_launch_mock_last_stop_helper = NULL; gchar * ubuntu_app_launch_mock_last_stop_appid = NULL; gchar * ubuntu_app_launch_mock_last_stop_instance = NULL; gboolean ubuntu_app_launch_stop_multiple_helper (const gchar * helper_type, const gchar * appid, const gchar * instance) { g_clear_pointer(&ubuntu_app_launch_mock_last_stop_helper, g_free); g_clear_pointer(&ubuntu_app_launch_mock_last_stop_appid, g_free); g_clear_pointer(&ubuntu_app_launch_mock_last_stop_instance, g_free); ubuntu_app_launch_mock_last_stop_helper = g_strdup(helper_type); ubuntu_app_launch_mock_last_stop_appid = g_strdup(appid); ubuntu_app_launch_mock_last_stop_instance = g_strdup(instance); return TRUE; } ./tests/apparmor-mock.c0000644000004100000410000000201313066525443015325 0ustar www-datawww-data/** * Copyright © 2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include #include const char * aa_mock_gettask_profile = NULL; int aa_gettaskcon (pid_t pid, char ** profile, char ** mode) { g_debug("Gettask Con '%s'", aa_mock_gettask_profile); if (aa_mock_gettask_profile == NULL) { return 1; } if (profile != NULL) { *profile = g_strdup(aa_mock_gettask_profile); } return 0; } ./tests/overlay-tracker-mock.h0000644000004100000410000000236513066525443016635 0ustar www-datawww-data/** * Copyright © 2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 #include "overlay-tracker-iface.h" #include class OverlayTrackerMock : public OverlayTrackerIface { public: std::vector> addedOverlays; std::vector> addedBadUrl; bool addOverlay (const char * appid, unsigned long pid, const char * url) { addedOverlays.push_back(std::make_tuple(std::string(appid), pid, std::string(url))); return true; } bool badUrl (unsigned long pid, const char * url) { addedBadUrl.push_back(std::make_pair(pid, std::string(url))); return true; } }; ./tests/test-urls-varied/0000755000004100000410000000000013066525443015627 5ustar www-datawww-data./tests/test-urls-varied/not-json.url-dispatcher0000644000004100000410000000013613066525443022246 0ustar www-datawww-data notjson not.json.com ./tests/test-urls-varied/dup-file-1.url-dispatcher0000644000004100000410000000012013066525443022333 0ustar www-datawww-data[ { "protocol": "dupfile", "domain-suffix": "this.is.in.two.file.org" } ] ./tests/test-urls-varied/lots-o-entries.url-dispatcher0000644000004100000410000000113313066525443023361 0ustar www-datawww-data[ { "protocol": "lots0", "domain-suffix": "lots.com" }, { "protocol": "lots1", "domain-suffix": "lots.com" }, { "protocol": "lots2", "domain-suffix": "lots.com" }, { "protocol": "lots3", "domain-suffix": "lots.com" }, { "protocol": "lots4", "domain-suffix": "lots.com" }, { "protocol": "lots5", "domain-suffix": "lots.com" }, { "protocol": "lots6", "domain-suffix": "lots.com" }, { "protocol": "lots7", "domain-suffix": "lots.com" }, { "protocol": "lots8", "domain-suffix": "lots.com" }, { "protocol": "lots9", "domain-suffix": "lots.com" } ] ./tests/test-urls-varied/dup-file-2.url-dispatcher0000644000004100000410000000012013066525443022334 0ustar www-datawww-data[ { "protocol": "dupfile", "domain-suffix": "this.is.in.two.file.org" } ] ./tests/test-urls-varied/bad-filename-suffix.url-launcher0000644000004100000410000000011113066525443023751 0ustar www-datawww-data[ { "protocol": "badsuffix", "domain-suffix": "bad.suffix.com" } ] ./tests/test-urls-varied/duplicate.url-dispatcher0000644000004100000410000000021713066525443022451 0ustar www-datawww-data[ { "protocol": "duplicate", "domain-suffix": "dup.licate.com" }, { "protocol": "duplicate", "domain-suffix": "dup.licate.com" } ] ./tests/test-urls-varied/object-base.url-dispatcher0000644000004100000410000000012013066525443022646 0ustar www-datawww-data{ "first": { "protocol": "object", "domain-suffix": "object-base.com" } } ./tests/url-db-test.cc0000644000004100000410000001645013066525443015074 0ustar www-datawww-data/** * Copyright © 2014 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include "test-config.h" #include #include "url-db.h" class UrlDBTest : public ::testing::Test { protected: gchar * cachedir = nullptr; virtual void SetUp() { cachedir = g_build_filename(CMAKE_BINARY_DIR, "url-db-test-cache", nullptr); g_setenv("URL_DISPATCHER_CACHE_DIR", cachedir, TRUE); } virtual void TearDown() { gchar * cmdline = g_strdup_printf("rm -rf \"%s\"", cachedir); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); g_free(cachedir); } bool file_list_has (GList * list, const gchar * filename) { GList * cur; for (cur = list; cur != nullptr; cur = g_list_next(cur)) { const gchar * path = (const gchar *)cur->data; gchar * basename = g_path_get_basename(path); gint same = g_strcmp0(basename, filename); g_free(basename); if (same == 0) { return true; } } return false; } }; static void verify_tables(const gchar *cachedir) { sqlite3 * db = url_db_create_database(); ASSERT_TRUE(db != nullptr); gchar * dbfile = g_build_filename(cachedir, "url-dispatcher", "urls-1.db", nullptr); EXPECT_TRUE(g_file_test(dbfile, G_FILE_TEST_EXISTS)); g_free(dbfile); const char * type = nullptr; EXPECT_EQ(SQLITE_OK, sqlite3_table_column_metadata(db, nullptr, "configfiles", "name", &type, nullptr, nullptr, nullptr, nullptr)); EXPECT_STREQ("text", type); EXPECT_EQ(SQLITE_OK, sqlite3_table_column_metadata(db, nullptr, "configfiles", "timestamp", &type, nullptr, nullptr, nullptr, nullptr)); EXPECT_STREQ("bigint", type); EXPECT_EQ(SQLITE_OK, sqlite3_table_column_metadata(db, nullptr, "urls", "sourcefile", &type, nullptr, nullptr, nullptr, nullptr)); EXPECT_STREQ("integer", type); EXPECT_EQ(SQLITE_OK, sqlite3_table_column_metadata(db, nullptr, "urls", "protocol", &type, nullptr, nullptr, nullptr, nullptr)); EXPECT_STREQ("text", type); EXPECT_EQ(SQLITE_OK, sqlite3_table_column_metadata(db, nullptr, "urls", "domainsuffix", &type, nullptr, nullptr, nullptr, nullptr)); EXPECT_STREQ("text", type); sqlite3_close(db); } TEST_F(UrlDBTest, CreateTest) { // Do it twice to ensure that url_db_create_database works // when invoked on a db that already has the tables. verify_tables(cachedir); verify_tables(cachedir); } TEST_F(UrlDBTest, TimestampTest) { sqlite3 * db = url_db_create_database(); ASSERT_TRUE(db != nullptr); GTimeVal timeval = {0, 0}; EXPECT_FALSE(url_db_get_file_motification_time(db, "/foo", &timeval)); timeval.tv_sec = 12345; EXPECT_TRUE(url_db_set_file_motification_time(db, "/foo", &timeval)); timeval.tv_sec = 0; EXPECT_TRUE(url_db_get_file_motification_time(db, "/foo", &timeval)); EXPECT_EQ(12345, timeval.tv_sec); sqlite3_close(db); } TEST_F(UrlDBTest, UrlTest) { sqlite3 * db = url_db_create_database(); ASSERT_TRUE(db != nullptr); GTimeVal timeval = {0, 0}; timeval.tv_sec = 12345; EXPECT_TRUE(url_db_set_file_motification_time(db, "/foo.url-dispatcher", &timeval)); /* Insert and find */ EXPECT_TRUE(url_db_insert_url(db, "/foo.url-dispatcher", "bar", "foo.com")); EXPECT_STREQ("foo", url_db_find_url(db, "bar", "foo.com")); EXPECT_STREQ("foo", url_db_find_url(db, "bar", "www.foo.com")); /* Two to compete */ timeval.tv_sec = 67890; EXPECT_TRUE(url_db_set_file_motification_time(db, "/bar.url-dispatcher", &timeval)); EXPECT_TRUE(url_db_insert_url(db, "/bar.url-dispatcher", "bar", "more.foo.com")); EXPECT_STREQ("foo", url_db_find_url(db, "bar", "www.foo.com")); EXPECT_STREQ("bar", url_db_find_url(db, "bar", "more.foo.com")); EXPECT_STREQ("bar", url_db_find_url(db, "bar", "www.more.foo.com")); sqlite3_close(db); } TEST_F(UrlDBTest, FileListTest) { sqlite3 * db = url_db_create_database(); ASSERT_TRUE(db != nullptr); GTimeVal timeval = {0, 0}; timeval.tv_sec = 12345; /* One Dir */ EXPECT_TRUE(url_db_set_file_motification_time(db, "/base/directory/for/us/one.url-dispatcher", &timeval)); EXPECT_TRUE(url_db_set_file_motification_time(db, "/base/directory/for/us/two.url-dispatcher", &timeval)); /* No three! */ EXPECT_TRUE(url_db_set_file_motification_time(db, "/base/directory/for/us/four.url-dispatcher", &timeval)); EXPECT_TRUE(url_db_set_file_motification_time(db, "/base/directory/for/us/five.url-dispatcher", &timeval)); /* Another Dir */ EXPECT_TRUE(url_db_set_file_motification_time(db, "/base/directory/for/them/six.url-dispatcher", &timeval)); GList * files = url_db_files_for_dir(db, "/base/directory/for/us"); EXPECT_TRUE(file_list_has(files, "one.url-dispatcher")); EXPECT_TRUE(file_list_has(files, "two.url-dispatcher")); EXPECT_FALSE(file_list_has(files, "three.url-dispatcher")); EXPECT_TRUE(file_list_has(files, "four.url-dispatcher")); EXPECT_TRUE(file_list_has(files, "five.url-dispatcher")); EXPECT_FALSE(file_list_has(files, "six.url-dispatcher")); g_list_free_full(files, g_free); files = url_db_files_for_dir(db, "/base/directory/for/them"); EXPECT_FALSE(file_list_has(files, "five.url-dispatcher")); EXPECT_TRUE(file_list_has(files, "six.url-dispatcher")); g_list_free_full(files, g_free); files = url_db_files_for_dir(db, "/dir/not/there"); EXPECT_EQ(0, g_list_length(files)); sqlite3_close(db); } TEST_F(UrlDBTest, RemoveFile) { sqlite3 * db = url_db_create_database(); ASSERT_TRUE(db != nullptr); GTimeVal timeval = {0, 0}; timeval.tv_sec = 12345; EXPECT_TRUE(url_db_set_file_motification_time(db, "/foo.url-dispatcher", &timeval)); /* Insert and find */ EXPECT_TRUE(url_db_insert_url(db, "/foo.url-dispatcher", "bar", "foo.com")); EXPECT_STREQ("foo", url_db_find_url(db, "bar", "foo.com")); /* Remove and not find */ EXPECT_TRUE(url_db_remove_file(db, "/foo.url-dispatcher")); EXPECT_FALSE(url_db_get_file_motification_time(db, "/foo.url-dispatcher", &timeval)); EXPECT_STREQ(nullptr, url_db_find_url(db, "bar", "foo.com")); sqlite3_close(db); } TEST_F(UrlDBTest, ReplaceTest) { sqlite3 * db = url_db_create_database(); ASSERT_TRUE(db != nullptr); GTimeVal timeval = {0, 0}; GTimeVal timevaltest = {0, 0}; timeval.tv_sec = 12345; EXPECT_TRUE(url_db_set_file_motification_time(db, "/foo.url-dispatcher", &timeval)); url_db_get_file_motification_time(db, "/foo.url-dispatcher", &timevaltest); EXPECT_EQ(12345, timevaltest.tv_sec); /* Replace it */ timeval.tv_sec = 67890; EXPECT_TRUE(url_db_set_file_motification_time(db, "/foo.url-dispatcher", &timeval)); url_db_get_file_motification_time(db, "/foo.url-dispatcher", &timevaltest); EXPECT_EQ(67890, timevaltest.tv_sec); /* Replace it again with the same value */ EXPECT_TRUE(url_db_set_file_motification_time(db, "/foo.url-dispatcher", &timeval)); url_db_get_file_motification_time(db, "/foo.url-dispatcher", &timevaltest); EXPECT_EQ(67890, timevaltest.tv_sec); sqlite3_close(db); } ./tests/lib-test.cc0000644000004100000410000001135113066525443014450 0ustar www-datawww-data/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include "test-config.h" #include #include #include #include class LibTest : public ::testing::Test { protected: DbusTestService * service = nullptr; DbusTestDbusMock * mock = nullptr; DbusTestDbusMockObject * obj = nullptr; GDBusConnection * bus = nullptr; virtual void SetUp() { service = dbus_test_service_new(nullptr); mock = dbus_test_dbus_mock_new("com.canonical.URLDispatcher"); obj = dbus_test_dbus_mock_get_object(mock, "/com/canonical/URLDispatcher", "com.canonical.URLDispatcher", nullptr); dbus_test_dbus_mock_object_add_method(mock, obj, "DispatchURL", G_VARIANT_TYPE("(ss)"), nullptr, /* out */ "", /* python */ nullptr); /* error */ dbus_test_dbus_mock_object_add_method(mock, obj, "TestURL", G_VARIANT_TYPE("as"), G_VARIANT_TYPE("as"), "ret = ['appid']", /* python */ nullptr); /* error */ dbus_test_service_add_task(service, DBUS_TEST_TASK(mock)); dbus_test_service_start_tasks(service); bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); g_dbus_connection_set_exit_on_close(bus, FALSE); g_object_add_weak_pointer(G_OBJECT(bus), (gpointer *)&bus); return; } virtual void TearDown() { g_clear_object(&mock); g_clear_object(&service); g_object_unref(bus); unsigned int cleartry = 0; while (bus != nullptr && cleartry < 100) { g_usleep(100000); while (g_main_pending()) g_main_iteration(TRUE); cleartry++; } return; } }; static void simple_cb (const gchar * /*url*/, gboolean /*success*/, gpointer user_data) { g_main_loop_quit(static_cast(user_data)); } TEST_F(LibTest, BaseTest) { GMainLoop * main = g_main_loop_new(nullptr, FALSE); url_dispatch_send("foo://bar/barish", simple_cb, main); /* Give it some time to send and reply */ g_main_loop_run(main); g_main_loop_unref(main); guint callslen = 0; const DbusTestDbusMockCall * calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "DispatchURL", &callslen, nullptr); // ASSERT_NE(calls, nullptr); ASSERT_EQ(callslen, 1); GVariant * check = g_variant_new_parsed("('foo://bar/barish', '')"); g_variant_ref_sink(check); ASSERT_TRUE(g_variant_equal(calls->params, check)); g_variant_unref(check); } TEST_F(LibTest, NoMain) { /* Spawning a non-main caller */ g_spawn_command_line_sync(LIB_TEST_NO_MAIN_HELPER, nullptr, nullptr, nullptr, nullptr); guint callslen = 0; const DbusTestDbusMockCall * calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "DispatchURL", &callslen, nullptr); // ASSERT_NE(calls, nullptr); ASSERT_EQ(callslen, 1); GVariant * check = g_variant_new_parsed("('foo://bar/barish', '')"); g_variant_ref_sink(check); ASSERT_TRUE(g_variant_equal(calls->params, check)); g_variant_unref(check); } TEST_F(LibTest, RestrictedTest) { GMainLoop * main = g_main_loop_new(nullptr, FALSE); url_dispatch_send_restricted("foo://bar/barish", "bar-package", simple_cb, main); /* Give it some time to send and reply */ g_main_loop_run(main); g_main_loop_unref(main); guint callslen = 0; const DbusTestDbusMockCall * calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "DispatchURL", &callslen, nullptr); // ASSERT_NE(calls, nullptr); ASSERT_EQ(callslen, 1); GVariant * check = g_variant_new_parsed("('foo://bar/barish', 'bar-package')"); g_variant_ref_sink(check); ASSERT_TRUE(g_variant_equal(calls->params, check)); g_variant_unref(check); } TEST_F(LibTest, TestTest) { const gchar * urls[2] = { "foo://bar/barish", nullptr }; gchar ** appids = url_dispatch_url_appid(urls); EXPECT_EQ(1, g_strv_length(appids)); EXPECT_STREQ("appid", appids[0]); g_strfreev(appids); guint callslen = 0; const DbusTestDbusMockCall * calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "TestURL", &callslen, nullptr); // ASSERT_NE(calls, nullptr); ASSERT_EQ(callslen, 1); GVariant * check = g_variant_new_parsed("(['foo://bar/barish'],)"); g_variant_ref_sink(check); ASSERT_TRUE(g_variant_equal(calls->params, check)); g_variant_unref(check); } ./tests/test-urls-simple/0000755000004100000410000000000013066525443015646 5ustar www-datawww-data./tests/test-urls-simple/single-good.url-dispatcher0000644000004100000410000000010013066525443022714 0ustar www-datawww-data[ { "protocol": "http", "domain-suffix": "ubuntu.com" } ] ./tests/lib-test-no-main.c0000644000004100000410000000152313066525443015641 0ustar www-datawww-data/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include #include int main (int argc, char * argv[]) { url_dispatch_send("foo://bar/barish", NULL, NULL); return 0; } ./tests/overlay-tracker-test.cpp0000644000004100000410000001222113066525443017206 0ustar www-datawww-data/** * Copyright © 2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 #include "test-config.h" #include #include #include #include "overlay-tracker-mir.h" #include "ubuntu-app-launch-mock.h" #include "mir-mock.h" class OverlayTrackerTest : public ::testing::Test { protected: virtual void SetUp() { g_setenv("URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR", "1", TRUE); return; } virtual void TearDown() { return; } static gboolean quit_loop (gpointer ploop) { g_main_loop_quit((GMainLoop *)ploop); return FALSE; } void pause (int time) { GMainLoop * loop = g_main_loop_new(nullptr, FALSE); g_timeout_add(time, quit_loop, loop); g_main_loop_run(loop); g_main_loop_unref(loop); } }; TEST_F(OverlayTrackerTest, BasicCreation) { auto tracker = new OverlayTrackerMir(); delete tracker; } TEST_F(OverlayTrackerTest, AddOverlay) { auto tracker = new OverlayTrackerMir(); auto mirconn = mir_mock_connect_last_connect(); EXPECT_EQ("mir_socket_trusted", mirconn.first.substr(mirconn.first.size() - 18)); EXPECT_EQ("url-dispatcher", mirconn.second); EXPECT_TRUE(tracker->addOverlay("app-id", 5, "http://no-name-yet.com")); EXPECT_EQ(5, mir_mock_last_trust_pid); EXPECT_STREQ("url-overlay", ubuntu_app_launch_mock_last_start_session_helper); EXPECT_STREQ("app-id", ubuntu_app_launch_mock_last_start_session_appid); EXPECT_STREQ("http://no-name-yet.com", ubuntu_app_launch_mock_last_start_session_uris[0]); delete tracker; EXPECT_STREQ("url-overlay", ubuntu_app_launch_mock_last_stop_helper); EXPECT_STREQ("app-id", ubuntu_app_launch_mock_last_stop_appid); EXPECT_STREQ("instance", ubuntu_app_launch_mock_last_stop_instance); } TEST_F(OverlayTrackerTest, OverlayABunch) { OverlayTrackerMir tracker; std::uniform_int_distribution<> randpid(1, 32000); std::mt19937 rand; /* Testing adding a bunch of overlays, we're using pretty standard data structures, but let's make sure we didn't break 'em */ for (auto name : std::vector{"warty", "hoary", "breezy", "dapper", "edgy", "feisty", "gutsy", "hardy", "intrepid", "jaunty", "karmic", "lucid", "maverick", "natty", "oneiric", "precise", "quantal", "raring", "saucy", "trusty", "utopic", "vivid", "wily"}) { int pid = randpid(rand); tracker.addOverlay(name.c_str(), pid, "http://ubuntu.com/releases"); EXPECT_EQ(pid, mir_mock_last_trust_pid); EXPECT_EQ(name, ubuntu_app_launch_mock_last_start_session_appid); } } void ualStop (const char * helper_type, std::function addFunc) { auto tracker = new OverlayTrackerMir(); /* Call with the overlay before it is set */ ubuntu_app_launch_mock_observer_helper_stop_func("app-id", "instance", helper_type, ubuntu_app_launch_mock_observer_helper_stop_user_data); EXPECT_TRUE(addFunc(tracker)); mir_mock_last_released_session = nullptr; ubuntu_app_launch_mock_observer_helper_stop_func("app-id", "instance", helper_type, ubuntu_app_launch_mock_observer_helper_stop_user_data); delete tracker; EXPECT_NE(nullptr, mir_mock_last_released_session); } TEST_F(OverlayTrackerTest, UALSignalStop) { ualStop("url-overlay", [](OverlayTrackerMir * tracker) { return tracker->addOverlay("app-id", 5, "http://no-name-yet.com"); }); ualStop("bad-url", [](OverlayTrackerMir * tracker) { return tracker->badUrl(5, "http://no-name-yet.com"); }); } void mirStop (const char * helper_type, const char * appid, std::function addFunc) { g_clear_pointer(&ubuntu_app_launch_mock_last_stop_helper, g_free); g_clear_pointer(&ubuntu_app_launch_mock_last_stop_appid, g_free); g_clear_pointer(&ubuntu_app_launch_mock_last_stop_instance, g_free); auto tracker = new OverlayTrackerMir(); EXPECT_TRUE(addFunc(tracker)); /* Try a badie */ mir_mock_last_trust_func((MirPromptSession *)1337, mir_prompt_session_state_stopped, mir_mock_last_trust_data); EXPECT_NE(nullptr, mir_mock_last_trust_func); mir_mock_last_trust_func(mir_mock_valid_trust_session, mir_prompt_session_state_stopped, mir_mock_last_trust_data); delete tracker; EXPECT_STREQ(helper_type, ubuntu_app_launch_mock_last_stop_helper); EXPECT_STREQ(appid, ubuntu_app_launch_mock_last_stop_appid); EXPECT_STREQ("instance", ubuntu_app_launch_mock_last_stop_instance); } TEST_F(OverlayTrackerTest, MirSignalStop) { mirStop("url-overlay", "app-id", [](OverlayTrackerMir * tracker) { return tracker->addOverlay("app-id", 5, "http://no-name-yet.com"); }); mirStop("bad-url", "url-dispatcher-bad-url-helper", [](OverlayTrackerMir * tracker) { return tracker->badUrl(5, "http://no-name-yet.com"); }); } ./tests/scope-mock.h0000644000004100000410000000671613066525443014640 0ustar www-datawww-data/** * Copyright © 2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include #include #include "scope-checker-facade.h" class unity::scopes::internal::ScopeMetadataImpl { public: ScopeMetadataImpl () { } virtual ~ScopeMetadataImpl () {} }; /******** TODO: Look at replacing this with: #include *******/ class RegistryMock : public virtual unity::scopes::Registry { std::shared_ptr> scopeExceptions; public: RegistryMock (std::shared_ptr> inexceptions) : scopeExceptions(inexceptions) { } unity::scopes::ScopeMetadata get_metadata(std::string const& scope_id) override { std::cout << "Getting Metadata" << std::endl; try { auto exp = scopeExceptions->at(scope_id); throw exp; } catch (std::out_of_range e) { unity::scopes::testing::ScopeMetadataBuilder builder; builder.scope_id(scope_id); std::shared_ptr foo((unity::scopes::Scope *)5, [](unity::scopes::Scope *) { return; }); builder.proxy(foo); builder.display_name("foo"); builder.description("foo"); builder.author("foo"); return builder(); } } /* I hate C++ so much right now */ std::string endpoint() override { throw new std::invalid_argument("Not implemented"); } std::string identity() override { throw new std::invalid_argument("Not implemented"); } std::string target_category() override { throw new std::invalid_argument("Not implemented"); } std::string to_string() override { throw new std::invalid_argument("Not implemented"); } int64_t timeout() override { throw new std::invalid_argument("Not implemented"); } unity::scopes::MetadataMap list() override { throw new std::invalid_argument("Not implemented"); } unity::scopes::MetadataMap list_if(std::function) override { throw new std::invalid_argument("Not implemented"); } bool is_scope_running(std::string const&) override { throw new std::invalid_argument("Not implemented"); } core::ScopedConnection set_scope_state_callback(std::string const&, std::function) override { throw new std::invalid_argument("Not implemented"); } core::ScopedConnection set_list_update_callback(std::function) override { throw new std::invalid_argument("Not implemented"); } }; class RuntimeMock : public RuntimeFacade { std::shared_ptr> scopeExceptions; public: RuntimeMock() { scopeExceptions = std::make_shared>(); } unity::scopes::RegistryProxy registry () { return std::make_shared(scopeExceptions); } void clearExceptions() { scopeExceptions->clear(); } void addException (std::string scopeid, std::exception exp) { (*scopeExceptions)[scopeid] = exp; } }; ./tests/systemd-mock.h0000644000004100000410000005233513066525443015215 0ustar www-datawww-data/* * Copyright © 2017 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program 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 . * * Authors: * Ted Gould */ #include #include #include #include #include #include #include #include #include #include "glib-thread.h" class SystemdMock { public: struct Instance { std::string job; std::string appid; std::string instanceid; pid_t primaryPid; std::vector pids; }; private: DbusTestDbusMock* mock = nullptr; DbusTestDbusMockObject* managerobj = nullptr; GLib::ContextThread thread; std::list> insts; void throwError(GError* error) { if (error == nullptr) { return; } auto message = std::string{"Error in systemd mock: "} + error->message; g_error_free(error); throw std::runtime_error{message}; } public: SystemdMock(const std::list& instances, const std::string& controlGroupPath) { GError* error = nullptr; mock = dbus_test_dbus_mock_new("org.freedesktop.systemd1"); dbus_test_task_set_bus(DBUS_TEST_TASK(mock), DBUS_TEST_SERVICE_BUS_SESSION); dbus_test_task_set_name(DBUS_TEST_TASK(mock), "systemd"); managerobj = dbus_test_dbus_mock_get_object(mock, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", nullptr); dbus_test_dbus_mock_object_add_method(mock, managerobj, "Subscribe", nullptr, nullptr, "", nullptr); dbus_test_dbus_mock_object_add_method( mock, managerobj, "ListUnits", nullptr, G_VARIANT_TYPE("(a(ssssssouso))"), /* ret type */ ("ret = [ " + std::accumulate(instances.begin(), instances.end(), std::string{}, [](const std::string accum, const Instance& inst) { std::string retval = accum; if (!retval.empty()) { retval += ", "; } retval += std::string{"("} + /* start tuple */ "'" + instanceName(inst) + "', " + /* id */ "'unused', " + /* description */ "'unused', " + /* load state */ "'unused', " + /* active state */ "'unused', " + /* substate */ "'unused', " + /* following */ "'/unused', " + /* path */ "5, " + /* jobId */ "'unused', " + /* jobType */ "'" + instancePath(inst) + "'" + /* jobPath */ ")"; /* finish tuple */ return retval; }) + "]") .c_str(), &error); throwError(error); dbus_test_dbus_mock_object_add_method( mock, managerobj, "GetUnit", G_VARIANT_TYPE_STRING, G_VARIANT_TYPE_OBJECT_PATH, /* ret type */ ("ret = '/'\n" + std::accumulate(instances.begin(), instances.end(), std::string{}, [](const std::string accum, const Instance& inst) { std::string retval = accum; retval += "if args[0] == '" + instanceName(inst) + "':\n"; retval += "\tret = '" + instancePath(inst) + "'\n"; return retval; })) .c_str(), &error); throwError(error); dbus_test_dbus_mock_object_add_method( mock, managerobj, "StopUnit", G_VARIANT_TYPE("(ss)"), G_VARIANT_TYPE_OBJECT_PATH, /* ret type */ std::accumulate(instances.begin(), instances.end(), std::string{}, [](const std::string accum, const Instance& inst) { std::string retval = accum; retval += "if args[0] == '" + instanceName(inst) + "':\n"; retval += "\tret = '" + instancePath(inst) + "'\n"; return retval; }) .c_str(), &error); throwError(error); dbus_test_dbus_mock_object_add_method( mock, managerobj, "StartTransientUnit", G_VARIANT_TYPE("(ssa(sv)a(sa(sv)))"), G_VARIANT_TYPE_OBJECT_PATH, /* ret type */ std::accumulate(instances.begin(), instances.end(), std::string{"ret = '/'\n"}, [](const std::string accum, const Instance& inst) { std::string retval = accum; retval += "if args[0] == '" + instanceName(inst) + "':\n"; retval += "\traise dbus.exceptions.DBusException('Already running app" + instanceName(inst) + "', name='org.freedesktop.systemd1.UnitExists')\n"; return retval; }) .c_str(), &error); throwError(error); dbus_test_dbus_mock_object_add_method(mock, managerobj, "ResetFailedUnit", G_VARIANT_TYPE_STRING, nullptr, /* ret type */ "", &error); throwError(error); for (auto& instance : instances) { auto obj = dbus_test_dbus_mock_get_object(mock, instancePath(instance).c_str(), "org.freedesktop.systemd1.Service", &error); throwError(error); dbus_test_dbus_mock_object_add_property(mock, obj, "MainPID", G_VARIANT_TYPE_UINT32, g_variant_new_uint32(instance.primaryPid), &error); throwError(error); dbus_test_dbus_mock_object_add_property(mock, obj, "Result", G_VARIANT_TYPE_STRING, g_variant_new_string("success"), &error); throwError(error); /* Control Group */ auto dir = g_build_filename(controlGroupPath.c_str(), instancePath(instance).c_str(), nullptr); auto tasks = g_build_filename(dir, "tasks", nullptr); g_mkdir_with_parents(dir, 0777); g_file_set_contents(tasks, std::accumulate(instance.pids.begin(), instance.pids.end(), std::string{}, [](const std::string& accum, pid_t pid) { if (accum.empty()) { return std::to_string(pid); } else { return accum + "\n" + std::to_string(pid); } }) .c_str(), -1, &error); throwError(error); g_free(tasks); g_free(dir); dbus_test_dbus_mock_object_add_property(mock, obj, "ControlGroup", G_VARIANT_TYPE_STRING, g_variant_new_string(instancePath(instance).c_str()), nullptr); insts.emplace_back(std::make_pair(instance, obj)); } } ~SystemdMock() { g_debug("Destroying the Systemd Mock"); g_clear_object(&mock); } static std::string dbusSafe(const std::string& input) { std::string output = input; std::transform(output.begin(), output.end(), output.begin(), [](char in) { if (std::isalpha(in) || std::isdigit(in)) { return in; } else { return '_'; } }); return output; } static std::string instancePath(const Instance& inst) { std::string retval = std::string{"/"} + dbusSafe(inst.job) + "/" + dbusSafe(inst.appid); if (!inst.instanceid.empty()) { retval += "/" + dbusSafe(inst.instanceid); } return retval; } static std::string instanceName(const Instance& inst) { return std::string{"ubuntu-app-launch--"} + inst.job + "--" + inst.appid + "--" + inst.instanceid + ".service"; } operator std::shared_ptr() { std::shared_ptr retval(DBUS_TEST_TASK(g_object_ref(mock)), [](DbusTestTask* task) { g_clear_object(&task); }); return retval; } operator DbusTestTask*() { return DBUS_TEST_TASK(mock); } operator DbusTestProcess*() { return DBUS_TEST_PROCESS(mock); } operator DbusTestDbusMock*() { return mock; } unsigned int subscribeCallsCnt() { guint len = 0; GError* error = nullptr; dbus_test_dbus_mock_object_get_method_calls(mock, /* mock */ managerobj, /* manager */ "Subscribe", /* function */ &len, /* number */ &error /* error */ ); if (error != nullptr) { g_warning("Unable to get 'Subscribe' calls from systemd mock: %s", error->message); g_error_free(error); throw std::runtime_error{"Mock disfunctional"}; } return len; } unsigned int listCallsCnt() { guint len = 0; GError* error = nullptr; dbus_test_dbus_mock_object_get_method_calls(mock, /* mock */ managerobj, /* manager */ "ListUnits", /* function */ &len, /* number */ &error /* error */ ); if (error != nullptr) { g_warning("Unable to get 'Subscribe' calls from systemd mock: %s", error->message); g_error_free(error); throw std::runtime_error{"Mock disfunctional"}; } return len; } std::list stopCalls() { guint len = 0; GError* error = nullptr; auto calls = dbus_test_dbus_mock_object_get_method_calls(mock, /* mock */ managerobj, /* manager */ "StopUnit", /* function */ &len, /* number */ &error /* error */ ); if (error != nullptr) { g_warning("Unable to get 'StopUnit' calls from systemd mock: %s", error->message); g_error_free(error); throw std::runtime_error{"Mock disfunctional"}; } std::list retval; for (unsigned int i = 0; i < len; i++) { auto& call = calls[i]; gchar* name = nullptr; gchar* inst = nullptr; g_variant_get(call.params, "(&s&s)", &name, &inst); if (name == nullptr) { g_warning("Invalid 'name' on 'StopUnit' call"); continue; } retval.emplace_back(name); } return retval; } struct TransientUnit { std::string name; std::set environment; std::string execpath; std::list execline; }; std::list unitCalls() { guint len = 0; GError* error = nullptr; auto calls = dbus_test_dbus_mock_object_get_method_calls(mock, /* mock */ managerobj, /* manager */ "StartTransientUnit", /* function */ &len, /* number */ &error /* error */ ); if (error != nullptr) { g_warning("Unable to get 'StartTransientUnit' calls from systemd mock: %s", error->message); g_error_free(error); throw std::runtime_error{"Mock disfunctional"}; } std::list retval; for (unsigned int i = 0; i < len; i++) { auto& call = calls[i]; gchar* name = nullptr; g_variant_get_child(call.params, 0, "&s", &name); if (name == nullptr) { g_warning("Invalid 'name' on 'StartTransientUnit' call"); continue; } TransientUnit unit; unit.name = name; auto paramarray = g_variant_get_child_value(call.params, 2); gchar* ckey; GVariant* var; GVariantIter iter; g_variant_iter_init(&iter, paramarray); while (g_variant_iter_loop(&iter, "(sv)", &ckey, &var)) { g_debug("Looking at parameter: %s", ckey); std::string key{ckey}; if (key == "Environment") { GVariantIter array; gchar* envvar; g_variant_iter_init(&array, var); while (g_variant_iter_loop(&array, "&s", &envvar)) { unit.environment.emplace(envvar); } } else if (key == "ExecStart") { /* a(sasb) */ if (g_variant_n_children(var) > 1) { g_warning("'ExecStart' has more than one entry, only processing the first"); } auto tuple = g_variant_get_child_value(var, 0); const gchar* cpath = nullptr; g_variant_get_child(tuple, 0, "&s", &cpath); if (cpath != nullptr) { unit.execpath = cpath; } else { g_warning("'ExecStart[0][0]' isn't a string?"); } auto vexecarray = g_variant_get_child_value(tuple, 1); GVariantIter execarray; g_variant_iter_init(&execarray, vexecarray); const gchar* execentry; while (g_variant_iter_loop(&execarray, "&s", &execentry)) { unit.execline.emplace_back(execentry); } g_clear_pointer(&vexecarray, g_variant_unref); g_clear_pointer(&tuple, g_variant_unref); } } g_variant_unref(paramarray); retval.emplace_back(unit); } return retval; } std::list resetCalls() { guint len = 0; GError* error = nullptr; auto calls = dbus_test_dbus_mock_object_get_method_calls(mock, /* mock */ managerobj, /* manager */ "ResetFailedUnit", /* function */ &len, /* number */ &error /* error */ ); if (error != nullptr) { g_warning("Unable to get 'ResetFailedUnit' calls from systemd mock: %s", error->message); g_error_free(error); throw std::runtime_error{"Mock disfunctional"}; } std::list retval; for (unsigned int i = 0; i < len; i++) { auto& call = calls[i]; gchar* name = nullptr; g_variant_get(call.params, "(&s)", &name); if (name == nullptr) { g_warning("Invalid 'name' on 'ResetFailedUnit' call"); continue; } retval.emplace_back(name); } return retval; } void managerClear() { GError* error = nullptr; dbus_test_dbus_mock_object_clear_method_calls(mock, /* mock */ managerobj, /* manager */ &error /* error */ ); if (error != nullptr) { g_warning("Unable to clear manager calls: %s", error->message); g_error_free(error); throw std::runtime_error{"Mock disfunctional"}; } } void managerEmitNew(const std::string& name, const std::string& path) { GError* error = nullptr; dbus_test_dbus_mock_object_emit_signal(mock, managerobj, "UnitNew", G_VARIANT_TYPE("(so)"), g_variant_new("(so)", name.c_str(), path.c_str()), &error); if (error != nullptr) { g_warning("Unable to emit 'UnitNew': %s", error->message); g_error_free(error); throw std::runtime_error{"Mock disfunctional"}; } } void managerEmitRemoved(const std::string& name, const std::string& path) { GError* error = nullptr; dbus_test_dbus_mock_object_emit_signal(mock, managerobj, "UnitRemoved", G_VARIANT_TYPE("(so)"), g_variant_new("(so)", name.c_str(), path.c_str()), &error); if (error != nullptr) { g_warning("Unable to emit 'UnitRemoved': %s", error->message); g_error_free(error); throw std::runtime_error{"Mock disfunctional"}; } } void managerEmitFailed(const Instance& inst, const std::string& reason = "fail") { auto instobj = std::find_if(insts.begin(), insts.end(), [inst](const std::pair& item) { return item.first.job == inst.job && item.first.appid == inst.appid && item.first.instanceid == inst.instanceid; }); if (instobj == insts.end()) { throw std::runtime_error{"Unable to find instance"}; } GError* error = nullptr; dbus_test_dbus_mock_object_update_property(mock, instobj->second, "Result", g_variant_new_string(reason.c_str()), &error); if (error != nullptr) { g_warning("Unable to set result to 'fail': %s", error->message); g_error_free(error); throw std::runtime_error{"Mock disfunctional"}; } } std::function stateFunc() { return [this] { return dbus_test_task_get_state(DBUS_TEST_TASK(mock)); }; } }; ./tests/service-test.cc0000644000004100000410000001774013066525443015352 0ustar www-datawww-data/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include "test-config.h" #include #include #include #include #include "url-db.h" #include "systemd-mock.h" #define CGROUP_DIR (CMAKE_BINARY_DIR "/systemd-service-test-cgroups") class ServiceTest : public ::testing::Test { protected: DbusTestService * service = nullptr; DbusTestDbusMock * dashmock = nullptr; DbusTestProcess * dispatcher = nullptr; std::shared_ptr systemd; GDBusConnection * bus = nullptr; virtual void SetUp() { g_setenv("UBUNTU_APP_LAUNCH_USE_SESSION", "1", TRUE); g_setenv("UBUNTU_APP_LAUNCH_SYSTEMD_CGROUP_ROOT", CGROUP_DIR, TRUE); g_setenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH", "/this/should/not/exist", TRUE); g_setenv("URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR", "1", TRUE); g_setenv("URL_DISPATCHER_DISABLE_SCOPE_CHECKING", "1", TRUE); g_setenv("XDG_DATA_DIRS", XDG_DATA_DIRS, TRUE); g_setenv("LD_PRELOAD", MIR_MOCK_PATH, TRUE); SetUpDb(); service = dbus_test_service_new(nullptr); dispatcher = dbus_test_process_new(URL_DISPATCHER_SERVICE); dbus_test_task_set_name(DBUS_TEST_TASK(dispatcher), "Dispatcher"); dbus_test_service_add_task(service, DBUS_TEST_TASK(dispatcher)); /* Systemd Mock */ systemd = std::make_shared( std::list{ {"application-legacy", "single", {}, getpid(), {getpid()}}, }, CGROUP_DIR); dbus_test_service_add_task(service, *systemd); /* Dash Mock */ dashmock = dbus_test_dbus_mock_new("com.canonical.UnityDash"); DbusTestDbusMockObject * fdoobj = dbus_test_dbus_mock_get_object(dashmock, "/unity8_2ddash", "org.freedesktop.Application", nullptr); dbus_test_dbus_mock_object_add_method(dashmock, fdoobj, "Open", G_VARIANT_TYPE("(asa{sv})"), nullptr, /* return */ "", /* python */ nullptr); /* error */ dbus_test_task_set_name(DBUS_TEST_TASK(dashmock), "UnityDash"); dbus_test_service_add_task(service, DBUS_TEST_TASK(dashmock)); /* Start your engines! */ dbus_test_service_start_tasks(service); bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); g_dbus_connection_set_exit_on_close(bus, FALSE); g_object_add_weak_pointer(G_OBJECT(bus), (gpointer *)&bus); return; } virtual void TearDown() { kill(dbus_test_process_get_pid(dispatcher), SIGTERM); systemd.reset(); g_clear_object(&dispatcher); g_clear_object(&dashmock); g_clear_object(&service); g_object_unref(bus); unsigned int cleartry = 0; while (bus != nullptr && cleartry < 100) { pause(100); cleartry++; } TearDownDb(); return; } void SetUpDb () { const gchar * cachedir = CMAKE_BINARY_DIR "/service-test-cache"; ASSERT_EQ(0, g_mkdir_with_parents(cachedir, 0700)); g_setenv("XDG_CACHE_HOME", cachedir, TRUE); sqlite3 * db = url_db_create_database(); GTimeVal time = {0, 0}; time.tv_sec = 5; url_db_set_file_motification_time(db, "/unity8-dash.url-dispatcher", &time); url_db_insert_url(db, "/unity8-dash.url-dispatcher", "scopeish", nullptr); sqlite3_close(db); } void TearDownDb () { g_spawn_command_line_sync("rm -rf " CMAKE_BINARY_DIR "/service-test-cache", nullptr, nullptr, nullptr, nullptr); } static gboolean quit_loop (gpointer ploop) { g_main_loop_quit((GMainLoop *)ploop); return FALSE; } void pause (int time) { GMainLoop * loop = g_main_loop_new(nullptr, FALSE); g_timeout_add(time, quit_loop, loop); g_main_loop_run(loop); g_main_loop_unref(loop); } }; static void simple_cb (const gchar * /*url*/, gboolean /*success*/, gpointer user_data) { g_main_loop_quit(static_cast(user_data)); } TEST_F(ServiceTest, InvalidTest) { GMainLoop * main = g_main_loop_new(nullptr, FALSE); /* Send an invalid URL */ url_dispatch_send("foo://bar/barish", simple_cb, main); /* Give it some time to send and reply */ g_main_loop_run(main); g_main_loop_unref(main); auto calls = systemd->unitCalls(); ASSERT_EQ(0u, calls.size()); } TEST_F(ServiceTest, ApplicationTest) { GMainLoop * main = g_main_loop_new(nullptr, FALSE); /* Send an invalid URL */ url_dispatch_send("application:///foo-bar.desktop", simple_cb, main); /* Give it some time to send and reply */ g_main_loop_run(main); g_main_loop_unref(main); /* Check to see it called systemd */ auto calls = systemd->unitCalls(); ASSERT_EQ(1u, calls.size()); EXPECT_EQ(SystemdMock::instanceName({"application-legacy", "foo-bar", "", 0, {}}), calls.begin()->name); } TEST_F(ServiceTest, TestURLTest) { /* Simple */ const char * testurls[] = { "application:///foo-bar.desktop", nullptr }; gchar ** appids = url_dispatch_url_appid(testurls); ASSERT_EQ(1u, g_strv_length(appids)); EXPECT_STREQ("foo-bar", appids[0]); g_strfreev(appids); /* Multiple */ const char * multiurls[] = { "application:///bar-foo.desktop", "application:///foo-bar.desktop", nullptr }; gchar ** multiappids = url_dispatch_url_appid(multiurls); ASSERT_EQ(2u, g_strv_length(multiappids)); EXPECT_STREQ("bar-foo", multiappids[0]); EXPECT_STREQ("foo-bar", multiappids[1]); g_strfreev(multiappids); /* Error URL */ const char * errorurls[] = { "foo://bar/no/url", nullptr }; gchar ** errorappids = url_dispatch_url_appid(errorurls); ASSERT_EQ(0u, g_strv_length(errorappids)); g_strfreev(errorappids); } void focus_signal_cb (GDBusConnection * /*connection*/, const gchar * /*sender_name*/, const gchar * /*object_path*/, const gchar * /*interface_name*/, const gchar * /*signal_name*/, GVariant * /*parameters*/, gpointer user_data) { guint * focus_count = (guint *)user_data; *focus_count = *focus_count + 1; g_debug("Focus signaled: %d", *focus_count); } TEST_F(ServiceTest, Unity8DashTest) { guint focus_count = 0; GDBusConnection * bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); guint focus_signal = g_dbus_connection_signal_subscribe(bus, nullptr, /* sender */ "com.canonical.UbuntuAppLaunch", "UnityFocusRequest", "/", "unity8-dash", /* arg0 */ G_DBUS_SIGNAL_FLAGS_NONE, focus_signal_cb, &focus_count, nullptr); /* destroy func */ DbusTestDbusMockObject * fdoobj = dbus_test_dbus_mock_get_object(dashmock, "/unity8_2ddash", "org.freedesktop.Application", nullptr); GMainLoop * main = g_main_loop_new(nullptr, FALSE); /* Send an invalid URL */ url_dispatch_send("scopeish://foo-bar", simple_cb, main); /* Give it some time to send and reply */ g_main_loop_run(main); g_main_loop_unref(main); guint callslen = 0; auto calls = dbus_test_dbus_mock_object_get_method_calls(dashmock, fdoobj, "Open", &callslen, nullptr); EXPECT_EQ(1u, callslen); EXPECT_TRUE(g_variant_equal(calls[0].params, g_variant_new_parsed("(['scopeish://foo-bar'], @a{sv} {})"))); EXPECT_EQ(1u, focus_count); g_dbus_connection_signal_unsubscribe(bus, focus_signal); g_clear_object(&bus); } ./tests/ubuntu-app-launch-mock.h0000644000004100000410000000321613066525443017067 0ustar www-datawww-data/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #ifndef UPSTART_APP_LAUNCH_MOCK #define UPSTART_APP_LAUNCH_MOCK 1 #include #include G_BEGIN_DECLS void ubuntu_app_launch_mock_clear_last_version (); void ubuntu_app_launch_mock_clear_last_app_id (); gchar * ubuntu_app_launch_mock_get_last_app_id (); extern UbuntuAppLaunchHelperObserver ubuntu_app_launch_mock_observer_helper_stop_func; extern gchar * ubuntu_app_launch_mock_observer_helper_stop_type; extern void * ubuntu_app_launch_mock_observer_helper_stop_user_data; extern gchar * ubuntu_app_launch_mock_last_start_session_helper; extern MirPromptSession * ubuntu_app_launch_mock_last_start_session_session; extern gchar * ubuntu_app_launch_mock_last_start_session_appid; extern gchar ** ubuntu_app_launch_mock_last_start_session_uris; extern gchar * ubuntu_app_launch_mock_last_stop_helper; extern gchar * ubuntu_app_launch_mock_last_stop_appid; extern gchar * ubuntu_app_launch_mock_last_stop_instance; G_END_DECLS #endif /* UPSTART_APP_LAUNCH_MOCK */ ./tests/test-config.h.in0000644000004100000410000000154613066525443015423 0ustar www-datawww-data#pragma once #define CMAKE_SOURCE_DIR "@CMAKE_SOURCE_DIR@" #define CMAKE_BINARY_DIR "@CMAKE_BINARY_DIR@" #define APP_ID_TEST_URI @APP_ID_TEST_URI@ #define LIB_TEST_NO_MAIN_HELPER "@CMAKE_CURRENT_BINARY_DIR@/lib-test-no-main" #define URL_DISPATCHER_SERVICE "@CMAKE_BINARY_DIR@/service/url-dispatcher" #define XDG_DATA_DIRS "@CMAKE_CURRENT_SOURCE_DIR@/xdg-data" #define UPDATE_DIRECTORY_TOOL "@CMAKE_BINARY_DIR@/service/update-directory" #define UPDATE_DIRECTORY_URLS "@CMAKE_CURRENT_SOURCE_DIR@/test-urls-simple" #define UPDATE_DIRECTORY_VARIED "@CMAKE_CURRENT_SOURCE_DIR@/test-urls-varied" #define UPDATE_DIRECTORY_INTENT "@CMAKE_CURRENT_SOURCE_DIR@/test-urls-intent" #define OVERLAY_TEST_DIR "@CMAKE_CURRENT_SOURCE_DIR@/overlay-dir" #define EXEC_TOOL "@CMAKE_BINARY_DIR@/service/url-overlay/exec-tool" #define MIR_MOCK_PATH "@CMAKE_CURRENT_BINARY_DIR@/libmir-mock.so" ./tests/dispatcher-test.cc0000644000004100000410000003104313066525464016033 0ustar www-datawww-data/** * Copyright (C) 2013 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include "test-config.h" #include #include #include #include "dispatcher.h" #include "ubuntu-app-launch-mock.h" #include "overlay-tracker-mock.h" #include "url-db.h" #include "scope-mock.h" #include "apparmor-mock.h" class DispatcherTest : public ::testing::Test { private: GTestDBus * testbus = nullptr; GMainLoop * mainloop = nullptr; gchar * cachedir = nullptr; protected: OverlayTrackerMock tracker; RuntimeMock scope_runtime; GDBusConnection * session = nullptr; virtual void SetUp() { g_setenv("URL_DISPATCHER_OVERLAY_DIR", OVERLAY_TEST_DIR, TRUE); cachedir = g_build_filename(CMAKE_BINARY_DIR, "dispatcher-test-cache", nullptr); g_setenv("URL_DISPATCHER_CACHE_DIR", cachedir, TRUE); sqlite3 * db = url_db_create_database(); GTimeVal timestamp; timestamp.tv_sec = 12345; timestamp.tv_usec = 0; url_db_set_file_motification_time(db, "/testdir/com.ubuntu.calendar_calendar_9.8.2343.url-dispatcher", ×tamp); url_db_insert_url(db, "/testdir/com.ubuntu.calendar_calendar_9.8.2343.url-dispatcher", "calendar", nullptr); url_db_set_file_motification_time(db, "/testdir/com.ubuntu.dialer_dialer_1234.url-dispatcher", ×tamp); url_db_insert_url(db, "/testdir/com.ubuntu.dialer_dialer_1234.url-dispatcher", "tel", NULL); url_db_set_file_motification_time(db, "/testdir/magnet-test.url-dispatcher", ×tamp); url_db_insert_url(db, "/testdir/magnet-test.url-dispatcher", "magnet", NULL); url_db_set_file_motification_time(db, "/testdir/browser.url-dispatcher", ×tamp); url_db_insert_url(db, "/testdir/browser.url-dispatcher", "http", NULL); url_db_set_file_motification_time(db, "/testdir/webapp.url-dispatcher", ×tamp); url_db_insert_url(db, "/testdir/webapp.url-dispatcher", "http", "foo.com"); url_db_set_file_motification_time(db, "/testdir/intenter.url-dispatcher", ×tamp); url_db_insert_url(db, "/testdir/intenter.url-dispatcher", "intent", "my.android.package"); url_db_set_file_motification_time(db, "/testdir/scoper.url-dispatcher", ×tamp); url_db_insert_url(db, "/testdir/scoper.url-dispatcher", "scope", nullptr); sqlite3_close(db); testbus = g_test_dbus_new(G_TEST_DBUS_NONE); g_test_dbus_up(testbus); session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); mainloop = g_main_loop_new(nullptr, FALSE); dispatcher_init(mainloop, reinterpret_cast(&tracker), reinterpret_cast(&scope_runtime)); return; } virtual void TearDown() { dispatcher_shutdown(); /* Clean up queued events */ while (g_main_pending()) { g_main_iteration(TRUE); } g_main_loop_unref(mainloop); ubuntu_app_launch_mock_clear_last_app_id(); scope_runtime.clearExceptions(); /* let other threads settle */ g_usleep(500000); g_clear_object(&session); g_test_dbus_down(testbus); g_object_unref(testbus); gchar * cmdline = g_strdup_printf("rm -rf \"%s\"", cachedir); g_spawn_command_line_sync(cmdline, nullptr, nullptr, nullptr, nullptr); g_free(cmdline); g_free(cachedir); return; } }; TEST_F(DispatcherTest, AppIdTest) { gchar * out_appid = nullptr; const gchar * out_url = nullptr; /* Good sanity check */ dispatcher_url_to_appid("appid://foobar/baz/1.2.3", &out_appid, &out_url); ASSERT_STREQ("foobar_baz_1.2.3", out_appid); dispatcher_send_to_app(out_appid, out_url); EXPECT_STREQ("foobar_baz_1.2.3", ubuntu_app_launch_mock_get_last_app_id()); ubuntu_app_launch_mock_clear_last_app_id(); ubuntu_app_launch_mock_clear_last_version(); g_clear_pointer(&out_appid, g_free); out_url = nullptr; /* No version at all */ dispatcher_url_to_appid("appid://com.test.good/app1", &out_appid, &out_url); EXPECT_STREQ(nullptr, out_appid); EXPECT_EQ(nullptr, out_url); ubuntu_app_launch_mock_clear_last_app_id(); ubuntu_app_launch_mock_clear_last_version(); g_clear_pointer(&out_appid, g_free); out_url = nullptr; /* App that would be in a libertine container */ dispatcher_url_to_appid("appid://container-id/org.canonical.app1/0.0", &out_appid, &out_url); EXPECT_STREQ("container-id_org.canonical.app1_0.0", out_appid); EXPECT_EQ(nullptr, out_url); dispatcher_send_to_app(out_appid, out_url); EXPECT_STREQ("container-id_org.canonical.app1_0.0", ubuntu_app_launch_mock_get_last_app_id()); ubuntu_app_launch_mock_clear_last_app_id(); g_clear_pointer(&out_appid, g_free); out_url = nullptr; } TEST_F(DispatcherTest, ApplicationTest) { gchar * out_appid = nullptr; const gchar * out_url = nullptr; /* Good sanity check */ dispatcher_url_to_appid("application:///foo.desktop", &out_appid, &out_url); dispatcher_send_to_app(out_appid, out_url); ASSERT_STREQ("foo", ubuntu_app_launch_mock_get_last_app_id()); ubuntu_app_launch_mock_clear_last_app_id(); /* No .desktop */ dispatcher_url_to_appid("application:///foo", &out_appid, &out_url); dispatcher_send_to_app(out_appid, out_url); ASSERT_TRUE(nullptr == ubuntu_app_launch_mock_get_last_app_id()); ubuntu_app_launch_mock_clear_last_app_id(); /* Missing a / */ dispatcher_url_to_appid("application://foo.desktop", &out_appid, &out_url); dispatcher_send_to_app(out_appid, out_url); ASSERT_TRUE(nullptr == ubuntu_app_launch_mock_get_last_app_id()); ubuntu_app_launch_mock_clear_last_app_id(); /* Good with hyphens */ dispatcher_url_to_appid("application:///my-really-cool-app.desktop", &out_appid, &out_url); dispatcher_send_to_app(out_appid, out_url); ASSERT_STREQ("my-really-cool-app", ubuntu_app_launch_mock_get_last_app_id()); ubuntu_app_launch_mock_clear_last_app_id(); /* Good Click Style */ dispatcher_url_to_appid("application:///com.test.foo_bar-app_0.3.4.desktop", &out_appid, &out_url); dispatcher_send_to_app(out_appid, out_url); ASSERT_STREQ("com.test.foo_bar-app_0.3.4", ubuntu_app_launch_mock_get_last_app_id()); ubuntu_app_launch_mock_clear_last_app_id(); return; } TEST_F(DispatcherTest, RestrictionTest) { /* nullptr cases */ EXPECT_FALSE(dispatcher_appid_restrict("foo-bar", nullptr)); EXPECT_FALSE(dispatcher_appid_restrict("foo-bar", "")); /* Legacy case, full match */ EXPECT_FALSE(dispatcher_appid_restrict("foo-bar", "foo-bar")); EXPECT_FALSE(dispatcher_appid_restrict("foo_bar", "foo_bar")); EXPECT_TRUE(dispatcher_appid_restrict("foo_bar", "foo-bar")); /* Click case, match package */ EXPECT_FALSE(dispatcher_appid_restrict("com.test.foo_bar-app_0.3.4", "com.test.foo")); EXPECT_TRUE(dispatcher_appid_restrict("com.test.foo_bar-app_0.3.4", "com.test.bar")); EXPECT_TRUE(dispatcher_appid_restrict("com.test.foo_bar-app", "com.test.bar")); } TEST_F(DispatcherTest, CalendarTest) { gchar * out_appid = nullptr; const gchar * out_url = nullptr; /* Base Calendar */ dispatcher_url_to_appid("calendar:///?starttime=196311221830Z", &out_appid, &out_url); dispatcher_send_to_app(out_appid, out_url); ASSERT_STREQ("com.ubuntu.calendar_calendar_9.8.2343", ubuntu_app_launch_mock_get_last_app_id()); ubuntu_app_launch_mock_clear_last_app_id(); /* Two Slash, nothing else */ dispatcher_url_to_appid("calendar://", &out_appid, &out_url); dispatcher_send_to_app(out_appid, out_url); ASSERT_STREQ("com.ubuntu.calendar_calendar_9.8.2343", ubuntu_app_launch_mock_get_last_app_id()); ubuntu_app_launch_mock_clear_last_app_id(); return; } TEST_F(DispatcherTest, DialerTest) { gchar * out_appid = NULL; const gchar * out_url = NULL; /* Base Telephone */ EXPECT_TRUE(dispatcher_url_to_appid("tel:+442031485000", &out_appid, &out_url)); EXPECT_STREQ("com.ubuntu.dialer_dialer_1234", out_appid); g_free(out_appid); /* Tel with bunch of commas */ EXPECT_TRUE(dispatcher_url_to_appid("tel:911,,,1,,1,,2", &out_appid, &out_url)); EXPECT_STREQ("com.ubuntu.dialer_dialer_1234", out_appid); g_free(out_appid); /* Telephone with slashes */ EXPECT_TRUE(dispatcher_url_to_appid("tel:///+442031485000", &out_appid, &out_url)); EXPECT_STREQ("com.ubuntu.dialer_dialer_1234", out_appid); g_free(out_appid); return; } TEST_F(DispatcherTest, MagnetTest) { gchar * out_appid = NULL; const gchar * out_url = NULL; EXPECT_TRUE(dispatcher_url_to_appid("magnet:?xt=urn:ed2k:31D6CFE0D16AE931B73C59D7E0C089C0&xl=0&dn=zero_len.fil&xt=urn:bitprint:3I42H3S6NNFQ2MSVX7XZKYAYSCX5QBYJ.LWPNACQDBZRYXW3VHJVCJ64QBZNGHOHHHZWCLNQ&xt=urn:md5:D41D8CD98F00B204E9800998ECF8427E", &out_appid, &out_url)); EXPECT_STREQ("magnet-test", out_appid); g_free(out_appid); return; } TEST_F(DispatcherTest, WebappTest) { gchar * out_appid = NULL; const gchar * out_url = NULL; /* Browser test */ EXPECT_TRUE(dispatcher_url_to_appid("http://ubuntu.com", &out_appid, &out_url)); EXPECT_STREQ("browser", out_appid); g_free(out_appid); /* Webapp test */ EXPECT_TRUE(dispatcher_url_to_appid("http://foo.com", &out_appid, &out_url)); EXPECT_STREQ("webapp", out_appid); g_free(out_appid); EXPECT_TRUE(dispatcher_url_to_appid("http://m.foo.com", &out_appid, &out_url)); EXPECT_STREQ("webapp", out_appid); g_free(out_appid); return; } TEST_F(DispatcherTest, IntentTest) { gchar * out_appid = nullptr; const gchar * out_url = nullptr; /* Intent basic test */ EXPECT_TRUE(dispatcher_url_to_appid("intent://foo.google.com/maps#Intent;scheme=http;package=my.android.package;end", &out_appid, &out_url)); EXPECT_STREQ("intenter", out_appid); g_free(out_appid); /* Not our intent test */ out_appid = nullptr; EXPECT_FALSE(dispatcher_url_to_appid("intent://foo.google.com/maps#Intent;scheme=http;package=not.android.package;end", &out_appid, &out_url)); EXPECT_EQ(nullptr, out_appid); /* Ensure domain is ignored */ out_appid = nullptr; EXPECT_FALSE(dispatcher_url_to_appid("intent://my.android.package/maps#Intent;scheme=http;package=not.android.package;end", &out_appid, &out_url)); EXPECT_EQ(nullptr, out_appid); return; } DbusTestDbusMock * setupPidMock() { auto mock = dbus_test_dbus_mock_new("com.canonical.UnityDash"); dbus_test_task_set_name(DBUS_TEST_TASK(mock), "UnityDash"); dbus_test_task_run(DBUS_TEST_TASK(mock)); return mock; } TEST_F(DispatcherTest, OverlayTest) { EXPECT_TRUE(dispatcher_is_overlay("com.test.good_application_1.2.3")); EXPECT_FALSE(dispatcher_is_overlay("com.test.bad_application_1.2.3")); EXPECT_TRUE(dispatcher_send_to_overlay ("com.test.good_application_1.2.3", "overlay://ubuntu.com", session, g_dbus_connection_get_unique_name(session))); ASSERT_EQ(1, tracker.addedOverlays.size()); EXPECT_EQ("com.test.good_application_1.2.3", std::get<0>(tracker.addedOverlays[0])); EXPECT_EQ(getpid(), std::get<1>(tracker.addedOverlays[0])); EXPECT_EQ("overlay://ubuntu.com", std::get<2>(tracker.addedOverlays[0])); tracker.addedOverlays.clear(); aa_mock_gettask_profile = "simplescope.scopemaster_simplescope_1.2.3"; auto pidmock = setupPidMock(); EXPECT_TRUE(dispatcher_send_to_overlay ("com.test.good_application_1.2.3", "overlay://ubuntu.com", session, g_dbus_connection_get_unique_name(session))); ASSERT_EQ(1, tracker.addedOverlays.size()); EXPECT_EQ("com.test.good_application_1.2.3", std::get<0>(tracker.addedOverlays[0])); EXPECT_NE(0, std::get<1>(tracker.addedOverlays[0])); EXPECT_EQ("overlay://ubuntu.com", std::get<2>(tracker.addedOverlays[0])); g_object_unref(pidmock); return; } TEST_F(DispatcherTest, ScopeTest) { gchar * out_appid = nullptr; unity::scopes::NotFoundException scopeException("test", "badscope"); scope_runtime.addException("badscope.scopemaster_badscope", scopeException); std::invalid_argument invalidException("confused"); scope_runtime.addException("confusedscope.scopemaster_confusedscope", invalidException); /* Good sanity check */ dispatcher_url_to_appid("scope://simplescope.scopemaster_simplescope", &out_appid, nullptr); EXPECT_STREQ("scoper", out_appid); g_free(out_appid); /* Bad scope */ dispatcher_url_to_appid("scope://badscope.scopemaster_badscope", &out_appid, nullptr); EXPECT_STRNE("scoper", out_appid); g_free(out_appid); /* Confused scope */ dispatcher_url_to_appid("scope://confusedscope.scopemaster_confusedscope", &out_appid, nullptr); EXPECT_STRNE("scoper", out_appid); g_free(out_appid); } ./tests/recoverable-problem-mock.c0000644000004100000410000000156313066525473017447 0ustar www-datawww-data/* * Copyright 2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Ted Gould */ #include void whoopsie_report_recoverable_problem (const gchar * signature, GPid report_pid, gboolean wait, const gchar * additional_properties[]) { return; } ./tests/manual0000644000004100000410000000311413066525443013614 0ustar www-datawww-dataTest-case url-dispatcher/scope-url
Navigate to the Home screen of the shell
Touch the search icon to open the search box of the shell
The search text box should open and if there is no physical keyboard present a keyboard to enter a search
Enter berlin wall into the search box
An entry with a picture of the Berlin Wall should be included
Activate the entry for The Berlin Wall to see the description and verify it's the correct entry
There should be a short description of the wall and a button to open up the Wikipedia entry
Select the button to bring up the Wikipedia entry
The webbrowser should come to focus and navigate to the Wikipedia entry on the Berlin Wall
Test-case url-dispatcher/settings-second-activation
Open the system settings app
Settings app should open, focus, and be showing the category selection screen
Open the laucher using the left swipe/dt>
Select the Ubuntu button to return to the home screen
The settings apps should not be visible
Open the power indicator by swiping from the top by the battery icon
At the top of the screen there should be a "Battery" header
Select the "Settings" item at the bottom of the indicator entries
The indicator should close
The settings application should come to focus
The settings app should display the battery settings
./tests/xdg-data/0000755000004100000410000000000013066525443014106 5ustar www-datawww-data./tests/xdg-data/libertine/0000755000004100000410000000000013066525443016063 5ustar www-datawww-data./tests/xdg-data/libertine/ContainersConfig.json0000644000004100000410000000052213066525443022210 0ustar www-datawww-data{ "containerList": [ { "distro": "xenial", "id": "container-id", "installStatus": "ready", "installedApps": [], "multiarch": "disabled", "name": "Ubuntu 'Xenial Xerus'", "type": "lxc" } ], "defaultContainer": "container-id" } ./tests/xdg-data/applications/0000755000004100000410000000000013066525443016574 5ustar www-datawww-data./tests/xdg-data/applications/foo-bar.desktop0000644000004100000410000000015213066525443021512 0ustar www-datawww-data[Desktop Entry] Name=Foo Bar Exec=foo-bar Type=Application Icon=foo-bar.png X-Ubuntu-Single-Instance=true ./tests/apparmor-mock.h0000644000004100000410000000134313066525443015337 0ustar www-datawww-data/** * Copyright © 2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #pragma once extern const char * aa_mock_gettask_profile; ./tests/url_dispatcher_testability/0000755000004100000410000000000013066525505020041 5ustar www-datawww-data./tests/url_dispatcher_testability/CMakeLists.txt0000644000004100000410000000122213066525443022577 0ustar www-datawww-dataexecute_process(COMMAND python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())" OUTPUT_VARIABLE PYTHON_PACKAGE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) configure_file( test_fake_dispatcher.py.in test_fake_dispatcher.py ) install(FILES fixture_setup.py fake_dispatcher.py ${CMAKE_CURRENT_BINARY_DIR}/test_fake_dispatcher.py DESTINATION ${PYTHON_PACKAGE_DIR}/url_dispatcher_testability ) add_test(fake-dispatcher dbus-test-runner --task nosetests3 --parameter ${CMAKE_CURRENT_BINARY_DIR}/test_fake_dispatcher.py) set_tests_properties(fake-dispatcher PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/..) ./tests/url_dispatcher_testability/fixture_setup.py0000644000004100000410000000222113066525443023317 0ustar www-datawww-data# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright (C) 2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; version 3. # # This program 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import fixtures from url_dispatcher_testability import fake_dispatcher class FakeURLDispatcher(fixtures.Fixture): def setUp(self): super(FakeURLDispatcher, self).setUp() self.fake_service = fake_dispatcher.FakeURLDispatcherService() self.addCleanup(self.fake_service.stop) self.fake_service.start() def get_last_dispatch_url_call_parameter(self): return self.fake_service.get_last_dispatch_url_call_parameter() ./tests/url_dispatcher_testability/fake_dispatcher.py0000644000004100000410000000441213066525443023531 0ustar www-datawww-data# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright (C) 2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; version 3. # # This program 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import dbus import dbusmock import subprocess class FakeDispatcherException(Exception): pass class FakeURLDispatcherService: """Fake URL Dispatcher service using a dbusmock interface.""" def __init__(self): super(FakeURLDispatcherService, self).__init__() self.dbus_connection = dbusmock.DBusTestCase.get_dbus(system_bus=False) def start(self): """Start the fake URL Dispatcher service.""" self.dbus_mock_server = dbusmock.DBusTestCase.spawn_server( 'com.canonical.URLDispatcher', '/com/canonical/URLDispatcher', 'com.canonical.URLDispatcher', system_bus=False, stdout=subprocess.PIPE) self.mock = self._get_mock_interface() self.mock.AddMethod( 'com.canonical.URLDispatcher', 'DispatchURL', 'ss', '', '') def _get_mock_interface(self): return dbus.Interface( self.dbus_connection.get_object( 'com.canonical.URLDispatcher', '/com/canonical/URLDispatcher'), dbusmock.MOCK_IFACE) def stop(self): """Stop the fake URL Dispatcher service.""" self.dbus_mock_server.terminate() self.dbus_mock_server.wait() def get_last_dispatch_url_call_parameter(self): """Return the parameter used in the last call to dispatch URL.""" calls = self.mock.GetCalls() if len(calls) == 0: raise FakeDispatcherException( 'URL dispatcher has not been called.') last_call = self.mock.GetCalls()[-1] return last_call[2][0] ./tests/url_dispatcher_testability/__init__.py0000644000004100000410000000000013066525443022141 0ustar www-datawww-data./tests/url_dispatcher_testability/test_fake_dispatcher.py.in0000644000004100000410000000320313066525505025171 0ustar www-datawww-data# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright (C) 2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; version 3. # # This program 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import testtools from url_dispatcher_testability import fixture_setup, fake_dispatcher from subprocess import call, CalledProcessError class FakeDispatcherTestCase(testtools.TestCase): def setUp(self): super(FakeDispatcherTestCase, self).setUp() self.dispatcher = fixture_setup.FakeURLDispatcher() self.useFixture(self.dispatcher) def test_url_dispatcher(self): bin_dir = "@PROJECT_BINARY_DIR@" result = call([bin_dir + '/tools/url-dispatcher', 'test://testurl']) self.assertEqual(0, result) try: last_url = self.dispatcher.get_last_dispatch_url_call_parameter() except fake_dispatcher.FakeDispatcherException: last_url = None self.assertEqual(last_url, 'test://testurl') def test_url_dispatcher_no_args(self): bin_dir = "@PROJECT_BINARY_DIR@" result = call([bin_dir + '/tools/url-dispatcher']) self.assertEqual(1, result) ./po/0000755000004100000410000000000013066525443011671 5ustar www-datawww-data./po/CMakeLists.txt0000644000004100000410000000044013066525443014427 0ustar www-datawww-datafind_package(Intltool REQUIRED) set(GETTEXT_PACKAGE ${PROJECT_NAME}) intltool_update_potfile( GETTEXT_PACKAGE ${GETTEXT_PACKAGE} COPYRIGHT_HOLDER "Canonical Ltd." UBUNTU_SDK_DEFAULTS FILTER "tests/*" ) intltool_install_translations( ALL GETTEXT_PACKAGE ${GETTEXT_PACKAGE} ) ./po/url-dispatcher.pot0000644000004100000410000000140113066525443015337 0ustar www-datawww-data# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Canonical Ltd. # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-03-03 17:18-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../data/bad-url.qml:30 msgid "Unrecognized Address" msgstr "" #: ../data/bad-url.qml:31 msgid "Ubuntu can't open addresses of type “%1”." msgstr "" #: ../data/bad-url.qml:34 msgid "OK" msgstr "" ./po/genpotfiles.sh0000755000004100000410000000016713066525443014553 0ustar www-datawww-data#!/bin/sh sed '/^#/d s/^[[].*] *// /^[ ]*$/d' \ "`dirname ${0}`/POTFILES.in" | sed '$!s/$/ \\/' > POTFILES ./tools/0000755000004100000410000000000013066525505012412 5ustar www-datawww-data./tools/CMakeLists.txt0000644000004100000410000000103613066525505015152 0ustar www-datawww-data ########################### # URL Dispatcher ########################### include_directories(${CMAKE_SOURCE_DIR}/liburl-dispatcher) add_executable(url-dispatcher url-dispatcher.cpp) target_link_libraries(url-dispatcher dispatcher ) install( TARGETS url-dispatcher RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) install( PROGRAMS url-dispatcher-dump DESTINATION "${CMAKE_INSTALL_BINDIR}" ) ########################## # Coverage ########################### set_property(GLOBAL APPEND PROPERTY COVERAGE_TARGETS url-dispatcher) ./tools/url-dispatcher-dump0000755000004100000410000000035313066525443016233 0ustar www-datawww-data#!/bin/sh echo .quit | sqlite3 -batch -csv -cmd "select urls.protocol, urls.domainsuffix, configfiles.name from urls, configfiles where urls.sourcefile = configfiles.rowid;" "${XDG_CACHE_HOME:-$HOME/.cache}"/url-dispatcher/urls-1.db ./tools/url-dispatcher.cpp0000644000004100000410000000262013066525505016044 0ustar www-datawww-data/** * Copyright (C) 2017 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include "url-dispatcher.h" int main (int argc, char * argv[]) { struct Data { GMainLoop* loop = nullptr; bool success = false; }; if (argc != 2) { g_printerr("Usage: %s \n", argv[0]); return 1; } Data data; data.loop = g_main_loop_new(nullptr, FALSE); url_dispatch_send(argv[1], [](const gchar* url, gboolean success, gpointer user_data) { auto cbdata = static_cast(user_data); cbdata->success = success; g_main_loop_quit(cbdata->loop); }, &data); g_main_loop_run(data.loop); return !data.success; } ./data/0000755000004100000410000000000013066525443012164 5ustar www-datawww-data./data/url-dispatcher-update-user.conf.in0000644000004100000410000000073413066525443020626 0ustar www-datawww-datadescription "URL Dispatcher User Directory Watch" start on (file FILE=~/.config/url-dispatcher/urls/*.url-dispatcher) or url-dispatcher-update-user task nice 19 pre-start script RUNNING=$(initctl status url-dispatcher | grep start/running 2> /dev/null) if [ "x${RUNNING}" = "x" ]; then echo "DEBUG: Started before URL Dispatcher, let it try to build the DB" sleep 5 fi end script exec ${SNAP}@pkglibexecdir@/update-directory "${HOME}/.config/url-dispatcher/urls/" ./data/CMakeLists.txt0000644000004100000410000000343013066525443014724 0ustar www-datawww-data ########################### # Dbus Interfaces ########################### install( FILES com.canonical.URLDispatcher.xml DESTINATION ${DBUSIFACEDIR} ) ########################### # Set stuff ########################### set(pkglibexecdir "${CMAKE_INSTALL_FULL_LIBEXECDIR}/url-dispatcher") set(datadir "${CMAKE_INSTALL_FULL_DATADIR}") ########################### # Upstart Job ########################### set(DISPATCHER_UPSTART "${CMAKE_CURRENT_BINARY_DIR}/url-dispatcher.conf" ) configure_file("url-dispatcher.conf.in" "${DISPATCHER_UPSTART}" @ONLY ) install( FILES "${DISPATCHER_UPSTART}" DESTINATION "/usr/share/upstart/sessions" ) ########################### # Upstart Update Jobs ########################### set(DISPATCHER_UPDATE_USER_UPSTART "${CMAKE_CURRENT_BINARY_DIR}/url-dispatcher-update-user.conf" ) configure_file("url-dispatcher-update-user.conf.in" "${DISPATCHER_UPDATE_USER_UPSTART}" @ONLY ) install( FILES "${DISPATCHER_UPDATE_USER_UPSTART}" DESTINATION "/usr/share/upstart/sessions" ) set(DISPATCHER_UPDATE_SYSTEM_UPSTART "${CMAKE_CURRENT_BINARY_DIR}/url-dispatcher-update-system.conf" ) configure_file("url-dispatcher-update-system.conf.in" "${DISPATCHER_UPDATE_SYSTEM_UPSTART}" @ONLY ) install( FILES "${DISPATCHER_UPDATE_SYSTEM_UPSTART}" DESTINATION "/usr/share/upstart/sessions" ) ########################### # Upstart Refresh Job ########################### set(DISPATCHER_REFRESH_UPSTART "${CMAKE_CURRENT_BINARY_DIR}/url-dispatcher-refresh.conf" ) configure_file("url-dispatcher-refresh.conf.in" "${DISPATCHER_REFRESH_UPSTART}" @ONLY ) install( FILES "${DISPATCHER_REFRESH_UPSTART}" DESTINATION "/usr/share/upstart/sessions" ) ########################### # QML ########################### install( FILES bad-url.qml DESTINATION ${datadir}/url-dispatcher ) ./data/url-dispatcher-refresh.conf.in0000644000004100000410000000043613066525443020025 0ustar www-datawww-datadescription "Ensure the URL dispatcher database is up-to-date, likely at session init" start on started url-dispatcher emits url-dispatcher-update-user url-dispatcher-update-system script initctl emit url-dispatcher-update-system initctl emit url-dispatcher-update-user end script ./data/bad-url.qml0000644000004100000410000000230413066525443014224 0ustar www-datawww-data/* * Copyright © 2015 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program 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 . */ import QtQuick 2.0 import Ubuntu.Components 1.0 import Ubuntu.Components.Popups 1.0 MainView { applicationName: "com.canonical.url-dispatcher.bad-url-prompt" automaticOrientation: true backgroundColor: "transparent" anchors.fill: parent Component { id: dialog Dialog { title: i18n.tr("Unrecognized Address") text: i18n.tr("Ubuntu can't open addresses of type “%1”.").arg(Qt.application.arguments[2].split(':')[0]) Button { text: i18n.tr("OK") color: UbuntuColors.orange onClicked: Qt.quit() } } } Component.onCompleted: PopupUtils.open(dialog) } ./data/url-dispatcher.conf.in0000644000004100000410000000074713066525443016376 0ustar www-datawww-datadescription "URL Dispatcher" author "Ted Gould " start on started unity8 stop on stopping unity8 respawn emits application-start emits untrusted-helper-type-end script ${SNAP}@pkglibexecdir@/url-dispatcher if [ $? -ne 0 ]; then retval = $? rm -rf "${XDG_CACHE_HOME:-$HOME/.cache}"/url-dispatcher/urls-1.db* start url-dispatcher-refresh exit $retval fi end script post-stop script initctl emit untrusted-helper-type-end HELPER_TYPE=url-overlay end script ./data/com.canonical.URLDispatcher.xml0000644000004100000410000000060713066525443020065 0ustar www-datawww-data ./data/url-dispatcher-update-system.conf.in0000644000004100000410000000074013066525443021171 0ustar www-datawww-datadescription "URL Dispatcher System Directory Watch" start on (file FILE=@datadir@/url-dispatcher/urls/*.url-dispatcher) or url-dispatcher-update-system task nice 19 pre-start script RUNNING=$(initctl status url-dispatcher | grep start/running 2> /dev/null) if [ "x${RUNNING}" = "x" ]; then echo "DEBUG: Started before URL Dispatcher, let it try to build the DB" sleep 5 fi end script exec ${SNAP}@pkglibexecdir@/update-directory "${SNAP}@datadir@/url-dispatcher/urls"