/* 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 "nsDBusRemoteClient.h"
#include "RemoteUtils.h"
#include "nsAppRunner.h"
#include "mozilla/XREAppData.h"
#include "mozilla/Logging.h"
#include "mozilla/Base64.h"
#include "nsPrintfCString.h"
#include "mozilla/GUniquePtr.h"
#include "nsAppShell.h"

#include <dbus/dbus.h>

#undef LOG
#ifdef MOZ_LOGGING
static mozilla::LazyLogModule sRemoteLm("nsDBusRemoteClient");
#  define LOG(str, ...) \
    MOZ_LOG(sRemoteLm, mozilla::LogLevel::Debug, (str, ##__VA_ARGS__))
#else
#  define LOG(...)
#endif

using namespace mozilla;

nsDBusRemoteClient::nsDBusRemoteClient(nsACString& aStartupToken)
    : mStartupToken(aStartupToken) {
  LOG("nsDBusRemoteClient::nsDBusRemoteClient");
}

nsDBusRemoteClient::~nsDBusRemoteClient() {
  LOG("nsDBusRemoteClient::~nsDBusRemoteClient");
}

nsresult nsDBusRemoteClient::SendCommandLine(const char* aProgram,
                                             const char* aProfile, int32_t argc,
                                             const char** argv, bool aRaise) {
  // aRaise is unused on this platform.
  NS_ENSURE_TRUE(aProfile, NS_ERROR_INVALID_ARG);

  LOG("nsDBusRemoteClient::SendCommandLine");

  int commandLineLength;
  mozilla::UniquePtr<char[]> commandLine = ConstructCommandLine(
      argc, argv,
      mStartupToken.IsEmpty() ? nullptr
                              : PromiseFlatCString(mStartupToken).get(),
      &commandLineLength);
  if (!commandLine) {
    LOG("  failed to create command line");
    return NS_ERROR_FAILURE;
  }

  nsresult rv =
      DoSendDBusCommandLine(aProfile, commandLine.get(), commandLineLength);

  LOG("DoSendDBusCommandLine %s", NS_SUCCEEDED(rv) ? "OK" : "FAILED");
  return rv;
}

bool nsDBusRemoteClient::GetRemoteDestinationName(const char* aProgram,
                                                  const char* aProfile,
                                                  nsCString& aDestinationName) {
  // We have a profile name - just create the destination.
  // 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(aProfile), profileName);
  NS_ENSURE_SUCCESS(rv, false);

  mozilla::XREAppData::SanitizeNameForDBus(profileName);

  aDestinationName =
      nsPrintfCString("org.torproject.%s.%s", aProgram, profileName.get());
  if (aDestinationName.Length() > DBUS_MAXIMUM_NAME_LENGTH)
    aDestinationName.Truncate(DBUS_MAXIMUM_NAME_LENGTH);

  if (!g_dbus_is_name(aDestinationName.get())) {
    // We don't have a valid busName yet - try to create a default one.
    aDestinationName =
        nsPrintfCString("org.torproject.%s.%s", aProgram, "default");
    if (!g_dbus_is_name(aDestinationName.get())) {
      // We failed completely to get a valid bus name - just quit.
      LOG("  failed to validate profile DBus name");
      return false;
    }
  }

  return true;
}

nsresult nsDBusRemoteClient::DoSendDBusCommandLine(const char* aProfile,
                                                   const char* aBuffer,
                                                   int aLength) {
  LOG("nsDBusRemoteClient::DoSendDBusCommandLine()");

  if (!gAppData) {
    return NS_ERROR_FAILURE;
  }

  nsAutoCString appName;
  gAppData->GetDBusAppName(appName);

  nsAutoCString destinationName;
  if (!GetRemoteDestinationName(appName.get(), aProfile, destinationName)) {
    LOG("  failed to get remote destination name");
    return NS_ERROR_FAILURE;
  }

  nsAutoCString pathName;
  pathName = nsPrintfCString("/org/torproject/%s/Remote", appName.get());

  if (!g_variant_is_object_path(pathName.get())) {
    LOG("  failed to validate path name");
    return NS_ERROR_FAILURE;
  }

  nsAutoCString remoteInterfaceName;
  remoteInterfaceName = nsPrintfCString("org.torproject.%s", appName.get());

  LOG("  DBus destination: %s\n", destinationName.get());
  LOG("  DBus path: %s\n", pathName.get());
  LOG("  DBus interface: %s\n", remoteInterfaceName.get());

  nsAppShell::DBusConnectionCheck();
  RefPtr<GDBusProxy> proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
      G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr,
      destinationName.get(), pathName.get(), remoteInterfaceName.get(), nullptr,
      nullptr));
  if (!proxy) {
    LOG("  failed to create DBus proxy");
    return NS_ERROR_FAILURE;
  }

  GUniquePtr<GError> error;
  RefPtr<GVariant> reply = dont_AddRef(g_dbus_proxy_call_sync(
      proxy, "OpenURL",
      g_variant_new_from_data(G_VARIANT_TYPE("(ay)"), aBuffer, aLength, true,
                              nullptr, nullptr),
      G_DBUS_CALL_FLAGS_NONE, -1, nullptr, getter_Transfers(error)));
#ifdef MOZ_LOGGING
  if (!reply) {
    LOG("  failed to OpenURL: %s", error->message);
  }
#endif

  return reply ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
