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(NETSCAPE_PLUGIN_API)
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#import <wtf/NeverDestroyed.h>
48
49using namespace WebCore;
50
51const CFStringRef kLSPlugInBundleIdentifierKey = CFSTR("LSPlugInBundleIdentifierKey");
52
53// These values were chosen to match default NSURLCache sizes at the time of this writing.
54const NSUInteger pluginMemoryCacheSize = 512000;
55const NSUInteger pluginDiskCacheSize = 20000000;
56
57namespace WebKit {
58
59class FullscreenWindowTracker {
60    WTF_MAKE_NONCOPYABLE(FullscreenWindowTracker);
61
62public:
63    FullscreenWindowTracker() { }
64
65    template<typename T> void windowShown(T window);
66    template<typename T> void windowHidden(T window);
67
68private:
69    typedef HashSet<void*> WindowSet;
70    WindowSet m_windows;
71};
72
73static bool rectCoversAnyScreen(NSRect rect)
74{
75    for (NSScreen *screen in [NSScreen screens]) {
76        if (NSContainsRect(rect, [screen frame]))
77            return YES;
78    }
79    return NO;
80}
81
82#ifndef NP_NO_CARBON
83static bool windowCoversAnyScreen(WindowRef window)
84{
85    HIRect bounds;
86    HIWindowGetBounds(window, kWindowStructureRgn, kHICoordSpaceScreenPixel, &bounds);
87
88    // Convert to Cocoa-style screen coordinates that use a Y offset relative to the zeroth screen's origin.
89    bounds.origin.y = NSHeight([(NSScreen *)[[NSScreen screens] objectAtIndex:0] frame]) - CGRectGetMaxY(bounds);
90
91    return rectCoversAnyScreen(NSRectFromCGRect(bounds));
92}
93#endif
94
95static bool windowCoversAnyScreen(NSWindow* window)
96{
97    return rectCoversAnyScreen([window frame]);
98}
99
100template<typename T> void FullscreenWindowTracker::windowShown(T window)
101{
102    // If this window is already visible then there is nothing to do.
103    WindowSet::iterator it = m_windows.find(window);
104    if (it != m_windows.end())
105        return;
106
107    // If the window is not full-screen then we're not interested in it.
108    if (!windowCoversAnyScreen(window))
109        return;
110
111    bool windowSetWasEmpty = m_windows.isEmpty();
112
113    m_windows.add(window);
114
115    // If this is the first full screen window to be shown, notify the UI process.
116    if (windowSetWasEmpty)
117        PluginProcess::shared().setFullscreenWindowIsShowing(true);
118}
119
120template<typename T> void FullscreenWindowTracker::windowHidden(T window)
121{
122    // If this is not a window that we're tracking then there is nothing to do.
123    WindowSet::iterator it = m_windows.find(window);
124    if (it == m_windows.end())
125        return;
126
127    m_windows.remove(it);
128
129    // If this was the last full screen window that was visible, notify the UI process.
130    if (m_windows.isEmpty())
131        PluginProcess::shared().setFullscreenWindowIsShowing(false);
132}
133
134static FullscreenWindowTracker& fullscreenWindowTracker()
135{
136    static NeverDestroyed<FullscreenWindowTracker> fullscreenWindowTracker;
137    return fullscreenWindowTracker;
138}
139
140#if defined(__i386__)
141
142static pthread_once_t shouldCallRealDebuggerOnce = PTHREAD_ONCE_INIT;
143static bool isUserbreakSet = false;
144
145static void initShouldCallRealDebugger()
146{
147    char* var = getenv("USERBREAK");
148
149    if (var)
150        isUserbreakSet = atoi(var);
151}
152
153static bool shouldCallRealDebugger()
154{
155    pthread_once(&shouldCallRealDebuggerOnce, initShouldCallRealDebugger);
156
157    return isUserbreakSet;
158}
159
160static bool isWindowActive(WindowRef windowRef, bool& result)
161{
162#ifndef NP_NO_CARBON
163    if (NetscapePlugin* plugin = NetscapePlugin::netscapePluginFromWindow(windowRef)) {
164        result = plugin->isWindowActive();
165        return true;
166    }
167#endif
168    return false;
169}
170
171static UInt32 getCurrentEventButtonState()
172{
173#ifndef NP_NO_CARBON
174    return NetscapePlugin::buttonState();
175#else
176    ASSERT_NOT_REACHED();
177    return 0;
178#endif
179}
180
181static void carbonWindowShown(WindowRef window)
182{
183#ifndef NP_NO_CARBON
184    fullscreenWindowTracker().windowShown(window);
185#endif
186}
187
188static void carbonWindowHidden(WindowRef window)
189{
190#ifndef NP_NO_CARBON
191    fullscreenWindowTracker().windowHidden(window);
192#endif
193}
194
195static bool openCFURLRef(CFURLRef url, int32_t& status, CFURLRef* launchedURL)
196{
197    String launchedURLString;
198    if (!PluginProcess::shared().openURL(URL(url).string(), status, launchedURLString))
199        return false;
200
201    if (!launchedURLString.isNull() && launchedURL)
202        *launchedURL = URL(ParsedURLString, launchedURLString).createCFURL().leakRef();
203    return true;
204}
205
206#endif
207
208static void setModal(bool modalWindowIsShowing)
209{
210    PluginProcess::shared().setModalWindowIsShowing(modalWindowIsShowing);
211}
212
213static unsigned modalCount = 0;
214
215static void beginModal()
216{
217#pragma clang diagnostic push
218#pragma clang diagnostic ignored "-Wdeprecated-declarations"
219    // Make sure to make ourselves the front process
220    ProcessSerialNumber psn;
221    GetCurrentProcess(&psn);
222    SetFrontProcess(&psn);
223#pragma clang diagnostic pop
224
225    if (!modalCount++)
226        setModal(true);
227}
228
229static void endModal()
230{
231    if (!--modalCount)
232        setModal(false);
233}
234
235static IMP NSApplication_RunModalForWindow;
236
237static NSInteger replacedRunModalForWindow(id self, SEL _cmd, NSWindow* window)
238{
239    beginModal();
240    NSInteger result = ((NSInteger (*)(id, SEL, NSWindow *))NSApplication_RunModalForWindow)(self, _cmd, window);
241    endModal();
242
243    return result;
244}
245
246#if defined(__i386__)
247static void initializeShim()
248{
249    // Initialize the shim for 32-bit only.
250    const PluginProcessShimCallbacks callbacks = {
251        shouldCallRealDebugger,
252        isWindowActive,
253        getCurrentEventButtonState,
254        beginModal,
255        endModal,
256        carbonWindowShown,
257        carbonWindowHidden,
258        setModal,
259        openCFURLRef,
260    };
261
262    PluginProcessShimInitializeFunc initFunc = reinterpret_cast<PluginProcessShimInitializeFunc>(dlsym(RTLD_DEFAULT, "WebKitPluginProcessShimInitialize"));
263    initFunc(callbacks);
264}
265#endif
266
267static IMP NSConcreteTask_launch;
268
269static void replacedNSConcreteTask_launch(NSTask *self, SEL _cmd)
270{
271    String launchPath = self.launchPath;
272
273    Vector<String> arguments;
274    arguments.reserveInitialCapacity(self.arguments.count);
275    for (NSString *argument in self.arguments)
276        arguments.uncheckedAppend(argument);
277
278    if (PluginProcess::shared().launchProcess(launchPath, arguments))
279        return;
280
281    NSConcreteTask_launch(self, _cmd);
282}
283
284static NSRunningApplication *(*NSWorkspace_launchApplicationAtURL_options_configuration_error)(NSWorkspace *, SEL, NSURL *, NSWorkspaceLaunchOptions, NSDictionary *, NSError **);
285
286static NSRunningApplication *replacedNSWorkspace_launchApplicationAtURL_options_configuration_error(NSWorkspace *self, SEL _cmd, NSURL *url, NSWorkspaceLaunchOptions options, NSDictionary *configuration, NSError **error)
287{
288    Vector<String> arguments;
289    if (NSArray *argumentsArray = [configuration objectForKey:NSWorkspaceLaunchConfigurationArguments]) {
290        if ([argumentsArray isKindOfClass:[NSArray array]]) {
291            for (NSString *argument in argumentsArray) {
292                if ([argument isKindOfClass:[NSString class]])
293                    arguments.append(argument);
294            }
295        }
296    }
297
298    if (PluginProcess::shared().launchApplicationAtURL(URL(url).string(), arguments)) {
299        if (error)
300            *error = nil;
301        return nil;
302    }
303
304    return NSWorkspace_launchApplicationAtURL_options_configuration_error(self, _cmd, url, options, configuration, error);
305}
306
307static BOOL (*NSWorkspace_openFile)(NSWorkspace *, SEL, NSString *);
308
309static BOOL replacedNSWorkspace_openFile(NSWorkspace *self, SEL _cmd, NSString *fullPath)
310{
311    if (PluginProcess::shared().openFile(fullPath))
312        return true;
313
314    return NSWorkspace_openFile(self, _cmd, fullPath);
315}
316
317static void initializeCocoaOverrides()
318{
319    // Override -[NSConcreteTask launch:]
320    Method launchMethod = class_getInstanceMethod(objc_getClass("NSConcreteTask"), @selector(launch));
321    NSConcreteTask_launch = method_setImplementation(launchMethod, reinterpret_cast<IMP>(replacedNSConcreteTask_launch));
322
323    // Override -[NSWorkspace launchApplicationAtURL:options:configuration:error:]
324    Method launchApplicationAtURLOptionsConfigurationErrorMethod = class_getInstanceMethod(objc_getClass("NSWorkspace"), @selector(launchApplicationAtURL:options:configuration:error:));
325    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)));
326
327    // Override -[NSWorkspace openFile:]
328    Method openFileMethod = class_getInstanceMethod(objc_getClass("NSWorkspace"), @selector(openFile:));
329    NSWorkspace_openFile = reinterpret_cast<BOOL (*)(NSWorkspace *, SEL, NSString *)>(method_setImplementation(openFileMethod, reinterpret_cast<IMP>(replacedNSWorkspace_openFile)));
330
331    // Override -[NSApplication runModalForWindow:]
332    Method runModalForWindowMethod = class_getInstanceMethod(objc_getClass("NSApplication"), @selector(runModalForWindow:));
333    NSApplication_RunModalForWindow = method_setImplementation(runModalForWindowMethod, reinterpret_cast<IMP>(replacedRunModalForWindow));
334
335    NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
336
337    // Track when any Cocoa window is about to be be shown.
338    id orderOnScreenObserver = [defaultCenter addObserverForName:WKWindowWillOrderOnScreenNotification()
339                                                          object:nil
340                                                           queue:nil
341                                                           usingBlock:^(NSNotification *notification) { fullscreenWindowTracker().windowShown([notification object]); }];
342    // Track when any Cocoa window is about to be hidden.
343    id orderOffScreenObserver = [defaultCenter addObserverForName:WKWindowWillOrderOffScreenNotification()
344                                                           object:nil
345                                                            queue:nil
346                                                       usingBlock:^(NSNotification *notification) { fullscreenWindowTracker().windowHidden([notification object]); }];
347
348    // Leak the two observers so that they observe notifications for the lifetime of the process.
349    CFRetain(orderOnScreenObserver);
350    CFRetain(orderOffScreenObserver);
351}
352
353void PluginProcess::setModalWindowIsShowing(bool modalWindowIsShowing)
354{
355    parentProcessConnection()->send(Messages::PluginProcessProxy::SetModalWindowIsShowing(modalWindowIsShowing), 0);
356}
357
358void PluginProcess::setFullscreenWindowIsShowing(bool fullscreenWindowIsShowing)
359{
360    parentProcessConnection()->send(Messages::PluginProcessProxy::SetFullscreenWindowIsShowing(fullscreenWindowIsShowing), 0);
361}
362
363bool PluginProcess::launchProcess(const String& launchPath, const Vector<String>& arguments)
364{
365    bool result;
366    if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::LaunchProcess(launchPath, arguments), Messages::PluginProcessProxy::LaunchProcess::Reply(result), 0))
367        return false;
368
369    return result;
370}
371
372bool PluginProcess::launchApplicationAtURL(const String& urlString, const Vector<String>& arguments)
373{
374    bool result = false;
375    if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::LaunchApplicationAtURL(urlString, arguments), Messages::PluginProcessProxy::LaunchProcess::Reply(result), 0))
376        return false;
377
378    return result;
379}
380
381bool PluginProcess::openURL(const String& urlString, int32_t& status, String& launchedURLString)
382{
383    bool result;
384    if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::OpenURL(urlString), Messages::PluginProcessProxy::OpenURL::Reply(result, status, launchedURLString), 0))
385        return false;
386
387    return result;
388}
389
390bool PluginProcess::openFile(const String& fullPath)
391{
392    bool result;
393    if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::OpenFile(fullPath), Messages::PluginProcessProxy::OpenFile::Reply(result), 0))
394        return false;
395
396    return result;
397}
398
399static void muteAudio(void)
400{
401    AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyProcessIsAudible, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
402    UInt32 propertyData = 0;
403    OSStatus result = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, 0, sizeof(UInt32), &propertyData);
404    ASSERT_UNUSED(result, result == noErr);
405}
406
407void PluginProcess::platformInitializePluginProcess(const PluginProcessCreationParameters& parameters)
408{
409    m_compositingRenderServerPort = parameters.acceleratedCompositingPort.port();
410    if (parameters.processType == PluginProcessTypeSnapshot)
411        muteAudio();
412
413    [NSURLCache setSharedURLCache:adoptNS([[NSURLCache alloc]
414        initWithMemoryCapacity:pluginMemoryCacheSize
415        diskCapacity:pluginDiskCacheSize
416        diskPath:m_nsurlCacheDirectory]).get()];
417}
418
419void PluginProcess::platformInitializeProcess(const ChildProcessInitializationParameters& parameters)
420{
421#if defined(__i386__)
422    // Initialize the shim.
423    initializeShim();
424#endif
425
426    // Initialize Cocoa overrides.
427    initializeCocoaOverrides();
428
429    // FIXME: It would be better to proxy SetCursor calls over to the UI process instead of
430    // allowing plug-ins to change the mouse cursor at any time.
431    WKEnableSettingCursorWhenInBackground();
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        (new UserActivity("com.oracle.java.JavaAppletPlugin"))->start();
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.get()));
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    char temporaryDirectory[PATH_MAX];
490    if (!confstr(_CS_DARWIN_USER_TEMP_DIR, temporaryDirectory, sizeof(temporaryDirectory))) {
491        WTFLogAlways("PluginProcess: couldn't retrieve system temporary directory path: %d\n", errno);
492        exit(EX_OSERR);
493    }
494
495    char cacheDirectory[PATH_MAX];
496    if (!confstr(_CS_DARWIN_USER_CACHE_DIR, cacheDirectory, sizeof(cacheDirectory))) {
497        WTFLogAlways("PluginProcess: couldn't retrieve system cache directory path: %d\n", errno);
498        exit(EX_OSERR);
499    }
500
501    m_nsurlCacheDirectory = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:cacheDirectory length:strlen(temporaryDirectory)] stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]];
502    if (![[NSFileManager defaultManager] createDirectoryAtURL:[NSURL fileURLWithPath:m_nsurlCacheDirectory isDirectory:YES] withIntermediateDirectories:YES attributes:nil error:nil]) {
503        WTFLogAlways("PluginProcess: couldn't create NSURL cache directory '%s'\n", temporaryDirectory);
504        exit(EX_OSERR);
505    }
506
507    if (strlcpy(temporaryDirectory, [[[[NSFileManager defaultManager] stringWithFileSystemRepresentation:temporaryDirectory length:strlen(temporaryDirectory)] stringByAppendingPathComponent:@"WebKitPlugin-XXXXXX"] fileSystemRepresentation], sizeof(temporaryDirectory)) >= sizeof(temporaryDirectory)
508        || !mkdtemp(temporaryDirectory)) {
509        WTFLogAlways("PluginProcess: couldn't create private temporary directory '%s'\n", temporaryDirectory);
510        exit(EX_OSERR);
511    }
512
513    sandboxParameters.setSystemDirectorySuffix([[[[NSFileManager defaultManager] stringWithFileSystemRepresentation:temporaryDirectory length:strlen(temporaryDirectory)] lastPathComponent] fileSystemRepresentation]);
514
515    sandboxParameters.addPathParameter("PLUGIN_PATH", m_pluginPath);
516    sandboxParameters.addPathParameter("NSURL_CACHE_DIR", m_nsurlCacheDirectory);
517
518    [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSUseRemoteSavePanel" : @YES }];
519
520    ChildProcess::initializeSandbox(parameters, sandboxParameters);
521}
522
523
524void PluginProcess::stopRunLoop()
525{
526    ChildProcess::stopNSAppRunLoop();
527}
528
529} // namespace WebKit
530
531#endif // ENABLE(NETSCAPE_PLUGIN_API)
532