1/*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 * Copyright (C) 2011 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#import "config.h"
28#import "PluginProcess.h"
29
30#if ENABLE(PLUGIN_PROCESS)
31
32#import "ArgumentCoders.h"
33#import "NetscapePlugin.h"
34#import "PluginProcessCreationParameters.h"
35#import "PluginProcessProxyMessages.h"
36#import "PluginProcessShim.h"
37#import "PluginSandboxProfile.h"
38#import "SandboxInitializationParameters.h"
39#import "SandboxUtilities.h"
40#import <CoreAudio/AudioHardware.h>
41#import <WebCore/LocalizedStrings.h>
42#import <WebKitSystemInterface.h>
43#import <dlfcn.h>
44#import <objc/runtime.h>
45#import <sysexits.h>
46#import <wtf/HashSet.h>
47
48using namespace WebCore;
49
50const CFStringRef kLSPlugInBundleIdentifierKey = CFSTR("LSPlugInBundleIdentifierKey");
51
52namespace WebKit {
53
54class FullscreenWindowTracker {
55    WTF_MAKE_NONCOPYABLE(FullscreenWindowTracker);
56
57public:
58    FullscreenWindowTracker() { }
59
60    template<typename T> void windowShown(T window);
61    template<typename T> void windowHidden(T window);
62
63private:
64    typedef HashSet<void*> WindowSet;
65    WindowSet m_windows;
66};
67
68static bool rectCoversAnyScreen(NSRect rect)
69{
70    for (NSScreen *screen in [NSScreen screens]) {
71        if (NSContainsRect(rect, [screen frame]))
72            return YES;
73    }
74    return NO;
75}
76
77#ifndef NP_NO_CARBON
78static bool windowCoversAnyScreen(WindowRef window)
79{
80    HIRect bounds;
81    HIWindowGetBounds(window, kWindowStructureRgn, kHICoordSpaceScreenPixel, &bounds);
82
83    // Convert to Cocoa-style screen coordinates that use a Y offset relative to the zeroth screen's origin.
84    bounds.origin.y = NSHeight([(NSScreen *)[[NSScreen screens] objectAtIndex:0] frame]) - CGRectGetMaxY(bounds);
85
86    return rectCoversAnyScreen(NSRectFromCGRect(bounds));
87}
88#endif
89
90static bool windowCoversAnyScreen(NSWindow* window)
91{
92    return rectCoversAnyScreen([window frame]);
93}
94
95template<typename T> void FullscreenWindowTracker::windowShown(T window)
96{
97    // If this window is already visible then there is nothing to do.
98    WindowSet::iterator it = m_windows.find(window);
99    if (it != m_windows.end())
100        return;
101
102    // If the window is not full-screen then we're not interested in it.
103    if (!windowCoversAnyScreen(window))
104        return;
105
106    bool windowSetWasEmpty = m_windows.isEmpty();
107
108    m_windows.add(window);
109
110    // If this is the first full screen window to be shown, notify the UI process.
111    if (windowSetWasEmpty)
112        PluginProcess::shared().setFullscreenWindowIsShowing(true);
113}
114
115template<typename T> void FullscreenWindowTracker::windowHidden(T window)
116{
117    // If this is not a window that we're tracking then there is nothing to do.
118    WindowSet::iterator it = m_windows.find(window);
119    if (it == m_windows.end())
120        return;
121
122    m_windows.remove(it);
123
124    // If this was the last full screen window that was visible, notify the UI process.
125    if (m_windows.isEmpty())
126        PluginProcess::shared().setFullscreenWindowIsShowing(false);
127}
128
129static FullscreenWindowTracker& fullscreenWindowTracker()
130{
131    DEFINE_STATIC_LOCAL(FullscreenWindowTracker, fullscreenWindowTracker, ());
132    return fullscreenWindowTracker;
133}
134
135#if defined(__i386__)
136
137static pthread_once_t shouldCallRealDebuggerOnce = PTHREAD_ONCE_INIT;
138static bool isUserbreakSet = false;
139
140static void initShouldCallRealDebugger()
141{
142    char* var = getenv("USERBREAK");
143
144    if (var)
145        isUserbreakSet = atoi(var);
146}
147
148static bool shouldCallRealDebugger()
149{
150    pthread_once(&shouldCallRealDebuggerOnce, initShouldCallRealDebugger);
151
152    return isUserbreakSet;
153}
154
155static bool isWindowActive(WindowRef windowRef, bool& result)
156{
157#ifndef NP_NO_CARBON
158    if (NetscapePlugin* plugin = NetscapePlugin::netscapePluginFromWindow(windowRef)) {
159        result = plugin->isWindowActive();
160        return true;
161    }
162#endif
163    return false;
164}
165
166static UInt32 getCurrentEventButtonState()
167{
168#ifndef NP_NO_CARBON
169    return NetscapePlugin::buttonState();
170#else
171    ASSERT_NOT_REACHED();
172    return 0;
173#endif
174}
175
176static void carbonWindowShown(WindowRef window)
177{
178#ifndef NP_NO_CARBON
179    fullscreenWindowTracker().windowShown(window);
180#endif
181}
182
183static void carbonWindowHidden(WindowRef window)
184{
185#ifndef NP_NO_CARBON
186    fullscreenWindowTracker().windowHidden(window);
187#endif
188}
189
190static bool openCFURLRef(CFURLRef url, int32_t& status, CFURLRef* launchedURL)
191{
192    String launchedURLString;
193    if (!PluginProcess::shared().openURL(KURL(url).string(), status, launchedURLString))
194        return false;
195
196    if (!launchedURLString.isNull() && launchedURL)
197        *launchedURL = KURL(ParsedURLString, launchedURLString).createCFURL().leakRef();
198    return true;
199}
200
201#endif
202
203static void setModal(bool modalWindowIsShowing)
204{
205    PluginProcess::shared().setModalWindowIsShowing(modalWindowIsShowing);
206}
207
208static unsigned modalCount = 0;
209
210static void beginModal()
211{
212#if COMPILER(CLANG)
213#pragma clang diagnostic push
214#pragma clang diagnostic ignored "-Wdeprecated-declarations"
215#endif
216    // Make sure to make ourselves the front process
217    ProcessSerialNumber psn;
218    GetCurrentProcess(&psn);
219    SetFrontProcess(&psn);
220#if COMPILER(CLANG)
221#pragma clang diagnostic pop
222#endif
223
224    if (!modalCount++)
225        setModal(true);
226}
227
228static void endModal()
229{
230    if (!--modalCount)
231        setModal(false);
232}
233
234static IMP NSApplication_RunModalForWindow;
235
236static NSInteger replacedRunModalForWindow(id self, SEL _cmd, NSWindow* window)
237{
238    beginModal();
239    NSInteger result = ((NSInteger (*)(id, SEL, NSWindow *))NSApplication_RunModalForWindow)(self, _cmd, window);
240    endModal();
241
242    return result;
243}
244
245#if defined(__i386__)
246static void initializeShim()
247{
248    // Initialize the shim for 32-bit only.
249    const PluginProcessShimCallbacks callbacks = {
250        shouldCallRealDebugger,
251        isWindowActive,
252        getCurrentEventButtonState,
253        beginModal,
254        endModal,
255        carbonWindowShown,
256        carbonWindowHidden,
257        setModal,
258        openCFURLRef,
259    };
260
261    PluginProcessShimInitializeFunc initFunc = reinterpret_cast<PluginProcessShimInitializeFunc>(dlsym(RTLD_DEFAULT, "WebKitPluginProcessShimInitialize"));
262    initFunc(callbacks);
263}
264#endif
265
266static IMP NSConcreteTask_launch;
267
268static void replacedNSConcreteTask_launch(NSTask *self, SEL _cmd)
269{
270    String launchPath = self.launchPath;
271
272    Vector<String> arguments;
273    arguments.reserveInitialCapacity(self.arguments.count);
274    for (NSString *argument in self.arguments)
275        arguments.uncheckedAppend(argument);
276
277    if (PluginProcess::shared().launchProcess(launchPath, arguments))
278        return;
279
280    NSConcreteTask_launch(self, _cmd);
281}
282
283static NSRunningApplication *(*NSWorkspace_launchApplicationAtURL_options_configuration_error)(NSWorkspace *, SEL, NSURL *, NSWorkspaceLaunchOptions, NSDictionary *, NSError **);
284
285static NSRunningApplication *replacedNSWorkspace_launchApplicationAtURL_options_configuration_error(NSWorkspace *self, SEL _cmd, NSURL *url, NSWorkspaceLaunchOptions options, NSDictionary *configuration, NSError **error)
286{
287    Vector<String> arguments;
288    if (NSArray *argumentsArray = [configuration objectForKey:NSWorkspaceLaunchConfigurationArguments]) {
289        if ([argumentsArray isKindOfClass:[NSArray array]]) {
290            for (NSString *argument in argumentsArray) {
291                if ([argument isKindOfClass:[NSString class]])
292                    arguments.append(argument);
293            }
294        }
295    }
296
297    if (PluginProcess::shared().launchApplicationAtURL(KURL(url).string(), arguments)) {
298        if (error)
299            *error = nil;
300        return nil;
301    }
302
303    return NSWorkspace_launchApplicationAtURL_options_configuration_error(self, _cmd, url, options, configuration, error);
304}
305
306static BOOL (*NSWorkspace_openFile)(NSWorkspace *, SEL, NSString *);
307
308static BOOL replacedNSWorkspace_openFile(NSWorkspace *self, SEL _cmd, NSString *fullPath)
309{
310    if (PluginProcess::shared().openFile(fullPath))
311        return true;
312
313    return NSWorkspace_openFile(self, _cmd, fullPath);
314}
315
316static void initializeCocoaOverrides()
317{
318    // Override -[NSConcreteTask launch:]
319    Method launchMethod = class_getInstanceMethod(objc_getClass("NSConcreteTask"), @selector(launch));
320    NSConcreteTask_launch = method_setImplementation(launchMethod, reinterpret_cast<IMP>(replacedNSConcreteTask_launch));
321
322    // Override -[NSWorkspace launchApplicationAtURL:options:configuration:error:]
323    Method launchApplicationAtURLOptionsConfigurationErrorMethod = class_getInstanceMethod(objc_getClass("NSWorkspace"), @selector(launchApplicationAtURL:options:configuration:error:));
324    NSWorkspace_launchApplicationAtURL_options_configuration_error = reinterpret_cast<NSRunningApplication *(*)(NSWorkspace *, SEL, NSURL *, NSWorkspaceLaunchOptions, NSDictionary *, NSError **)>(method_setImplementation(launchApplicationAtURLOptionsConfigurationErrorMethod, reinterpret_cast<IMP>(replacedNSWorkspace_launchApplicationAtURL_options_configuration_error)));
325
326    // Override -[NSWorkspace openFile:]
327    Method openFileMethod = class_getInstanceMethod(objc_getClass("NSWorkspace"), @selector(openFile:));
328    NSWorkspace_openFile = reinterpret_cast<BOOL (*)(NSWorkspace *, SEL, NSString *)>(method_setImplementation(openFileMethod, reinterpret_cast<IMP>(replacedNSWorkspace_openFile)));
329
330    // Override -[NSApplication runModalForWindow:]
331    Method runModalForWindowMethod = class_getInstanceMethod(objc_getClass("NSApplication"), @selector(runModalForWindow:));
332    NSApplication_RunModalForWindow = method_setImplementation(runModalForWindowMethod, reinterpret_cast<IMP>(replacedRunModalForWindow));
333
334    NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
335
336    // Track when any Cocoa window is about to be be shown.
337    id orderOnScreenObserver = [defaultCenter addObserverForName:WKWindowWillOrderOnScreenNotification()
338                                                          object:nil
339                                                           queue:nil
340                                                           usingBlock:^(NSNotification *notification) { fullscreenWindowTracker().windowShown([notification object]); }];
341    // Track when any Cocoa window is about to be hidden.
342    id orderOffScreenObserver = [defaultCenter addObserverForName:WKWindowWillOrderOffScreenNotification()
343                                                           object:nil
344                                                            queue:nil
345                                                       usingBlock:^(NSNotification *notification) { fullscreenWindowTracker().windowHidden([notification object]); }];
346
347    // Leak the two observers so that they observe notifications for the lifetime of the process.
348    CFRetain(orderOnScreenObserver);
349    CFRetain(orderOffScreenObserver);
350}
351
352void PluginProcess::setModalWindowIsShowing(bool modalWindowIsShowing)
353{
354    parentProcessConnection()->send(Messages::PluginProcessProxy::SetModalWindowIsShowing(modalWindowIsShowing), 0);
355}
356
357void PluginProcess::setFullscreenWindowIsShowing(bool fullscreenWindowIsShowing)
358{
359    parentProcessConnection()->send(Messages::PluginProcessProxy::SetFullscreenWindowIsShowing(fullscreenWindowIsShowing), 0);
360}
361
362bool PluginProcess::launchProcess(const String& launchPath, const Vector<String>& arguments)
363{
364    bool result;
365    if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::LaunchProcess(launchPath, arguments), Messages::PluginProcessProxy::LaunchProcess::Reply(result), 0))
366        return false;
367
368    return result;
369}
370
371bool PluginProcess::launchApplicationAtURL(const String& urlString, const Vector<String>& arguments)
372{
373    bool result = false;
374    if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::LaunchApplicationAtURL(urlString, arguments), Messages::PluginProcessProxy::LaunchProcess::Reply(result), 0))
375        return false;
376
377    return result;
378}
379
380bool PluginProcess::openURL(const String& urlString, int32_t& status, String& launchedURLString)
381{
382    bool result;
383    if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::OpenURL(urlString), Messages::PluginProcessProxy::OpenURL::Reply(result, status, launchedURLString), 0))
384        return false;
385
386    return result;
387}
388
389bool PluginProcess::openFile(const String& fullPath)
390{
391    bool result;
392    if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::OpenFile(fullPath), Messages::PluginProcessProxy::OpenFile::Reply(result), 0))
393        return false;
394
395    return result;
396}
397
398static void muteAudio(void)
399{
400    AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyProcessIsAudible, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
401    UInt32 propertyData = 0;
402    OSStatus result = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, 0, sizeof(UInt32), &propertyData);
403    ASSERT_UNUSED(result, result == noErr);
404}
405
406void PluginProcess::platformInitializePluginProcess(const PluginProcessCreationParameters& parameters)
407{
408    m_compositingRenderServerPort = parameters.acceleratedCompositingPort.port();
409    if (parameters.processType == PluginProcessTypeSnapshot)
410        muteAudio();
411}
412
413void PluginProcess::platformInitializeProcess(const ChildProcessInitializationParameters& parameters)
414{
415#if defined(__i386__)
416    // Initialize the shim.
417    initializeShim();
418#endif
419
420    // Initialize Cocoa overrides.
421    initializeCocoaOverrides();
422
423    // FIXME: It would be better to proxy SetCursor calls over to the UI process instead of
424    // allowing plug-ins to change the mouse cursor at any time.
425    WKEnableSettingCursorWhenInBackground();
426
427#if defined(__i386__)
428    NSDictionary *defaults = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"AppleMagnifiedMode", nil];
429    [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
430    [defaults release];
431#endif
432
433    RetainPtr<CFURLRef> pluginURL = adoptCF(CFURLCreateWithFileSystemPath(0, m_pluginPath.createCFString().get(), kCFURLPOSIXPathStyle, false));
434    if (!pluginURL)
435        return;
436
437    RetainPtr<CFBundleRef> pluginBundle = adoptCF(CFBundleCreate(kCFAllocatorDefault, pluginURL.get()));
438    if (!pluginBundle)
439        return;
440
441    m_pluginBundleIdentifier = CFBundleGetIdentifier(pluginBundle.get());
442
443    // FIXME: Workaround for Java not liking its plugin process to be supressed - <rdar://problem/14267843>
444    if (m_pluginBundleIdentifier == "com.oracle.java.JavaAppletPlugin")
445        incrementActiveTaskCount();
446}
447
448void PluginProcess::initializeProcessName(const ChildProcessInitializationParameters& parameters)
449{
450    NSString *applicationName = [NSString stringWithFormat:WEB_UI_STRING("%@ (%@ Internet plug-in)", "visible name of the plug-in host process. The first argument is the plug-in name and the second argument is the application name."), [[(NSString *)m_pluginPath lastPathComponent] stringByDeletingPathExtension], (NSString *)parameters.uiProcessName];
451    WKSetVisibleApplicationName((CFStringRef)applicationName);
452#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090
453    if (!m_pluginBundleIdentifier.isEmpty())
454        WKSetApplicationInformationItem(kLSPlugInBundleIdentifierKey, m_pluginBundleIdentifier.createCFString().get());
455#endif
456}
457
458void PluginProcess::initializeSandbox(const ChildProcessInitializationParameters& parameters, SandboxInitializationParameters& sandboxParameters)
459{
460    // PluginProcess may already be sandboxed if its parent process was sandboxed, and launched a child process instead of an XPC service.
461    // This is generally not expected, however we currently always spawn a child process to create a MIME type preferences file.
462    if (processIsSandboxed(getpid())) {
463        RELEASE_ASSERT(!parameters.connectionIdentifier.xpcConnection);
464        RELEASE_ASSERT(processIsSandboxed(getppid()));
465        return;
466    }
467
468    bool parentIsSandboxed = parameters.connectionIdentifier.xpcConnection && processIsSandboxed(xpc_connection_get_pid(parameters.connectionIdentifier.xpcConnection));
469
470    if (parameters.extraInitializationData.get("disable-sandbox") == "1") {
471        if (parentIsSandboxed) {
472            WTFLogAlways("Sandboxed processes may not disable plug-in sandbox, terminating %s.", parameters.clientIdentifier.utf8().data());
473            exit(EX_OSERR);
474        }
475        return;
476    }
477
478    String sandboxProfile = pluginSandboxProfile(m_pluginBundleIdentifier);
479    if (sandboxProfile.isEmpty()) {
480        if (parentIsSandboxed) {
481            WTFLogAlways("Sandboxed processes may only use sandboxed plug-ins, terminating %s.", parameters.clientIdentifier.utf8().data());
482            exit(EX_OSERR);
483        }
484        return;
485    }
486
487    sandboxParameters.setSandboxProfile(sandboxProfile);
488
489#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080
490    // Use private temporary and cache directories.
491    char temporaryDirectory[PATH_MAX];
492    if (!confstr(_CS_DARWIN_USER_TEMP_DIR, temporaryDirectory, sizeof(temporaryDirectory))) {
493        WTFLogAlways("PluginProcess: couldn't retrieve system temporary directory path: %d\n", errno);
494        exit(EX_OSERR);
495    }
496
497    if (strlcpy(temporaryDirectory, [[[[NSFileManager defaultManager] stringWithFileSystemRepresentation:temporaryDirectory length:strlen(temporaryDirectory)] stringByAppendingPathComponent:@"WebKitPlugin-XXXXXX"] fileSystemRepresentation], sizeof(temporaryDirectory)) >= sizeof(temporaryDirectory)
498        || !mkdtemp(temporaryDirectory)) {
499        WTFLogAlways("PluginProcess: couldn't create private temporary directory '%s'\n", temporaryDirectory);
500        exit(EX_OSERR);
501    }
502
503    sandboxParameters.setSystemDirectorySuffix([[[[NSFileManager defaultManager] stringWithFileSystemRepresentation:temporaryDirectory length:strlen(temporaryDirectory)] lastPathComponent] fileSystemRepresentation]);
504#endif
505
506    sandboxParameters.addPathParameter("PLUGIN_PATH", m_pluginPath);
507
508    RetainPtr<CFStringRef> cachePath = adoptCF(WKCopyFoundationCacheDirectory());
509    sandboxParameters.addPathParameter("NSURL_CACHE_DIR", (NSString *)cachePath.get());
510
511    RetainPtr<NSDictionary> defaults = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"NSUseRemoteSavePanel", nil]);
512    [[NSUserDefaults standardUserDefaults] registerDefaults:defaults.get()];
513
514    ChildProcess::initializeSandbox(parameters, sandboxParameters);
515}
516
517
518void PluginProcess::stopRunLoop()
519{
520    ChildProcess::stopNSAppRunLoop();
521}
522
523} // namespace WebKit
524
525#endif // ENABLE(PLUGIN_PROCESS)
526