toolkit/components/remote/nsDBusRemoteServer.cpp
author Dave Townsend <dtownsend@oxymoronical.com>
Wed, 27 Feb 2019 15:45:51 -0800
changeset 462601 28c7186745e3d5de5f44a72a81e0068cb23ce547
parent 458363 toolkit/components/remote/nsDBusRemoteService.cpp@9c647756b479f5e4e21a39f70ae8b2d9d7c4a803
permissions -rw-r--r--
Bug 1518639: Break out the remote server pieces from nsRemoteService and deCOMtaminate. r=jimm This code is only ever used from c++ so does not need to be an XPCOM component. Broken out a single nsRemoteService that is responsible for choosing the server implementation to use. Differential Revision: https://phabricator.services.mozilla.com/D19067

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=2:
 */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsDBusRemoteServer.h"

#include "nsIBaseWindow.h"
#include "nsIDocShell.h"
#include "nsPIDOMWindow.h"
#include "mozilla/ModuleUtils.h"
#include "mozilla/Base64.h"
#include "nsIServiceManager.h"
#include "nsIWidget.h"
#include "nsIAppShellService.h"
#include "nsAppShellCID.h"
#include "nsPrintfCString.h"

#include "nsCOMPtr.h"

#include "nsGTKToolkit.h"

#include <dbus/dbus.h>
#include <dbus/dbus-glib-lowlevel.h>

#include <dlfcn.h>

const char *introspect_template =
    "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection "
    "1.0//EN\"\n"
    "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\";>\n"
    "<node>\n"
    " <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
    "   <method name=\"Introspect\">\n"
    "     <arg name=\"data\" direction=\"out\" type=\"s\"/>\n"
    "   </method>\n"
    " </interface>\n"
    " <interface name=\"org.mozilla.%s\">\n"
    "   <method name=\"OpenURL\">\n"
    "     <arg name=\"url\" direction=\"in\" type=\"s\"/>\n"
    "   </method>\n"
    " </interface>\n"
    "</node>\n";

DBusHandlerResult nsDBusRemoteServer::Introspect(DBusMessage *msg) {
  DBusMessage *reply;

  reply = dbus_message_new_method_return(msg);
  if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY;

  nsAutoCString introspect_xml;
  introspect_xml = nsPrintfCString(introspect_template, mAppName.get());

  const char *message = introspect_xml.get();
  dbus_message_append_args(reply, DBUS_TYPE_STRING, &message,
                           DBUS_TYPE_INVALID);

  dbus_connection_send(mConnection, reply, nullptr);
  dbus_message_unref(reply);

  return DBUS_HANDLER_RESULT_HANDLED;
}

DBusHandlerResult nsDBusRemoteServer::OpenURL(DBusMessage *msg) {
  DBusMessage *reply = nullptr;
  const char *commandLine;
  int length;

  if (!dbus_message_get_args(msg, nullptr, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
                             &commandLine, &length, DBUS_TYPE_INVALID) ||
      length == 0) {
    nsAutoCString errorMsg;
    errorMsg = nsPrintfCString("org.mozilla.%s.Error", mAppName.get());
    reply = dbus_message_new_error(msg, errorMsg.get(), "Wrong argument");
  } else {
    guint32 timestamp = gtk_get_current_event_time();
    if (timestamp == GDK_CURRENT_TIME) {
      timestamp = guint32(g_get_monotonic_time() / 1000);
    }
    HandleCommandLine(commandLine, timestamp);
    reply = dbus_message_new_method_return(msg);
  }

  dbus_connection_send(mConnection, reply, nullptr);
  dbus_message_unref(reply);

  return DBUS_HANDLER_RESULT_HANDLED;
}

DBusHandlerResult nsDBusRemoteServer::HandleDBusMessage(
    DBusConnection *aConnection, DBusMessage *msg) {
  NS_ASSERTION(mConnection == aConnection, "Wrong D-Bus connection.");

  const char *method = dbus_message_get_member(msg);
  const char *iface = dbus_message_get_interface(msg);

  if ((strcmp("Introspect", method) == 0) &&
      (strcmp("org.freedesktop.DBus.Introspectable", iface) == 0)) {
    return Introspect(msg);
  }

  nsAutoCString ourInterfaceName;
  ourInterfaceName = nsPrintfCString("org.mozilla.%s", mAppName.get());

  if ((strcmp("OpenURL", method) == 0) &&
      (strcmp(ourInterfaceName.get(), iface) == 0)) {
    return OpenURL(msg);
  }

  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

void nsDBusRemoteServer::UnregisterDBusInterface(DBusConnection *aConnection) {
  NS_ASSERTION(mConnection == aConnection, "Wrong D-Bus connection.");
  // Not implemented
}

static DBusHandlerResult message_handler(DBusConnection *conn, DBusMessage *msg,
                                         void *user_data) {
  auto interface = static_cast<nsDBusRemoteServer *>(user_data);
  return interface->HandleDBusMessage(conn, msg);
}

static void unregister(DBusConnection *conn, void *user_data) {
  auto interface = static_cast<nsDBusRemoteServer *>(user_data);
  interface->UnregisterDBusInterface(conn);
}

static DBusObjectPathVTable remoteHandlersTable = {
    .unregister_function = unregister,
    .message_function = message_handler,
};

nsresult nsDBusRemoteServer::Startup(const char *aAppName,
                                     const char *aProfileName) {
  if (mConnection && dbus_connection_get_is_connected(mConnection)) {
    // We're already connected so we don't need to reconnect
    return NS_ERROR_ALREADY_INITIALIZED;
  }

  // Don't even try to start without any application/profile name
  if (!aAppName || aAppName[0] == '\0' || !aProfileName ||
      aProfileName[0] == '\0')
    return NS_ERROR_INVALID_ARG;

  mConnection =
      already_AddRefed<DBusConnection>(dbus_bus_get(DBUS_BUS_SESSION, nullptr));
  if (!mConnection) {
    return NS_ERROR_FAILURE;
  }
  dbus_connection_set_exit_on_disconnect(mConnection, false);
  dbus_connection_setup_with_g_main(mConnection, nullptr);

  mAppName = aAppName;
  ToLowerCase(mAppName);

  // D-Bus names can contain only [a-z][A-Z][0-9]_
  // characters so adjust the profile string properly.
  nsAutoCString profileName;
  nsresult rv = mozilla::Base64Encode(nsAutoCString(aProfileName), profileName);
  NS_ENSURE_SUCCESS(rv, rv);
  profileName.ReplaceChar("+/=", '_');

  nsAutoCString busName;
  busName =
      nsPrintfCString("org.mozilla.%s.%s", mAppName.get(), profileName.get());
  if (busName.Length() > DBUS_MAXIMUM_NAME_LENGTH)
    busName.Truncate(DBUS_MAXIMUM_NAME_LENGTH);

  static auto sDBusValidateBusName = (bool (*)(const char *, DBusError *))dlsym(
      RTLD_DEFAULT, "dbus_validate_bus_name");
  if (!sDBusValidateBusName) {
    return NS_ERROR_FAILURE;
  }

  // We don't have a valid busName yet - try to create a default one.
  if (!sDBusValidateBusName(busName.get(), nullptr)) {
    busName = nsPrintfCString("org.mozilla.%s.%s", mAppName.get(), "default");
    if (!sDBusValidateBusName(busName.get(), nullptr)) {
      // We failed completelly to get a valid bus name - just quit
      // to prevent crash at dbus_bus_request_name().
      return NS_ERROR_FAILURE;
    }
  }

  DBusError err;
  dbus_error_init(&err);
  dbus_bus_request_name(mConnection, busName.get(), DBUS_NAME_FLAG_DO_NOT_QUEUE,
                        &err);
  // The interface is already owned - there is another application/profile
  // instance already running.
  if (dbus_error_is_set(&err)) {
    dbus_error_free(&err);
    mConnection = nullptr;
    return NS_ERROR_FAILURE;
  }

  mPathName = nsPrintfCString("/org/mozilla/%s/Remote", mAppName.get());
  if (!dbus_connection_register_object_path(mConnection, mPathName.get(),
                                            &remoteHandlersTable, this)) {
    mConnection = nullptr;
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

void nsDBusRemoteServer::Shutdown() {
  if (!mConnection) {
    return;
  }

  dbus_connection_unregister_object_path(mConnection, mPathName.get());

  // dbus_connection_unref() will be called by RefPtr here.
  mConnection = nullptr;
}