michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:expandtab:shiftwidth=2:tabstop=8: michael@0: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "nsXRemoteService.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsICommandLineRunner.h" michael@0: #include "nsICommandLine.h" michael@0: michael@0: #include "nsIBaseWindow.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIWeakReference.h" michael@0: #include "nsIWidget.h" michael@0: #include "nsIAppShellService.h" michael@0: #include "nsAppShellCID.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "mozilla/X11Util.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsString.h" michael@0: #include "prprf.h" michael@0: #include "prenv.h" michael@0: #include "nsCRT.h" michael@0: michael@0: #include "nsXULAppAPI.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #define MOZILLA_VERSION_PROP "_MOZILLA_VERSION" michael@0: #define MOZILLA_LOCK_PROP "_MOZILLA_LOCK" michael@0: #define MOZILLA_COMMAND_PROP "_MOZILLA_COMMAND" michael@0: #define MOZILLA_RESPONSE_PROP "_MOZILLA_RESPONSE" michael@0: #define MOZILLA_USER_PROP "_MOZILLA_USER" michael@0: #define MOZILLA_PROFILE_PROP "_MOZILLA_PROFILE" michael@0: #define MOZILLA_PROGRAM_PROP "_MOZILLA_PROGRAM" michael@0: #define MOZILLA_COMMANDLINE_PROP "_MOZILLA_COMMANDLINE" michael@0: michael@0: const unsigned char kRemoteVersion[] = "5.1"; michael@0: michael@0: #ifdef IS_BIG_ENDIAN michael@0: #define TO_LITTLE_ENDIAN32(x) \ michael@0: ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ michael@0: (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) michael@0: #else michael@0: #define TO_LITTLE_ENDIAN32(x) (x) michael@0: #endif michael@0: michael@0: // Minimize the roundtrips to the X server by getting all the atoms at once michael@0: static const char *XAtomNames[] = { michael@0: MOZILLA_VERSION_PROP, michael@0: MOZILLA_LOCK_PROP, michael@0: MOZILLA_COMMAND_PROP, michael@0: MOZILLA_RESPONSE_PROP, michael@0: MOZILLA_USER_PROP, michael@0: MOZILLA_PROFILE_PROP, michael@0: MOZILLA_PROGRAM_PROP, michael@0: MOZILLA_COMMANDLINE_PROP michael@0: }; michael@0: static Atom XAtoms[MOZ_ARRAY_LENGTH(XAtomNames)]; michael@0: michael@0: Atom nsXRemoteService::sMozVersionAtom; michael@0: Atom nsXRemoteService::sMozLockAtom; michael@0: Atom nsXRemoteService::sMozCommandAtom; michael@0: Atom nsXRemoteService::sMozResponseAtom; michael@0: Atom nsXRemoteService::sMozUserAtom; michael@0: Atom nsXRemoteService::sMozProfileAtom; michael@0: Atom nsXRemoteService::sMozProgramAtom; michael@0: Atom nsXRemoteService::sMozCommandLineAtom; michael@0: michael@0: nsXRemoteService * nsXRemoteService::sRemoteImplementation = 0; michael@0: michael@0: michael@0: static bool michael@0: FindExtensionParameterInCommand(const char* aParameterName, michael@0: const nsACString& aCommand, michael@0: char aSeparator, michael@0: nsACString* aValue) michael@0: { michael@0: nsAutoCString searchFor; michael@0: searchFor.Append(aSeparator); michael@0: searchFor.Append(aParameterName); michael@0: searchFor.Append('='); michael@0: michael@0: nsACString::const_iterator start, end; michael@0: aCommand.BeginReading(start); michael@0: aCommand.EndReading(end); michael@0: if (!FindInReadable(searchFor, start, end)) michael@0: return false; michael@0: michael@0: nsACString::const_iterator charStart, charEnd; michael@0: charStart = end; michael@0: aCommand.EndReading(charEnd); michael@0: nsACString::const_iterator idStart = charStart, idEnd; michael@0: if (FindCharInReadable(aSeparator, charStart, charEnd)) { michael@0: idEnd = charStart; michael@0: } else { michael@0: idEnd = charEnd; michael@0: } michael@0: *aValue = nsDependentCSubstring(idStart, idEnd); michael@0: return true; michael@0: } michael@0: michael@0: michael@0: nsXRemoteService::nsXRemoteService() michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsXRemoteService::XRemoteBaseStartup(const char *aAppName, const char *aProfileName) michael@0: { michael@0: EnsureAtoms(); michael@0: michael@0: mAppName = aAppName; michael@0: ToLowerCase(mAppName); michael@0: michael@0: mProfileName = aProfileName; michael@0: michael@0: nsCOMPtr obs(do_GetService("@mozilla.org/observer-service;1")); michael@0: if (obs) { michael@0: obs->AddObserver(this, "xpcom-shutdown", false); michael@0: obs->AddObserver(this, "quit-application", false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXRemoteService::HandleCommandsFor(Window aWindowId) michael@0: { michael@0: // set our version michael@0: XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozVersionAtom, XA_STRING, michael@0: 8, PropModeReplace, kRemoteVersion, sizeof(kRemoteVersion) - 1); michael@0: michael@0: // get our username michael@0: unsigned char *logname; michael@0: logname = (unsigned char*) PR_GetEnv("LOGNAME"); michael@0: if (logname) { michael@0: // set the property on the window if it's available michael@0: XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozUserAtom, XA_STRING, michael@0: 8, PropModeReplace, logname, strlen((char*) logname)); michael@0: } michael@0: michael@0: XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozProgramAtom, XA_STRING, michael@0: 8, PropModeReplace, (unsigned char*) mAppName.get(), mAppName.Length()); michael@0: michael@0: if (!mProfileName.IsEmpty()) { michael@0: XChangeProperty(mozilla::DefaultXDisplay(), michael@0: aWindowId, sMozProfileAtom, XA_STRING, michael@0: 8, PropModeReplace, michael@0: (unsigned char*) mProfileName.get(), mProfileName.Length()); michael@0: } michael@0: michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXRemoteService::Observe(nsISupports* aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: // This can be xpcom-shutdown or quit-application, but it's the same either michael@0: // way. michael@0: Shutdown(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsXRemoteService::HandleNewProperty(XID aWindowId, Display* aDisplay, michael@0: Time aEventTime, michael@0: Atom aChangedAtom, michael@0: nsIWeakReference* aDomWindow) michael@0: { michael@0: michael@0: nsCOMPtr window (do_QueryReferent(aDomWindow)); michael@0: michael@0: if (aChangedAtom == sMozCommandAtom || aChangedAtom == sMozCommandLineAtom) { michael@0: // We got a new command atom. michael@0: int result; michael@0: Atom actual_type; michael@0: int actual_format; michael@0: unsigned long nitems, bytes_after; michael@0: char *data = 0; michael@0: michael@0: result = XGetWindowProperty (aDisplay, michael@0: aWindowId, michael@0: aChangedAtom, michael@0: 0, /* long_offset */ michael@0: (65536 / sizeof (long)), /* long_length */ michael@0: True, /* atomic delete after */ michael@0: XA_STRING, /* req_type */ michael@0: &actual_type, /* actual_type return */ michael@0: &actual_format, /* actual_format_return */ michael@0: &nitems, /* nitems_return */ michael@0: &bytes_after, /* bytes_after_return */ michael@0: (unsigned char **)&data); /* prop_return michael@0: (we only care michael@0: about the first ) */ michael@0: michael@0: // Failed to get property off the window? michael@0: if (result != Success) michael@0: return false; michael@0: michael@0: // Failed to get the data off the window or it was the wrong type? michael@0: if (!data || !TO_LITTLE_ENDIAN32(*reinterpret_cast(data))) michael@0: return false; michael@0: michael@0: // cool, we got the property data. michael@0: const char *response = nullptr; michael@0: if (aChangedAtom == sMozCommandAtom) michael@0: response = HandleCommand(data, window, aEventTime); michael@0: else if (aChangedAtom == sMozCommandLineAtom) michael@0: response = HandleCommandLine(data, window, aEventTime); michael@0: michael@0: // put the property onto the window as the response michael@0: XChangeProperty (aDisplay, aWindowId, michael@0: sMozResponseAtom, XA_STRING, michael@0: 8, PropModeReplace, michael@0: (const unsigned char *)response, michael@0: strlen (response)); michael@0: XFree(data); michael@0: return true; michael@0: } michael@0: michael@0: else if (aChangedAtom == sMozResponseAtom) { michael@0: // client accepted the response. party on wayne. michael@0: return true; michael@0: } michael@0: michael@0: else if (aChangedAtom == sMozLockAtom) { michael@0: // someone locked the window michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: const char* michael@0: nsXRemoteService::HandleCommand(char* aCommand, nsIDOMWindow* aWindow, michael@0: uint32_t aTimestamp) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr cmdline michael@0: (do_CreateInstance("@mozilla.org/toolkit/command-line;1", &rv)); michael@0: if (NS_FAILED(rv)) michael@0: return "509 internal error"; michael@0: michael@0: // 1) Make sure that it looks remotely valid with parens michael@0: // 2) Treat ping() immediately and specially michael@0: michael@0: nsAutoCString command(aCommand); michael@0: int32_t p1, p2; michael@0: p1 = command.FindChar('('); michael@0: p2 = command.FindChar(')'); michael@0: michael@0: if (p1 == kNotFound || p2 == kNotFound || p1 == 0 || p2 < p1) { michael@0: return "500 command not parseable"; michael@0: } michael@0: michael@0: command.Truncate(p1); michael@0: command.Trim(" ", true, true); michael@0: ToLowerCase(command); michael@0: michael@0: if (!command.EqualsLiteral("ping")) { michael@0: nsAutoCString desktopStartupID; michael@0: nsDependentCString cmd(aCommand); michael@0: FindExtensionParameterInCommand("DESKTOP_STARTUP_ID", michael@0: cmd, '\n', michael@0: &desktopStartupID); michael@0: michael@0: const char* argv[3] = {"dummyappname", "-remote", aCommand}; michael@0: rv = cmdline->Init(3, argv, nullptr, nsICommandLine::STATE_REMOTE_EXPLICIT); michael@0: if (NS_FAILED(rv)) michael@0: return "509 internal error"; michael@0: michael@0: if (aWindow) michael@0: cmdline->SetWindowContext(aWindow); michael@0: michael@0: if (sRemoteImplementation) michael@0: sRemoteImplementation->SetDesktopStartupIDOrTimestamp(desktopStartupID, aTimestamp); michael@0: michael@0: rv = cmdline->Run(); michael@0: if (NS_ERROR_ABORT == rv) michael@0: return "500 command not parseable"; michael@0: if (NS_FAILED(rv)) michael@0: return "509 internal error"; michael@0: } michael@0: michael@0: return "200 executed command"; michael@0: } michael@0: michael@0: const char* michael@0: nsXRemoteService::HandleCommandLine(char* aBuffer, nsIDOMWindow* aWindow, michael@0: uint32_t aTimestamp) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr cmdline michael@0: (do_CreateInstance("@mozilla.org/toolkit/command-line;1", &rv)); michael@0: if (NS_FAILED(rv)) michael@0: return "509 internal error"; michael@0: michael@0: // the commandline property is constructed as an array of int32_t michael@0: // followed by a series of null-terminated strings: michael@0: // michael@0: // [argc][offsetargv0][offsetargv1...]\0\0argv[1]...\0 michael@0: // (offset is from the beginning of the buffer) michael@0: michael@0: int32_t argc = TO_LITTLE_ENDIAN32(*reinterpret_cast(aBuffer)); michael@0: char *wd = aBuffer + ((argc + 1) * sizeof(int32_t)); michael@0: michael@0: nsCOMPtr lf; michael@0: rv = NS_NewNativeLocalFile(nsDependentCString(wd), true, michael@0: getter_AddRefs(lf)); michael@0: if (NS_FAILED(rv)) michael@0: return "509 internal error"; michael@0: michael@0: nsAutoCString desktopStartupID; michael@0: michael@0: char **argv = (char**) malloc(sizeof(char*) * argc); michael@0: if (!argv) return "509 internal error"; michael@0: michael@0: int32_t *offset = reinterpret_cast(aBuffer) + 1; michael@0: michael@0: for (int i = 0; i < argc; ++i) { michael@0: argv[i] = aBuffer + TO_LITTLE_ENDIAN32(offset[i]); michael@0: michael@0: if (i == 0) { michael@0: nsDependentCString cmd(argv[0]); michael@0: FindExtensionParameterInCommand("DESKTOP_STARTUP_ID", michael@0: cmd, ' ', michael@0: &desktopStartupID); michael@0: } michael@0: } michael@0: michael@0: rv = cmdline->Init(argc, argv, lf, nsICommandLine::STATE_REMOTE_AUTO); michael@0: michael@0: free (argv); michael@0: if (NS_FAILED(rv)) { michael@0: return "509 internal error"; michael@0: } michael@0: michael@0: if (aWindow) michael@0: cmdline->SetWindowContext(aWindow); michael@0: michael@0: if (sRemoteImplementation) michael@0: sRemoteImplementation->SetDesktopStartupIDOrTimestamp(desktopStartupID, aTimestamp); michael@0: michael@0: rv = cmdline->Run(); michael@0: michael@0: if (NS_ERROR_ABORT == rv) michael@0: return "500 command not parseable"; michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return "509 internal error"; michael@0: michael@0: return "200 executed command"; michael@0: } michael@0: michael@0: void michael@0: nsXRemoteService::EnsureAtoms(void) michael@0: { michael@0: if (sMozVersionAtom) michael@0: return; michael@0: michael@0: XInternAtoms(mozilla::DefaultXDisplay(), const_cast(XAtomNames), michael@0: ArrayLength(XAtomNames), False, XAtoms); michael@0: michael@0: int i = 0; michael@0: sMozVersionAtom = XAtoms[i++]; michael@0: sMozLockAtom = XAtoms[i++]; michael@0: sMozCommandAtom = XAtoms[i++]; michael@0: sMozResponseAtom = XAtoms[i++]; michael@0: sMozUserAtom = XAtoms[i++]; michael@0: sMozProfileAtom = XAtoms[i++]; michael@0: sMozProgramAtom = XAtoms[i++]; michael@0: sMozCommandLineAtom = XAtoms[i++]; michael@0: }