url-dispatcher-0.1+16.04.20151110/0000755000015300001610000000000012620400774016626 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/data/0000755000015300001610000000000012620400774017537 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/data/CMakeLists.txt0000644000015300001610000000373512620400412022274 0ustar pbuserpbgroup00000000000000 ########################### # 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" ) ########################### # Click Hook ########################### configure_file("url-dispatcher.urls.click-hook.in" "${CMAKE_SOURCE_DIR}/debian/url-dispatcher.urls.click-hook" @ONLY ) configure_file("url-dispatcher.url-overlay.click-hook.in" "${CMAKE_SOURCE_DIR}/debian/url-dispatcher.url-overlay.click-hook" @ONLY ) url-dispatcher-0.1+16.04.20151110/data/url-dispatcher.url-overlay.click-hook.in0000644000015300001610000000015112620400412027301 0ustar pbuserpbgroup00000000000000Pattern: ${home}/.cache/url-dispatcher/url-overlays/${id}.desktop User-Level: yes Hook-Name: url-overlay url-dispatcher-0.1+16.04.20151110/data/url-dispatcher.conf.in0000644000015300001610000000051512620400420023726 0ustar pbuserpbgroup00000000000000description "URL Dispatcher" author "Ted Gould " start on started dbus stop on stopping dbus respawn emits application-start script @pkglibexecdir@/url-dispatcher if [ $? -ne 0 ]; then retval = $? rm -rf ${HOME}/.cache/url-dispatcher/urls-1.db* start url-dispatcher-refresh exit $retval fi end script url-dispatcher-0.1+16.04.20151110/data/url-dispatcher-update-system.conf.in0000644000015300001610000000071212620400412026530 0ustar pbuserpbgroup00000000000000description "URL Dispatcher System Directory Watch" start on (file FILE=@datadir@/url-dispatcher/urls/*.url-dispatcher) or url-dispatcher-update-system task 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 @pkglibexecdir@/update-directory "@datadir@/url-dispatcher/urls" url-dispatcher-0.1+16.04.20151110/data/url-dispatcher-update-user.conf.in0000644000015300001610000000072712620400412026170 0ustar pbuserpbgroup00000000000000description "URL Dispatcher User Directory Watch" start on (file FILE=~/.config/url-dispatcher/urls/*.url-dispatcher) or url-dispatcher-update-user task 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 @pkglibexecdir@/update-directory "~/.config/url-dispatcher/urls/*.url-dispatcher" url-dispatcher-0.1+16.04.20151110/data/url-dispatcher.urls.click-hook.in0000644000015300001610000000026612620400412026014 0ustar pbuserpbgroup00000000000000Pattern: ${home}/.cache/url-dispatcher/click-urls/${id}.url-dispatcher Exec: @pkglibexecdir@/update-directory $HOME/.cache/url-dispatcher/click-urls/ User-Level: yes Hook-Name: urls url-dispatcher-0.1+16.04.20151110/data/com.canonical.URLDispatcher.xml0000644000015300001610000000060712620400412025425 0ustar pbuserpbgroup00000000000000 url-dispatcher-0.1+16.04.20151110/data/url-dispatcher-refresh.conf.in0000644000015300001610000000073312620400412025365 0ustar pbuserpbgroup00000000000000description "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 pre-start script # If we're starting with url-dispatcher let's let the rest of the # system have a chance to settle. if [ "$UPSTART_EVENTS" = "started" ] ; then sleep 60 fi end script script initctl emit url-dispatcher-update-system initctl emit url-dispatcher-update-user end script url-dispatcher-0.1+16.04.20151110/docs/0000755000015300001610000000000012620400774017556 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/docs/URL Dispatcher Architecture.svg0000644000015300001610000004511012620400412025401 0ustar pbuserpbgroup00000000000000 image/svg+xml URL DispatcherUser Service DBus URL DispatcherUpdate Tool SQLiteURLCache Upstart Click Click PackageInstall orRemoval Session InitRefresh Configurationfile change Readonly Readwrite Upstart AppLaunch url-dispatcher-0.1+16.04.20151110/CMakeLists.txt0000644000015300001610000000734212620400412021361 0ustar pbuserpbgroup00000000000000project(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() option (enable_tests "Build tests" ON) 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}) # Trick the H10enable_coverage script into enabling coverage by including the text below: # CMAKE_BUILD_TYPE coverage find_package(PkgConfig REQUIRED) include(GNUInstallDirs) include(CheckIncludeFile) include(CheckFunctionExists) include(Coverage) include(UseGlibGeneration) include(UseGdbusCodegen) 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-2>=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(CLICK REQUIRED click-0.4) include_directories(${CLICK_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) # testing & coverage if (${enable_tests}) set (GTEST_SOURCE_DIR /usr/src/gtest/src) set (GTEST_INCLUDE_DIR ${GTEST_SOURCE_DIR}/..) set (GTEST_LIBS -pthread) enable_testing () if (${enable_lcov}) # include(GCov) endif () add_subdirectory(tests) endif () url-dispatcher-0.1+16.04.20151110/service/0000755000015300001610000000000012620400774020266 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/service/recoverable-problem.h0000644000015300001610000000175712620400412024365 0ustar pbuserpbgroup00000000000000/* * 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 report_recoverable_problem (const gchar * signature, GPid report_pid, gboolean wait, const gchar * additional_properties[]); url-dispatcher-0.1+16.04.20151110/service/url-overlay.c0000644000015300001610000001101112620400412022672 0ustar pbuserpbgroup00000000000000/* * 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 #include "recoverable-problem.h" 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) { GError * error = NULL; 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; } /* Check click to find out where the files are */ ClickDB * db = click_db_new(); /* If TEST_CLICK_DB is unset, this reads the system database. */ click_db_read(db, g_getenv("TEST_CLICK_DB"), &error); if (error != NULL) { g_warning("Unable to read Click database: %s", error->message); g_error_free(error); g_free(package); return NULL; } /* If TEST_CLICK_USER is unset, this uses the current user name. */ ClickUser * user = click_user_new_for_user(db, g_getenv("TEST_CLICK_USER"), &error); if (error != NULL) { g_warning("Unable to read Click database: %s", error->message); g_error_free(error); g_free(package); g_object_unref(db); return NULL; } gchar * pkgdir = click_user_get_path(user, package, &error); g_object_unref(user); g_object_unref(db); g_free(package); if (error != NULL) { g_warning("Unable to get the Click package directory for %s: %s", package, error->message); g_error_free(error); return NULL; } return pkgdir; } int main (int argc, char * argv[]) { /* Build up our exec */ const gchar * appid = g_getenv("APP_ID"); if (appid == NULL) { 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 not there look to the user directory (click) */ if (exec == NULL) { gchar * userdir = g_build_filename(g_get_user_cache_dir(), "url-dispatcher", "url-overlays", NULL); exec = build_exec(appid, userdir); g_free(userdir); } if (exec == NULL) { const gchar * props[3] = { "AppID", appid, NULL }; 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; } } url-dispatcher-0.1+16.04.20151110/service/create-db-sql.h0000644000015300001610000000006212620400412023045 0ustar pbuserpbgroup00000000000000#pragma once extern const char * create_db_sql; url-dispatcher-0.1+16.04.20151110/service/overlay-tracker.cpp0000644000015300001610000000314012620400412024067 0ustar pbuserpbgroup00000000000000/* * 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; } url-dispatcher-0.1+16.04.20151110/service/overlay-tracker-mir.cpp0000644000015300001610000001133212620400412024656 0ustar pbuserpbgroup00000000000000/* * 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 * HELPER_TYPE = "url-overlay"; OverlayTrackerMir::OverlayTrackerMir () : thread([this] { /* Setup Helper Observer */ ubuntu_app_launch_observer_add_helper_stop(untrustedHelperStoppedStatic, HELPER_TYPE, this); }, [this] { /* Remove Helper Observer */ ubuntu_app_launch_observer_delete_helper_stop(untrustedHelperStoppedStatic, 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] { while (!ongoingSessions.empty()) { removeSession(std::get<2>(*ongoingSessions.begin()).get()); } return true; }); mir.reset(); } bool OverlayTrackerMir::addOverlay (const char * appid, unsigned long pid, const char * url) { std::string sappid(appid); std::string surl(url); return thread.executeOnThread([this, sappid, pid, surl] { g_debug("Setting up over lay for PID %d with '%s'", pid, sappid.c_str()); auto session = std::shared_ptr( mir_connection_create_prompt_session_sync(mir.get(), pid, sessionStateChangedStatic, this), [] (MirPromptSession * session) { if (session) mir_prompt_session_release_sync(session); }); if (!session) { g_critical("Unable to create trusted prompt session for %d with appid '%s'", pid, sappid.c_str()); return false; } std::array urls { surl.c_str(), nullptr }; auto instance = ubuntu_app_launch_start_session_helper(HELPER_TYPE, session.get(), sappid.c_str(), urls.data()); if (instance == nullptr) { g_critical("Unable to start helper for %d with appid '%s'", pid, sappid.c_str()); return false; } ongoingSessions.emplace(std::make_tuple(sappid, std::string(instance), session)); g_free(instance); return true; }); } void OverlayTrackerMir::sessionStateChangedStatic (MirPromptSession * session, MirPromptSessionState state, void * user_data) { reinterpret_cast(user_data)->sessionStateChanged(session, state); } void OverlayTrackerMir::removeSession (MirPromptSession * session) { for (auto it = ongoingSessions.begin(); it != ongoingSessions.end(); it++) { if (std::get<2>(*it).get() == session) { ubuntu_app_launch_stop_multiple_helper(HELPER_TYPE, std::get<0>(*it).c_str(), std::get<1>(*it).c_str()); ongoingSessions.erase(it); break; } } } void OverlayTrackerMir::sessionStateChanged (MirPromptSession * session, MirPromptSessionState state) { 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, session]() { removeSession(session); }); } void OverlayTrackerMir::untrustedHelperStoppedStatic (const gchar * appid, const gchar * instanceid, const gchar * helpertype, gpointer user_data) { reinterpret_cast(user_data)->untrustedHelperStopped(appid, instanceid, helpertype); } void OverlayTrackerMir::untrustedHelperStopped(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, 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.begin(); it != ongoingSessions.end(); it++) { if (std::get<0>(*it) == sappid && std::get<1>(*it) == sinstanceid) { ongoingSessions.erase(it); break; } } } url-dispatcher-0.1+16.04.20151110/service/glib-thread.h0000644000015300001610000000515512620400412022614 0ustar pbuserpbgroup00000000000000/* * 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); }; } url-dispatcher-0.1+16.04.20151110/service/CMakeLists.txt0000644000015300001610000000561612620400412023023 0ustar pbuserpbgroup00000000000000 include(UseConstantBuilder) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_definitions( -DOVERLAY_SYSTEM_DIRECTORY="${CMAKE_INSTALL_FULL_DATADIR}/url-dispatcher/url-overlays" ) ########################### # Generated Lib ########################### set(SERVICE_GENERATED_HEADERS service-iface.h ) set(SERVICE_GENERATED_SOURCES service-iface.c ) add_gdbus_codegen( OUTFILES SERVICE_GENERATED_SOURCES NAME service-iface PREFIX com.canonical.URLDispatcher. NAMESPACE ServiceIface SERVICE_XML ${CMAKE_CURRENT_SOURCE_DIR}/../data/com.canonical.URLDispatcher.xml ) add_library(service-generated STATIC ${SERVICE_GENERATED_SOURCES}) target_link_libraries(service-generated ${GLIB2_LIBRARIES} ${GOBJECT2_LIBRARIES} ${GIO2_LIBRARIES} ${JSONGLIB_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) target_link_libraries(dispatcher-lib url-db-lib service-generated -pthread ${GLIB2_LIBRARIES} ${GOBJECT2_LIBRARIES} ${GIO2_LIBRARIES} ${SQLITE_LIBRARIES} ${UBUNTU_APP_LAUNCH_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.c recoverable-problem.h recoverable-problem.c) set_target_properties(service-exec PROPERTIES OUTPUT_NAME "url-dispatcher") target_link_libraries(service-exec dispatcher-lib) ########################### # Update Directory ########################### add_executable(update-directory update-directory.c recoverable-problem.c) set_target_properties(update-directory PROPERTIES OUTPUT_NAME "update-directory") target_link_libraries(update-directory ${GIO2_LIBRARIES} ${JSONGLIB_LIBRARIES} url-db-lib) ########################### # URL Overlay Exec Tool ########################### add_executable(url-overlay-exec-tool url-overlay.c recoverable-problem.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} ${CLICK_LIBRARIES}) ########################### # Installation ########################### install( TARGETS service-exec update-directory RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_LIBEXECDIR}/url-dispatcher" ) install( TARGETS url-overlay-exec-tool RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_LIBEXECDIR}/ubuntu-app-launch/url-overlay" ) url-dispatcher-0.1+16.04.20151110/service/overlay-tracker-mir.h0000644000015300001610000000334512620400412024330 0ustar pbuserpbgroup00000000000000/* * 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 "glib-thread.h" #include "overlay-tracker-iface.h" class OverlayTrackerMir : public OverlayTrackerIface { private: GLib::ContextThread thread; std::shared_ptr mir; std::set>> ongoingSessions; public: OverlayTrackerMir (); ~OverlayTrackerMir (); bool addOverlay (const char * appid, unsigned long pid, const char * url) override; private: void removeSession (MirPromptSession * session); static void sessionStateChangedStatic (MirPromptSession * session, MirPromptSessionState state, void * user_data); void sessionStateChanged (MirPromptSession * session, MirPromptSessionState state); static void untrustedHelperStoppedStatic (const gchar * appid, const gchar * instanceid, const gchar * helpertype, gpointer user_data); void untrustedHelperStopped(const gchar * appid, const gchar * instanceid, const gchar * helpertype); }; url-dispatcher-0.1+16.04.20151110/service/overlay-tracker-iface.h0000644000015300001610000000157612620400412024614 0ustar pbuserpbgroup00000000000000/* * 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; }; url-dispatcher-0.1+16.04.20151110/service/dispatcher.c0000644000015300001610000003655012620400420022555 0ustar pbuserpbgroup00000000000000/** * 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 * */ #include #include #include #include "dispatcher.h" #include "service-iface.h" #include "recoverable-problem.h" #include "url-db.h" /* Globals */ static OverlayTracker * tracker = 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); const gchar * additional[3] = { "BadURL", badurl, NULL }; 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) { 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; } /* 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) { GError * error = NULL; /* TODO: Detect if a scope is what we need to overlay on */ 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' when processing URL '%s': %s", sender, url, error->message); g_error_free(error); return FALSE; } unsigned int pid = 0; g_variant_get_child(callret, 0, "u", &pid); g_variant_unref(callret); 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); } /* Check user dir (clicks) */ if (!found) { gchar * usrdir = g_build_filename(g_get_user_cache_dir(), "url-dispatcher", "url-overlays", desktopname, NULL); found = g_file_test(usrdir, G_FILE_TEST_EXISTS); g_free(usrdir); } 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) { retval = TRUE; } 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; } } 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: %s", error->message); 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) { tracker = intracker; 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; } url-dispatcher-0.1+16.04.20151110/service/recoverable-problem.c0000644000015300001610000000776112620400412024361 0ustar pbuserpbgroup00000000000000/* * 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 "recoverable-problem.h" #include #include #include /* Helpers to ensure we write nicely */ static void write_string (int fd, const gchar *string) { int res; do res = write (fd, string, strlen (string)); while (G_UNLIKELY (res == -1 && errno == EINTR)); } /* Make NULLs fast and fun! */ static void write_null (int fd) { int res; do res = write (fd, "", 1); while (G_UNLIKELY (res == -1 && errno == EINTR)); } /* Child watcher */ static gboolean apport_child_watch (GPid pid, gint status, gpointer user_data) { g_main_loop_quit((GMainLoop *)user_data); return FALSE; } static gboolean apport_child_timeout (gpointer user_data) { g_warning("Recoverable Error Reporter Timeout"); g_main_loop_quit((GMainLoop *)user_data); return FALSE; } /* Code to report an error */ void report_recoverable_problem (const gchar * signature, GPid report_pid, gboolean wait, const gchar * additional_properties[]) { /* Allow disabling for testing, we don't want to report bugs on our tests ;-) */ if (G_UNLIKELY(g_getenv("URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR") != NULL)) { return; } GError * error = NULL; gint error_stdin = 0; GPid pid = 0; gchar * pid_str = NULL; gchar ** argv = NULL; gchar * argv_nopid[2] = { "/usr/share/apport/recoverable_problem", NULL }; gchar * argv_pid[4] = { "/usr/share/apport/recoverable_problem", "-p", NULL, /* put pid_str when allocated here */ NULL }; argv = (gchar **)argv_nopid; if (report_pid != 0) { pid_str = g_strdup_printf("%d", report_pid); argv_pid[2] = pid_str; argv = (gchar**)argv_pid; } GSpawnFlags flags = G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL; if (wait) { flags |= G_SPAWN_DO_NOT_REAP_CHILD; } g_spawn_async_with_pipes(NULL, /* cwd */ argv, NULL, /* envp */ flags, NULL, NULL, /* child setup func */ &pid, &error_stdin, NULL, /* stdout */ NULL, /* stderr */ &error); if (error != NULL) { g_warning("Unable to report a recoverable error: %s", error->message); g_error_free(error); } gboolean first = TRUE; if (error_stdin != 0 && signature != NULL) { write_string(error_stdin, "DuplicateSignature"); write_null(error_stdin); write_string(error_stdin, signature); first = FALSE; } if (error_stdin != 0 && additional_properties != NULL) { gint i; for (i = 0; additional_properties[i] != NULL; i++) { if (!first) { write_null(error_stdin); } else { first = FALSE; } write_string(error_stdin, additional_properties[i]); } } if (error_stdin != 0) { close(error_stdin); } if (wait && pid != 0) { GSource * child_source, * timeout_source; GMainContext * context = g_main_context_new(); GMainLoop * loop = g_main_loop_new(context, FALSE); child_source = g_child_watch_source_new(pid); g_source_attach(child_source, context); g_source_set_callback(child_source, (GSourceFunc)apport_child_watch, loop, NULL); timeout_source = g_timeout_source_new_seconds(5); g_source_attach(timeout_source, context); g_source_set_callback(timeout_source, apport_child_timeout, loop, NULL); g_main_loop_run(loop); g_source_destroy(timeout_source); g_source_destroy(child_source); g_main_loop_unref(loop); g_main_context_unref(context); g_spawn_close_pid(pid); } g_free(pid_str); return; } url-dispatcher-0.1+16.04.20151110/service/overlay-tracker.h0000644000015300001610000000175212620400412023543 0ustar pbuserpbgroup00000000000000/* * 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); url-dispatcher-0.1+16.04.20151110/service/glib-thread.cpp0000644000015300001610000001244712620400412023151 0ustar pbuserpbgroup00000000000000/* * 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 url-dispatcher-0.1+16.04.20151110/service/update-directory.c0000644000015300001610000001550612620400412023712 0ustar pbuserpbgroup00000000000000/** * 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 "url-db.h" #include "recoverable-problem.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; 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; 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; 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; 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; } url-dispatcher-0.1+16.04.20151110/service/dispatcher.h0000644000015300001610000000261012620400412022551 0ustar pbuserpbgroup00000000000000/** * 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" G_BEGIN_DECLS gboolean dispatcher_init (GMainLoop * mainloop, OverlayTracker * tracker); 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 */ url-dispatcher-0.1+16.04.20151110/service/url-db.h0000644000015300001610000000423112620400412021611 0ustar pbuserpbgroup00000000000000/** * 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 */ url-dispatcher-0.1+16.04.20151110/service/create-db.sql0000644000015300001610000000050212620400412022617 0ustar pbuserpbgroup00000000000000pragma 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; url-dispatcher-0.1+16.04.20151110/service/url-db.c0000644000015300001610000002335412620400412021613 0ustar pbuserpbgroup00000000000000/** * 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; } url-dispatcher-0.1+16.04.20151110/service/service.c0000644000015300001610000000265512620400420022066 0ustar pbuserpbgroup00000000000000/** * 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 * */ #include #include #include "dispatcher.h" GMainLoop * mainloop = NULL; static gboolean sig_term (gpointer user_data) { g_debug("SIGTERM"); g_main_loop_quit((GMainLoop *)user_data); return G_SOURCE_CONTINUE; } /* Where it all begins */ int main (int argc, char * argv[]) { mainloop = g_main_loop_new(NULL, FALSE); guint term_source = g_unix_signal_add(SIGTERM, sig_term, mainloop); OverlayTracker * tracker = overlay_tracker_new(); if (!dispatcher_init(mainloop, tracker)) { return -1; } /* Run Main */ g_main_loop_run(mainloop); /* Clean up globals */ dispatcher_shutdown(); overlay_tracker_delete(tracker); g_source_remove(term_source); g_main_loop_unref(mainloop); return 0; } url-dispatcher-0.1+16.04.20151110/cmake/0000755000015300001610000000000012620400774017706 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/cmake/UseGdbusCodegen.cmake0000644000015300001610000000214612620400412023706 0ustar pbuserpbgroup00000000000000cmake_minimum_required(VERSION 2.6) if(POLICY CMP0011) cmake_policy(SET CMP0011 NEW) endif(POLICY CMP0011) find_program(GDBUS_CODEGEN NAMES gdbus-codegen DOC "gdbus-codegen executable") if(NOT GDBUS_CODEGEN) message(FATAL_ERROR "Excutable gdbus-codegen not found") endif() function(add_gdbus_codegen) set(_one_value OUTFILES NAME PREFIX NAMESPACE SERVICE_XML) set(_multi_value DEPENDS) cmake_parse_arguments (arg "" "${_one_value}" "${_multi_value}" ${ARGN}) if(arg_PREFIX) set(PREFIX --interface-prefix ${arg_PREFIX}) endif() if(arg_NAMESPACE) set(NAMESPACE --c-namespace ${arg_NAMESPACE}) endif() add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${arg_NAME}.h" "${CMAKE_CURRENT_BINARY_DIR}/${arg_NAME}.c" COMMAND "${GDBUS_CODEGEN}" --generate-c-code "${arg_NAME}" ${PREFIX} ${NAMESPACE} "${arg_SERVICE_XML}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${arg_DEPENDS} "${arg_SERVICE_XML}" ) set(${arg_OUTFILES} ${${arg_OUTFILES}} "${CMAKE_CURRENT_BINARY_DIR}/${arg_NAME}.c" PARENT_SCOPE) endfunction(add_gdbus_codegen) url-dispatcher-0.1+16.04.20151110/cmake/UseGlibGeneration.cmake0000644000015300001610000000544512620400412024253 0ustar pbuserpbgroup00000000000000cmake_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) url-dispatcher-0.1+16.04.20151110/cmake/UseConstantBuilder.cmake0000644000015300001610000000123212620400412024450 0ustar pbuserpbgroup00000000000000cmake_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) url-dispatcher-0.1+16.04.20151110/cmake/UseGSettings.cmake0000644000015300001610000000371512620400412023267 0ustar pbuserpbgroup00000000000000# GSettings.cmake, CMake macros written for Marlin, feel free to re-use them. option (GSETTINGS_LOCALINSTALL "Install GSettings Schemas locally instead of to the GLib prefix" ${LOCAL_INSTALL}) option (GSETTINGS_COMPILE "Compile GSettings Schemas after installation" ${GSETTINGS_LOCALINSTALL}) if(GSETTINGS_LOCALINSTALL) message(STATUS "GSettings schemas will be installed locally.") endif() if(GSETTINGS_COMPILE) message(STATUS "GSettings shemas will be compiled.") endif() macro(add_schema SCHEMA_NAME) set(PKG_CONFIG_EXECUTABLE pkg-config) # Have an option to not install the schema into where GLib is if (GSETTINGS_LOCALINSTALL) SET (GSETTINGS_DIR "${CMAKE_INSTALL_PREFIX}/share/glib-2.0/schemas/") else (GSETTINGS_LOCALINSTALL) execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} glib-2.0 --variable prefix OUTPUT_VARIABLE _glib_prefix OUTPUT_STRIP_TRAILING_WHITESPACE) SET (GSETTINGS_DIR "${_glib_prefix}/share/glib-2.0/schemas/") endif (GSETTINGS_LOCALINSTALL) # Run the validator and error if it fails execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable glib_compile_schemas OUTPUT_VARIABLE _glib_comple_schemas OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process (COMMAND ${_glib_comple_schemas} --dry-run --schema-file=${CMAKE_CURRENT_SOURCE_DIR}/${SCHEMA_NAME} ERROR_VARIABLE _schemas_invalid OUTPUT_STRIP_TRAILING_WHITESPACE) if (_schemas_invalid) message (SEND_ERROR "Schema validation error: ${_schemas_invalid}") endif (_schemas_invalid) # Actually install and recomple schemas message (STATUS "GSettings schemas will be installed into ${GSETTINGS_DIR}") install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/${SCHEMA_NAME} DESTINATION ${GSETTINGS_DIR} OPTIONAL) if (GSETTINGS_COMPILE) install (CODE "message (STATUS \"Compiling GSettings schemas\")") install (CODE "execute_process (COMMAND ${_glib_comple_schemas} ${GSETTINGS_DIR})") endif () endmacro() url-dispatcher-0.1+16.04.20151110/cmake/Coverage.cmake0000644000015300001610000000417012620400412022432 0ustar pbuserpbgroup00000000000000if (CMAKE_BUILD_TYPE MATCHES coverage) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} --coverage") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") find_program(GCOVR_EXECUTABLE gcovr HINTS ${GCOVR_ROOT} "${GCOVR_ROOT}/bin") if (NOT GCOVR_EXECUTABLE) message(STATUS "Gcovr binary was not found, can not generate XML coverage info.") else () message(STATUS "Gcovr found, can generate XML coverage info.") add_custom_target (coverage-xml WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND "${GCOVR_EXECUTABLE}" --exclude="test.*" --exclude="obj.*" -x -r "${CMAKE_SOURCE_DIR}" --object-directory=${CMAKE_BINARY_DIR} -o coverage.xml) endif() find_program(LCOV_EXECUTABLE lcov HINTS ${LCOV_ROOT} "${GCOVR_ROOT}/bin") find_program(GENHTML_EXECUTABLE genhtml HINTS ${GENHTML_ROOT}) if (NOT LCOV_EXECUTABLE) message(STATUS "Lcov binary was not found, can not generate HTML coverage info.") else () if(NOT GENHTML_EXECUTABLE) message(STATUS "Genthml binary not found, can not generate HTML coverage info.") else() message(STATUS "Lcov and genhtml found, can generate HTML coverage info.") add_custom_target (coverage-html WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND "${LCOV_EXECUTABLE}" --directory ${CMAKE_BINARY_DIR} --capture --output-file coverage.info --no-checksum COMMAND "${LCOV_EXECUTABLE}" --remove "${CMAKE_BINARY_DIR}/coverage.info" '${CMAKE_SOURCE_DIR}/tests/*' --output-file "${CMAKE_BINARY_DIR}/coverage.info" COMMAND "${LCOV_EXECUTABLE}" --remove "${CMAKE_BINARY_DIR}/coverage.info" '/usr/*' --output-file "${CMAKE_BINARY_DIR}/coverage.info" COMMAND "${LCOV_EXECUTABLE}" --remove "${CMAKE_BINARY_DIR}/coverage.info" '${CMAKE_BINARY_DIR}/*' --output-file "${CMAKE_BINARY_DIR}/coverage.info" COMMAND "${GENHTML_EXECUTABLE}" --prefix ${CMAKE_BINARY_DIR} --output-directory coveragereport --title "Code Coverage" --legend --show-details coverage.info ) endif() endif() endif() url-dispatcher-0.1+16.04.20151110/cmake/ConstantBuilderTemplates.cmake0000644000015300001610000000073212620400412025656 0ustar pbuserpbgroup00000000000000 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() url-dispatcher-0.1+16.04.20151110/liburl-dispatcher/0000755000015300001610000000000012620400774022243 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/liburl-dispatcher/CMakeLists.txt0000644000015300001610000000362312620400412024774 0ustar pbuserpbgroup00000000000000 ########################### # Version Information ########################### set(API_VERSION 1) set(ABI_VERSION 1) ########################### # Generated Lib ########################### set(GENERATED_HEADERS service-iface.h ) set(GENERATED_SOURCES service-iface.c ) add_gdbus_codegen( OUTFILES GENERATED_SOURCES NAME service-iface PREFIX com.canonical.URLDispatcher. NAMESPACE ServiceIface SERVICE_XML ${CMAKE_CURRENT_SOURCE_DIR}/../data/com.canonical.URLDispatcher.xml ) add_library(generated STATIC ${GENERATED_SOURCES}) target_link_libraries(generated ${GLIB2_LIBRARIES} ${GOBJECT2_LIBRARIES} ${GIO2_LIBRARIES} ) ########################### # 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 generated ${GLIB2_LIBRARIES} ${GOBJECT2_LIBRARIES} -Wl,--no-undefined ) ########################### # 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} ) url-dispatcher-0.1+16.04.20151110/liburl-dispatcher/url-dispatcher.h0000644000015300001610000000564412620400412025340 0ustar pbuserpbgroup00000000000000/** * 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 */ url-dispatcher-0.1+16.04.20151110/liburl-dispatcher/url-dispatcher.c0000644000015300001610000001060412620400412025323 0ustar pbuserpbgroup00000000000000/** * 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; } url-dispatcher-0.1+16.04.20151110/liburl-dispatcher/url-dispatcher.pc.in0000644000015300001610000000040012620400412026101 0ustar pbuserpbgroup00000000000000libdir=@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@ url-dispatcher-0.1+16.04.20151110/COPYING0000644000015300001610000001674312620400412017661 0ustar pbuserpbgroup00000000000000 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. url-dispatcher-0.1+16.04.20151110/README0000644000015300001610000000064612620400412017501 0ustar pbuserpbgroup00000000000000Introduction ------------ 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. url-dispatcher-0.1+16.04.20151110/tools/0000755000015300001610000000000012620400774017766 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tools/url-dispatcher-dump0000755000015300001610000000032212620400412023567 0ustar pbuserpbgroup00000000000000#!/bin/sh echo .quit | sqlite3 -batch -csv -cmd "select urls.protocol, urls.domainsuffix, configfiles.name from urls, configfiles where urls.sourcefile = configfiles.rowid;" ~/.cache/url-dispatcher/urls-1.db url-dispatcher-0.1+16.04.20151110/tools/CMakeLists.txt0000644000015300001610000000062512620400412022516 0ustar pbuserpbgroup00000000000000 ########################### # URL Dispatcher ########################### include_directories(${CMAKE_SOURCE_DIR}/liburl-dispatcher) add_executable(url-dispatcher url-dispatcher.c) target_link_libraries(url-dispatcher dispatcher ) install( TARGETS url-dispatcher RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) install( PROGRAMS url-dispatcher-dump DESTINATION "${CMAKE_INSTALL_BINDIR}" ) url-dispatcher-0.1+16.04.20151110/tools/url-dispatcher.c0000644000015300001610000000231112620400412023042 0ustar pbuserpbgroup00000000000000/** * 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" static gboolean global_success = FALSE; static void callback (const gchar * url, gboolean success, gpointer user_data) { global_success = success; g_main_loop_quit((GMainLoop *)user_data); return; } int main (int argc, char * argv[]) { if (argc != 2) { g_printerr("Usage: %s \n", argv[0]); return 1; } GMainLoop * mainloop = g_main_loop_new(NULL, FALSE); url_dispatch_send(argv[1], callback, mainloop); g_main_loop_run(mainloop); if (global_success) { return 0; } else { return 1; } } url-dispatcher-0.1+16.04.20151110/MERGE-REVIEW0000644000015300001610000000102012620400412020365 0ustar pbuserpbgroup00000000000000 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 url-dispatcher-0.1+16.04.20151110/tests/0000755000015300001610000000000012620400774017770 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/ubuntu-app-launch-mock.h0000644000015300001610000000313212620400412024424 0ustar pbuserpbgroup00000000000000/** * 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_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 */ url-dispatcher-0.1+16.04.20151110/tests/directory-update-test.cc0000644000015300001610000002644112620400412024534 0ustar pbuserpbgroup00000000000000/** * 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); } url-dispatcher-0.1+16.04.20151110/tests/xdg-data/0000755000015300001610000000000012620400774021461 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/xdg-data/applications/0000755000015300001610000000000012620400774024147 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/xdg-data/applications/foo-bar.desktop0000644000015300001610000000007312620400412027054 0ustar pbuserpbgroup00000000000000[Desktop Entry] Name=Foo Bar Exec=foo-bar Type=Application url-dispatcher-0.1+16.04.20151110/tests/overlay-tracker-test.cpp0000644000015300001610000001040412620400412024547 0ustar pbuserpbgroup00000000000000/** * 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); } } TEST_F(OverlayTrackerTest, UALSignalStop) { OverlayTrackerMir tracker; /* Call with the overlay before it is set */ ubuntu_app_launch_mock_observer_helper_stop_func("app-id", "instance", "url-overlay", ubuntu_app_launch_mock_observer_helper_stop_user_data); EXPECT_TRUE(tracker.addOverlay("app-id", 5, "http://no-name-yet.com")); mir_mock_last_released_session = nullptr; ubuntu_app_launch_mock_observer_helper_stop_func("app-id", "instance", "url-overlay", ubuntu_app_launch_mock_observer_helper_stop_user_data); EXPECT_NE(nullptr, mir_mock_last_released_session); } TEST_F(OverlayTrackerTest, MirSignalStop) { OverlayTrackerMir tracker; EXPECT_TRUE(tracker.addOverlay("app-id", 5, "http://no-name-yet.com")); /* 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); pause(100); 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); } url-dispatcher-0.1+16.04.20151110/tests/recoverable-problem-mock.c0000644000015300001610000000161312620400412025000 0ustar pbuserpbgroup00000000000000/* * 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 #include "recoverable-problem.h" void report_recoverable_problem (const gchar * signature, GPid report_pid, gboolean wait, const gchar * additional_properties[]) { return; } url-dispatcher-0.1+16.04.20151110/tests/service-test.cc0000644000015300001610000002205512620400412022705 0ustar pbuserpbgroup00000000000000/** * 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" class ServiceTest : public ::testing::Test { protected: DbusTestService * service = nullptr; DbusTestDbusMock * mock = nullptr; DbusTestDbusMock * dashmock = nullptr; DbusTestDbusMockObject * obj = nullptr; DbusTestDbusMockObject * jobobj = nullptr; DbusTestProcess * dispatcher = nullptr; GDBusConnection * bus = nullptr; virtual void SetUp() { g_setenv("UBUNTU_APP_LAUNCH_USE_SESSION", "1", TRUE); g_setenv("URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR", "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)); /* Upstart Mock */ mock = dbus_test_dbus_mock_new("com.ubuntu.Upstart"); obj = dbus_test_dbus_mock_get_object(mock, "/com/ubuntu/Upstart", "com.ubuntu.Upstart0_6", nullptr); dbus_test_dbus_mock_object_add_method(mock, obj, "GetJobByName", G_VARIANT_TYPE_STRING, G_VARIANT_TYPE_OBJECT_PATH, /* out */ "ret = dbus.ObjectPath('/job')", /* python */ nullptr); /* error */ jobobj = dbus_test_dbus_mock_get_object(mock, "/job", "com.ubuntu.Upstart0_6.Job", nullptr); dbus_test_dbus_mock_object_add_method(mock, jobobj, "Start", G_VARIANT_TYPE("(asb)"), G_VARIANT_TYPE_OBJECT_PATH, /* out */ "ret = dbus.ObjectPath('/instance')", /* python */ nullptr); /* error */ dbus_test_task_set_name(DBUS_TEST_TASK(mock), "Upstart"); dbus_test_service_add_task(service, DBUS_TEST_TASK(mock)); /* 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() { g_clear_object(&dispatcher); g_clear_object(&mock); 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", "scope", 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); guint callslen = 0; dbus_test_dbus_mock_object_get_method_calls(mock, jobobj, "Start", &callslen, nullptr); ASSERT_EQ(callslen, 0); } 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); guint callslen = 0; const DbusTestDbusMockCall * calls = dbus_test_dbus_mock_object_get_method_calls(mock, jobobj, "Start", &callslen, nullptr); ASSERT_EQ(callslen, 1); /* Making sure the APP_ID is here. We're not testing more to make it so the tests break less, that should be tested in Upstart App Launch, we don't need to retest */ GVariant * env = g_variant_get_child_value(calls->params, 0); GVariantIter iter; bool found_appid = false; g_variant_iter_init(&iter, env); gchar * var = nullptr; while (g_variant_iter_loop(&iter, "s", &var)) { if (g_strcmp0(var, "APP_ID=foo-bar") == 0) { ASSERT_FALSE(found_appid); found_appid = true; } } g_variant_unref(env); ASSERT_TRUE(found_appid); } TEST_F(ServiceTest, TestURLTest) { /* Simple */ const char * testurls[] = { "application:///foo-bar.desktop", nullptr }; gchar ** appids = url_dispatch_url_appid(testurls); ASSERT_EQ(1, 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(2, 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(0, 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("scope://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; const DbusTestDbusMockCall * calls = nullptr; calls = dbus_test_dbus_mock_object_get_method_calls(mock, jobobj, "Start", &callslen, nullptr); EXPECT_NE(calls, nullptr); EXPECT_EQ(0, callslen); callslen = 0; calls = dbus_test_dbus_mock_object_get_method_calls(dashmock, fdoobj, "Open", &callslen, nullptr); EXPECT_EQ(1, callslen); EXPECT_TRUE(g_variant_equal(calls[0].params, g_variant_new_parsed("(['scope://foo-bar'], @a{sv} {})"))); EXPECT_EQ(1, focus_count); g_dbus_connection_signal_unsubscribe(bus, focus_signal); g_clear_object(&bus); } url-dispatcher-0.1+16.04.20151110/tests/click-db/0000755000015300001610000000000012620400774021440 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-db/test.conf.in0000644000015300001610000000007612620400412023663 0ustar pbuserpbgroup00000000000000[Click Database] root = @CMAKE_CURRENT_SOURCE_DIR@/click-data url-dispatcher-0.1+16.04.20151110/tests/CMakeLists.txt0000644000015300001610000000735412620400412022526 0ustar pbuserpbgroup00000000000000set(APP_ID_TEST_URI 1) configure_file(test-config.h.in test-config.h) include_directories(${CMAKE_CURRENT_BINARY_DIR}) ########################### # Click Database ########################### configure_file ("click-db/test.conf.in" "${CMAKE_CURRENT_BINARY_DIR}/click-db/test.conf" @ONLY) set_directory_properties (PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_CURRENT_BINARY_DIR}/click-db/test.conf") ########################### # Google Test ########################### include_directories(${GTEST_INCLUDE_DIR}) add_library (gtest STATIC ${GTEST_SOURCE_DIR}/gtest-all.cc ${GTEST_SOURCE_DIR}/gtest_main.cc) ########################### # Mock Lib ########################### add_library(mock-lib STATIC 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} ${GTEST_LIBS}) add_test (dispatcher-test dispatcher-test) ########################### # App ID URL test ########################### include_directories("${CMAKE_BINARY_DIR}/service") add_executable (app-id-test app-id-test.cc) target_link_libraries (app-id-test dispatcher-lib mock-lib gtest ${UBUNTU_APP_LAUNCH_LIBRARIES} ${GTEST_LIBS}) add_test (app-id-test app-id-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} ${GTEST_LIBS}) add_test (lib-test lib-test) ########################### # service test ########################### add_executable (service-test service-test.cc) target_link_libraries (service-test url-db-lib dispatcher gtest ${DBUSTEST_LIBRARIES} ${UBUNTU_APP_LAUNCH_LIBRARIES} ${GTEST_LIBS}) 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 ${GTEST_LIBS}) 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 ${GTEST_LIBS}) add_test (url-db-test url-db-test) add_subdirectory(url_dispatcher_testability) ########################### # exec tool test ########################### add_executable (exec-tool-test exec-tool-test.cc) target_link_libraries (exec-tool-test gtest ${GTEST_LIBS} ${GIO2_LIBRARIES} ${DBUSTEST_LIBRARIES}) add_test (exec-tool-test exec-tool-test) ########################### # 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 ${GTEST_LIBS} ${GIO2_LIBRARIES} ${DBUSTEST_LIBRARIES}) add_test (overlay-tracker-test overlay-tracker-test) url-dispatcher-0.1+16.04.20151110/tests/lib-test-no-main.c0000644000015300001610000000152312620400412023201 0ustar pbuserpbgroup00000000000000/** * 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; } url-dispatcher-0.1+16.04.20151110/tests/manual0000644000015300001610000000311412620400412021154 0ustar pbuserpbgroup00000000000000Test-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
url-dispatcher-0.1+16.04.20151110/tests/url_dispatcher_testability/0000755000015300001610000000000012620400774025415 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/url_dispatcher_testability/CMakeLists.txt0000644000015300001610000000122212620400412030137 0ustar pbuserpbgroup00000000000000execute_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}/..) url-dispatcher-0.1+16.04.20151110/tests/url_dispatcher_testability/fixture_setup.py0000644000015300001610000000222112620400412030657 0ustar pbuserpbgroup00000000000000# -*- 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() url-dispatcher-0.1+16.04.20151110/tests/url_dispatcher_testability/test_fake_dispatcher.py.in0000644000015300001610000000264212620400412032540 0ustar pbuserpbgroup00000000000000# -*- 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@" call([bin_dir + '/tools/url-dispatcher', 'test://testurl']) try: last_url = self.dispatcher.get_last_dispatch_url_call_parameter() except fake_dispatcher.FakeDispatcherException: last_url = None self.assertEqual(last_url, 'test://testurl') url-dispatcher-0.1+16.04.20151110/tests/url_dispatcher_testability/fake_dispatcher.py0000644000015300001610000000456112620400412031076 0ustar pbuserpbgroup00000000000000# -*- 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.""" # Stop the real url-dispatcher. subprocess.call(['initctl', 'stop', 'url-dispatcher']) 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] url-dispatcher-0.1+16.04.20151110/tests/url_dispatcher_testability/__init__.py0000644000015300001610000000000012620400412027501 0ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/test-config.h.in0000644000015300001610000000163112620400412022756 0ustar pbuserpbgroup00000000000000#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/exec-tool" #define MIR_MOCK_PATH "@CMAKE_CURRENT_BINARY_DIR@/libmir-mock.so" #define CLICK_DATA_DIR "@CMAKE_CURRENT_SOURCE_DIR@/click-data" url-dispatcher-0.1+16.04.20151110/tests/overlay-tracker-mock.h0000644000015300001610000000204712620400412024172 0ustar pbuserpbgroup00000000000000/** * 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; 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; } }; url-dispatcher-0.1+16.04.20151110/tests/mir-mock.cpp0000644000015300001610000000647612620400412022214 0ustar pbuserpbgroup00000000000000/** * 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, mir_client_fd_callback 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; } url-dispatcher-0.1+16.04.20151110/tests/dispatcher-test.cc0000644000015300001610000002250612620400412023374 0ustar pbuserpbgroup00000000000000/** * 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 "dispatcher.h" #include "ubuntu-app-launch-mock.h" #include "overlay-tracker-mock.h" #include "url-db.h" class DispatcherTest : public ::testing::Test { private: GTestDBus * testbus = nullptr; GMainLoop * mainloop = nullptr; gchar * cachedir = nullptr; protected: OverlayTrackerMock tracker; GDBusConnection * session = nullptr; virtual void SetUp() { g_setenv("TEST_CLICK_DB", "click-db", TRUE); g_setenv("TEST_CLICK_USER", "test-user", TRUE); 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"); 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)); 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(); /* 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, 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; } 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])); return; } url-dispatcher-0.1+16.04.20151110/tests/exec-tool-test.cc0000644000015300001610000000700712620400412023144 0ustar pbuserpbgroup00000000000000/** * Copyright © 2013-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 "test-config.h" #include #include #include class ExecToolTest : public ::testing::Test { protected: DbusTestService * service = nullptr; DbusTestDbusMock * mock = nullptr; DbusTestDbusMockObject * obj = nullptr; GDBusConnection * bus = nullptr; virtual void SetUp() { g_setenv("URL_DISPATCHER_DISABLE_RECOVERABLE_ERROR", "1", TRUE); g_setenv("URL_DISPATCHER_OVERLAY_DIR", OVERLAY_TEST_DIR, TRUE); g_setenv("TEST_CLICK_DB", "click-db", TRUE); g_setenv("TEST_CLICK_USER", "test-user", TRUE); service = dbus_test_service_new(nullptr); /* Upstart Mock */ mock = dbus_test_dbus_mock_new("com.ubuntu.Upstart"); obj = dbus_test_dbus_mock_get_object(mock, "/com/ubuntu/Upstart", "com.ubuntu.Upstart0_6", nullptr); dbus_test_dbus_mock_object_add_method(mock, obj, "SetEnv", G_VARIANT_TYPE("(assb)"), NULL, "", NULL); dbus_test_task_set_name(DBUS_TEST_TASK(mock), "Upstart"); dbus_test_service_add_task(service, DBUS_TEST_TASK(mock)); /* 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() { /* dbustest should probably do this, not sure */ g_clear_object(&mock); g_clear_object(&service); g_object_unref(bus); unsigned int cleartry = 0; while (bus != nullptr && cleartry < 100) { pause(100); cleartry++; } 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(ExecToolTest, SetOverlay) { g_unsetenv("APP_ID"); gint retval = 0; EXPECT_TRUE(g_spawn_command_line_sync(EXEC_TOOL, nullptr, nullptr, &retval, nullptr)); EXPECT_NE(0, retval); g_setenv("APP_ID", "com.test.good_application_1.2.3", TRUE); g_setenv("UPSTART_JOB", "fubar", TRUE); EXPECT_TRUE(g_spawn_command_line_sync(EXEC_TOOL, nullptr, nullptr, &retval, nullptr)); EXPECT_EQ(0, retval); guint len = 0; const DbusTestDbusMockCall * calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "SetEnv", &len, NULL); ASSERT_NE(nullptr, calls); ASSERT_EQ(2, len); GVariant * appexecenv = g_variant_get_child_value(calls[0].params, 1); EXPECT_STREQ("APP_EXEC=foobar", g_variant_get_string(appexecenv, nullptr)); g_variant_unref(appexecenv); GVariant * appdirenv = g_variant_get_child_value(calls[1].params, 1); EXPECT_STREQ("APP_DIR=" CLICK_DATA_DIR "/.click/users/test-user/com.test.good", g_variant_get_string(appdirenv, nullptr)); g_variant_unref(appdirenv); } url-dispatcher-0.1+16.04.20151110/tests/test-urls-intent/0000755000015300001610000000000012620400774023231 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/test-urls-intent/intent-mixed.url-dispatcher0000644000015300001610000000025112620400412030471 0ustar pbuserpbgroup00000000000000[ { "protocol": "intent", "domain-suffix": "intent.mixed" }, { "protocol": "intent" }, { "protocol": "intent", "domain-suffix": "intent.mixed.again" } ] url-dispatcher-0.1+16.04.20151110/tests/test-urls-intent/intent-single.url-dispatcher0000644000015300001610000000010512620400412030642 0ustar pbuserpbgroup00000000000000[ { "protocol": "intent", "domain-suffix": "intent.single" } ] url-dispatcher-0.1+16.04.20151110/tests/test-urls-intent/intent-no-good.url-dispatcher0000644000015300001610000000007712620400412030733 0ustar pbuserpbgroup00000000000000[ { "protocol": "intent" }, { "protocol": "intent" } ] url-dispatcher-0.1+16.04.20151110/tests/mir-mock.h0000644000015300001610000000241212620400412021643 0ustar pbuserpbgroup00000000000000/** * 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 url-dispatcher-0.1+16.04.20151110/tests/test-urls-simple/0000755000015300001610000000000012620400774023221 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/test-urls-simple/single-good.url-dispatcher0000644000015300001610000000010012620400412030254 0ustar pbuserpbgroup00000000000000[ { "protocol": "http", "domain-suffix": "ubuntu.com" } ] url-dispatcher-0.1+16.04.20151110/tests/overlay-dir/0000755000015300001610000000000012620400774022225 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/overlay-dir/com.test.good_application_1.2.3.desktop0000644000015300001610000000003412620400412031371 0ustar pbuserpbgroup00000000000000[Desktop Entry] Exec=foobar url-dispatcher-0.1+16.04.20151110/tests/app-id-test.cc0000644000015300001610000001540412620400412022417 0ustar pbuserpbgroup00000000000000/** * 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 "dispatcher.h" #include "ubuntu-app-launch-mock.h" #include "overlay-tracker-mock.h" class AppIdTest : public ::testing::Test { private: GTestDBus * testbus = nullptr; GMainLoop * mainloop = nullptr; gchar * cachedir = nullptr; OverlayTrackerMock tracker; protected: virtual void SetUp() { g_setenv("TEST_CLICK_DB", "click-db", TRUE); g_setenv("TEST_CLICK_USER", "test-user", TRUE); cachedir = g_build_filename(CMAKE_BINARY_DIR, "app-id-test-cache", nullptr); g_setenv("URL_DISPATCHER_CACHE_DIR", cachedir, TRUE); testbus = g_test_dbus_new(G_TEST_DBUS_NONE); g_test_dbus_up(testbus); mainloop = g_main_loop_new(nullptr, FALSE); dispatcher_init(mainloop, reinterpret_cast(&tracker)); 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(); 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(AppIdTest, BaseUrl) { gchar * out_appid = nullptr; const gchar * out_url = nullptr; /* Good sanity check */ dispatcher_url_to_appid("appid://com.test.good/app1/1.2.3", &out_appid, &out_url); ASSERT_STREQ("com.test.good_app1_1.2.3", out_appid); EXPECT_EQ(nullptr, out_url); dispatcher_send_to_app(out_appid, out_url); EXPECT_STREQ("com.test.good_app1_1.2.3", 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; /* No version at all */ dispatcher_url_to_appid("appid://com.test.good/app1", &out_appid, &out_url); EXPECT_EQ(nullptr, out_appid); EXPECT_EQ(nullptr, out_url); ubuntu_app_launch_mock_clear_last_app_id(); } TEST_F(AppIdTest, WildcardUrl) { gchar * out_appid = nullptr; const gchar * out_url = nullptr; /* Version wildcard */ dispatcher_url_to_appid("appid://com.test.good/app1/current-user-version", &out_appid, &out_url); ASSERT_STREQ("com.test.good_app1_1.2.3", out_appid); EXPECT_EQ(nullptr, out_url); dispatcher_send_to_app(out_appid, out_url); EXPECT_STREQ("com.test.good_app1_1.2.3", 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; /* First app */ dispatcher_url_to_appid("appid://com.test.good/first-listed-app/current-user-version", &out_appid, &out_url); ASSERT_STREQ("com.test.good_app1_1.2.3", out_appid); EXPECT_EQ(nullptr, out_url); dispatcher_send_to_app(out_appid, out_url); EXPECT_STREQ("com.test.good_app1_1.2.3", 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; /* Last app */ dispatcher_url_to_appid("appid://com.test.good/last-listed-app/current-user-version", &out_appid, &out_url); ASSERT_STREQ("com.test.good_app1_1.2.3", out_appid); EXPECT_EQ(nullptr, out_url); dispatcher_send_to_app(out_appid, out_url); EXPECT_STREQ("com.test.good_app1_1.2.3", 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; /* Only app */ dispatcher_url_to_appid("appid://com.test.good/only-listed-app/current-user-version", &out_appid, &out_url); ASSERT_STREQ("com.test.good_app1_1.2.3", out_appid); EXPECT_EQ(nullptr, out_url); dispatcher_send_to_app(out_appid, out_url); EXPECT_STREQ("com.test.good_app1_1.2.3", 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; /* Wild app fixed version */ dispatcher_url_to_appid("appid://com.test.good/only-listed-app/1.2.3", &out_appid, &out_url); ASSERT_STREQ("com.test.good_app1_1.2.3", out_appid); EXPECT_EQ(nullptr, out_url); dispatcher_send_to_app(out_appid, out_url); EXPECT_STREQ("com.test.good_app1_1.2.3", 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; return; } TEST_F(AppIdTest, OrderingUrl) { gchar * out_appid = nullptr; const gchar * out_url = nullptr; dispatcher_url_to_appid("appid://com.test.multiple/first-listed-app/current-user-version", &out_appid, &out_url); ASSERT_STREQ("com.test.multiple_app-first_1.2.3", out_appid); EXPECT_EQ(nullptr, out_url); dispatcher_send_to_app(out_appid, out_url); EXPECT_STREQ("com.test.multiple_app-first_1.2.3", 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; dispatcher_url_to_appid("appid://com.test.multiple/last-listed-app/current-user-version", &out_appid, &out_url); ASSERT_STREQ("com.test.multiple_app-last_1.2.3", out_appid); EXPECT_EQ(nullptr, out_url); dispatcher_send_to_app(out_appid, out_url); EXPECT_STREQ("com.test.multiple_app-last_1.2.3", 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; dispatcher_url_to_appid("appid://com.test.multiple/only-listed-app/current-user-version", &out_appid, &out_url); EXPECT_EQ(nullptr, out_appid); EXPECT_EQ(nullptr, out_url); return; } TEST_F(AppIdTest, BadDirectory) { gchar * out_appid = nullptr; const gchar * out_url = nullptr; g_setenv("TEST_CLICK_DB", "not-click-db", TRUE); dispatcher_url_to_appid("appid://com.test.good/app1/current-user-version", &out_appid, &out_url); EXPECT_EQ(nullptr, out_appid); EXPECT_EQ(nullptr, out_url); return; } TEST_F(AppIdTest, BadUser) { gchar * out_appid = nullptr; const gchar * out_url = nullptr; g_setenv("TEST_CLICK_USER", "not-test-user", TRUE); dispatcher_url_to_appid("appid://com.test.good/app1/current-user-version", &out_appid, &out_url); EXPECT_EQ(nullptr, out_appid); EXPECT_EQ(nullptr, out_url); } url-dispatcher-0.1+16.04.20151110/tests/lib-test.cc0000644000015300001610000001135112620400412022010 0ustar pbuserpbgroup00000000000000/** * 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); } url-dispatcher-0.1+16.04.20151110/tests/click-data/0000755000015300001610000000000012620400774021764 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.test.multiple/0000755000015300001610000000000012620400774025352 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.test.multiple/1.2.3/0000755000015300001610000000000012620400774026013 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.test.multiple/1.2.3/.click/0000755000015300001610000000000012620400774027156 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.test.multiple/1.2.3/.click/info0000777000015300001610000000000012620400774032606 2../../../manifestsustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.clock/0000755000015300001610000000000012620400774025155 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.clock/3.23455.1/0000755000015300001610000000000012620400774026137 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.clock/3.23455.1/.click/0000755000015300001610000000000012620400774027302 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.clock/3.23455.1/.click/info0000777000015300001610000000000012620400774032732 2../../../manifestsustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.test.good/0000755000015300001610000000000012620400774024447 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.test.good/1.2.3/0000755000015300001610000000000012620400774025110 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.test.good/1.2.3/.click/0000755000015300001610000000000012620400774026253 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.test.good/1.2.3/.click/info0000777000015300001610000000000012620400774031703 2../../../manifestsustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.music/0000755000015300001610000000000012620400774025202 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.music/1.5.4/0000755000015300001610000000000012620400774025647 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.music/1.5.4/.click/0000755000015300001610000000000012620400774027012 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.music/1.5.4/.click/info0000777000015300001610000000000012620400774032442 2../../../manifestsustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.calendar/0000755000015300001610000000000012620400774025633 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.calendar/9.8.2343/0000755000015300001610000000000012620400774026543 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.calendar/9.8.2343/.click/0000755000015300001610000000000012620400774027706 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/com.ubuntu.calendar/9.8.2343/.click/info0000777000015300001610000000000012620400774033336 2../../../manifestsustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/.click/0000755000015300001610000000000012620400774023127 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/.click/users/0000755000015300001610000000000012620400774024270 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/.click/users/test-user/0000755000015300001610000000000012620400774026223 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/.click/users/test-user/com.test.multiple0000777000015300001610000000000012620400774036423 2../../../com.test.multiple/1.2.3/ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/.click/users/test-user/com.ubuntu.clock0000777000015300001610000000000012620400774036352 2../../../com.ubuntu.clock/3.23455.1/ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/.click/users/test-user/com.test.good0000777000015300001610000000000012620400774034615 2../../../com.test.good/1.2.3/ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/.click/users/test-user/com.ubuntu.music0000777000015300001610000000000012620400774036107 2../../../com.ubuntu.music/1.5.4/ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/.click/users/test-user/com.ubuntu.calendar0000777000015300001610000000000012620400774037434 2../../../com.ubuntu.calendar/9.8.2343/ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/manifests/0000755000015300001610000000000012620400774023755 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/click-data/manifests/com.ubuntu.clock.manifest0000644000015300001610000000015412620400412030663 0ustar pbuserpbgroup00000000000000{ "version": "3.23455.1", "name": "com.ubuntu.clock", "hooks": { "clock": { "test": "test" } } } url-dispatcher-0.1+16.04.20151110/tests/click-data/manifests/com.test.multiple.manifest0000644000015300001610000000027112620400412031060 0ustar pbuserpbgroup00000000000000{ "version": "1.2.3", "name": "com.test.good", "hooks": { "app-first": { "test": "test" }, "app-middle": { "test": "test" }, "app-last": { "test": "test" } } } url-dispatcher-0.1+16.04.20151110/tests/click-data/manifests/com.ubuntu.music.manifest0000644000015300001610000000015012620400412030704 0ustar pbuserpbgroup00000000000000{ "version": "1.5.4", "name": "com.ubuntu.music", "hooks": { "music": { "test": "test" } } } url-dispatcher-0.1+16.04.20151110/tests/click-data/manifests/com.ubuntu.calendar.manifest0000644000015300001610000000016112620400412031337 0ustar pbuserpbgroup00000000000000{ "version": "9.8.2343", "name": "com.ubuntu.calendar", "hooks": { "calendar": { "test": "test" } } } url-dispatcher-0.1+16.04.20151110/tests/click-data/manifests/com.test.good.manifest0000644000015300001610000000014412620400412030154 0ustar pbuserpbgroup00000000000000{ "version": "1.2.3", "name": "com.test.good", "hooks": { "app1": { "test": "test" } } } url-dispatcher-0.1+16.04.20151110/tests/test-urls-varied/0000755000015300001610000000000012620400774023202 5ustar pbuserpbgroup00000000000000url-dispatcher-0.1+16.04.20151110/tests/test-urls-varied/dup-file-2.url-dispatcher0000644000015300001610000000012012620400412027674 0ustar pbuserpbgroup00000000000000[ { "protocol": "dupfile", "domain-suffix": "this.is.in.two.file.org" } ] url-dispatcher-0.1+16.04.20151110/tests/test-urls-varied/dup-file-1.url-dispatcher0000644000015300001610000000012012620400412027673 0ustar pbuserpbgroup00000000000000[ { "protocol": "dupfile", "domain-suffix": "this.is.in.two.file.org" } ] url-dispatcher-0.1+16.04.20151110/tests/test-urls-varied/lots-o-entries.url-dispatcher0000644000015300001610000000113312620400412030721 0ustar pbuserpbgroup00000000000000[ { "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" } ] url-dispatcher-0.1+16.04.20151110/tests/test-urls-varied/not-json.url-dispatcher0000644000015300001610000000013612620400412027606 0ustar pbuserpbgroup00000000000000 notjson not.json.com url-dispatcher-0.1+16.04.20151110/tests/test-urls-varied/duplicate.url-dispatcher0000644000015300001610000000021712620400412030011 0ustar pbuserpbgroup00000000000000[ { "protocol": "duplicate", "domain-suffix": "dup.licate.com" }, { "protocol": "duplicate", "domain-suffix": "dup.licate.com" } ] url-dispatcher-0.1+16.04.20151110/tests/test-urls-varied/object-base.url-dispatcher0000644000015300001610000000012012620400412030206 0ustar pbuserpbgroup00000000000000{ "first": { "protocol": "object", "domain-suffix": "object-base.com" } } url-dispatcher-0.1+16.04.20151110/tests/test-urls-varied/bad-filename-suffix.url-launcher0000644000015300001610000000011112620400412031311 0ustar pbuserpbgroup00000000000000[ { "protocol": "badsuffix", "domain-suffix": "bad.suffix.com" } ] url-dispatcher-0.1+16.04.20151110/tests/ubuntu-app-launch-mock.c0000644000015300001610000000756712620400412024437 0ustar pbuserpbgroup00000000000000/** * 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" static gchar * last_appid = 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; } url-dispatcher-0.1+16.04.20151110/tests/test-sql.sh0000755000015300001610000000036012620400412022067 0ustar pbuserpbgroup00000000000000#!/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 url-dispatcher-0.1+16.04.20151110/tests/url-db-test.cc0000644000015300001610000001645012620400412022434 0ustar pbuserpbgroup00000000000000/** * 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); }