| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.extbrowser.plugins.chrome; |
| |
| import java.awt.Dialog; |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import javax.swing.JButton; |
| |
| import org.json.simple.JSONObject; |
| import org.netbeans.modules.extbrowser.plugins.ExtensionManager; |
| import org.netbeans.modules.extbrowser.plugins.ExtensionManager.ExtensitionStatus; |
| import org.netbeans.modules.extbrowser.plugins.ExtensionManagerAccessor; |
| import org.netbeans.modules.extbrowser.plugins.ExternalBrowserPlugin; |
| import org.netbeans.modules.extbrowser.plugins.Message; |
| import org.netbeans.modules.extbrowser.plugins.Message.MessageType; |
| import org.netbeans.modules.extbrowser.plugins.MessageListener; |
| import org.netbeans.modules.extbrowser.plugins.Utils; |
| import org.netbeans.modules.web.browser.api.BrowserFamilyId; |
| import org.netbeans.modules.web.browser.api.WebBrowser; |
| import org.netbeans.modules.web.browser.api.WebBrowsers; |
| |
| |
| import org.openide.DialogDescriptor; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.awt.HtmlBrowser; |
| import org.openide.modules.InstalledFileLocator; |
| import org.openide.util.NbBundle; |
| import org.openide.util.Utilities; |
| |
| |
| |
| /** |
| * @author ads |
| * |
| */ |
| public class ChromeManagerAccessor implements ExtensionManagerAccessor { |
| |
| static final Logger LOGGER = Logger.getLogger(ChromeManagerAccessor.class.getName()); |
| |
| private static final String NO_WEB_STORE_SWITCH= |
| "netbeans.extbrowser.manual_chrome_plugin_install"; // NOI18N |
| |
| private static final String PLUGIN_PAGE= |
| "https://chrome.google.com/webstore/detail/netbeans-connector/hafdlehgocfcodbgjnpecfajgkeejnaa"; //NOI18N |
| |
| /* (non-Javadoc) |
| * @see org.netbeans.modules.web.plugins.ExtensionManagerAccessor#getManager() |
| */ |
| @Override |
| public BrowserExtensionManager getManager() { |
| return new ChromeExtensionManager(); |
| } |
| |
| |
| static class ChromeExtensionManager extends AbstractBrowserExtensionManager { |
| |
| private static final String VERSION = "\"version\":"; // NOI18N |
| |
| private static final String PLUGIN_PUBLIC_KEY = |
| "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgo89CrO8f/2srD2BGUP9+dG4I" + |
| "kTC2D3gzEjXITaMaQgy8B4ObVpkA3bc27qc7HB9jhVj8P51aAETr89u+AvFgCeFt" + |
| "vtva0h0oodKRC3dCkQLnEWPGi7mEKB98cRhZmQ1Wa9A9tg3plKsujwwWskaFEL/h" + |
| "O7uu7myF0qLIeuiG6wIDAQAB";// NOI18N |
| |
| private static final String EXTENSION_PATH = "modules/lib/netbeans-chrome-connector.crx"; // NOI18N |
| |
| @Override |
| public BrowserFamilyId getBrowserFamilyId() { |
| return BrowserFamilyId.CHROME; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.netbeans.modules.web.plugins.ExtensionManagerAccessor.BrowserExtensionManager#isInstalled() |
| */ |
| @Override |
| public ExtensionManager.ExtensitionStatus isInstalled() { |
| // Allows to skip the detection of Chrome extension |
| String status = System.getProperty("netbeans.chrome.connector.status"); // NOI18N |
| if (status != null) { |
| try { |
| return ExtensionManager.ExtensitionStatus.valueOf(status); |
| } catch (IllegalArgumentException iaex) { |
| LOGGER.log(Level.INFO, iaex.getMessage(), iaex); |
| } |
| } |
| while (true) { |
| ExtensionManager.ExtensitionStatus result = isInstalledImpl(); |
| if (result == ExtensionManager.ExtensitionStatus.DISABLED) { |
| NotifyDescriptor descriptor = new NotifyDescriptor.Message( |
| NbBundle.getMessage(ChromeExtensionManager.class, |
| "LBL_ChromePluginIsDisabled"), // NOI18N |
| NotifyDescriptor.ERROR_MESSAGE); |
| descriptor.setTitle(NbBundle.getMessage(ChromeExtensionManager.class, |
| "TTL_ChromePluginIsDisabled")); // NOI18N |
| if (DialogDisplayer.getDefault().notify(descriptor) != DialogDescriptor.OK_OPTION) { |
| return result; |
| } |
| continue; |
| } |
| return result; |
| } |
| } |
| |
| private ExtensionManager.ExtensitionStatus isInstalledImpl() { |
| JSONObject preferences = findPreferences(); |
| LOGGER.log(Level.FINE, "Chrome preferences: {0}", preferences); |
| if (preferences == null) { |
| return ExtensionManager.ExtensitionStatus.MISSING; |
| } |
| LOGGER.log(Level.FINE, "Chrome preferences -> extensions: {0}", preferences.get("extensions")); |
| JSONObject extensions = (JSONObject)preferences.get("extensions"); |
| if (extensions == null) { |
| return ExtensionManager.ExtensitionStatus.MISSING; |
| } |
| LOGGER.log(Level.FINE, "Chrome preferences -> extensions -> settings: {0}", extensions.get("settings")); |
| JSONObject settings = (JSONObject)extensions.get("settings"); |
| if (settings == null) { |
| return ExtensionManager.ExtensitionStatus.MISSING; |
| } |
| for (Object item : settings.entrySet()) { |
| Map.Entry e = (Map.Entry)item; |
| Object value = e.getValue(); |
| LOGGER.log(Level.FINE, "Chrome preferences - extensions -> settings -> value/extension: {0}", value); |
| // #251250 |
| if (value instanceof JSONObject) { |
| JSONObject extension = (JSONObject) value; |
| String path = (String)extension.get("path"); |
| if (path != null && (path.contains("/extbrowser.chrome/plugins/chrome") |
| || path.contains("\\extbrowser.chrome\\plugins\\chrome"))) |
| { |
| return ExtensionManager.ExtensitionStatus.INSTALLED; |
| } |
| LOGGER.log(Level.FINE, "Chrome preferences - extensions -> settings -> value/extension -> manifest: {0}", extension.get("manifest")); |
| JSONObject manifest = (JSONObject)extension.get("manifest"); |
| if (manifest != null && PLUGIN_PUBLIC_KEY.equals((String)manifest.get("key"))) { |
| String version = (String)manifest.get("version"); |
| if (isUpdateRequired( version )){ |
| return ExtensionManager.ExtensitionStatus.NEEDS_UPGRADE; |
| } |
| Number n = (Number)extension.get("state"); |
| if (n != null && n.intValue() != 1) { |
| return ExtensionManager.ExtensitionStatus.DISABLED; |
| } |
| return ExtensionManager.ExtensitionStatus.INSTALLED; |
| } |
| } |
| } |
| return ExtensionManager.ExtensitionStatus.MISSING; |
| } |
| |
| private JSONObject findPreferences() { |
| File defaultProfile = getDefaultProfile(); |
| LOGGER.log(Level.FINE, "Chrome default profile: {0}", defaultProfile); |
| if (defaultProfile == null) { |
| return null; |
| } |
| |
| String[] prefFiles = new String[]{"secure preferences", "protected preferences", "preferences"}; // NOI18N |
| for (String prefFile : prefFiles) { |
| File[] prefs = defaultProfile.listFiles(new FileFinder(prefFile)); |
| if (prefs != null |
| && prefs.length > 0) { |
| JSONObject preferences = Utils.readFile(prefs[0]); |
| if (preferences != null |
| && preferences.get("extensions") != null) { // NOI18N |
| LOGGER.log(Level.FINE, "Chrome preferences file: {0}", prefs[0]); |
| return preferences; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public boolean install( ExtensionManager.ExtensitionStatus currentStatus ) |
| { |
| File extensionFile = InstalledFileLocator.getDefault().locate( |
| EXTENSION_PATH,PLUGIN_MODULE_NAME, false); |
| |
| if ( extensionFile == null ){ |
| Logger.getLogger(ChromeExtensionManager.class.getCanonicalName()). |
| severe("Could not find chrome extension in installation directory"); // NOI18N |
| return false; |
| } |
| |
| String useManualInstallation = System |
| .getProperty(NO_WEB_STORE_SWITCH); |
| if (useManualInstallation != null) { |
| return manualInstallPluginDialog(currentStatus, extensionFile); |
| } |
| else { |
| return alertGoogleWebStore(currentStatus); |
| } |
| |
| |
| /* NotifyDescriptor installDesc = new NotifyDescriptor.Confirmation( |
| NbBundle.getMessage(ChromeExtensionManager.class, |
| currentStatus == ExtensionManager.ExtensitionStatus.MISSING ? |
| "LBL_InstallMsg" : "LBL_UpgradeMsg"), // NOI18N |
| NbBundle.getMessage(ChromeExtensionManager.class, |
| "TTL_InstallExtension"), // NOI18N |
| NotifyDescriptor.OK_CANCEL_OPTION, |
| NotifyDescriptor.QUESTION_MESSAGE); |
| Object result = DialogDisplayer.getDefault().notify(installDesc); |
| if (result != NotifyDescriptor.OK_OPTION) { |
| return false; |
| } |
| |
| try { |
| loader.requestPluginLoad( new URL("file:///"+extensionFile.getCanonicalPath())); |
| } |
| catch( IOException e ){ |
| Logger.getLogger( ChromeExtensionManager.class.getCanonicalName()). |
| log(Level.INFO , null ,e ); |
| return false; |
| } |
| return true;*/ |
| } |
| |
| @Override |
| protected String getCurrentPluginVersion(){ |
| File extensionFile = InstalledFileLocator.getDefault().locate( |
| EXTENSION_PATH,PLUGIN_MODULE_NAME, false); |
| if (extensionFile == null) { |
| Logger.getLogger(ChromeExtensionManager.class.getCanonicalName()). |
| info("Could not find chrome extension in installation directory!"); // NOI18N |
| return null; |
| } |
| String content = Utils.readZip( extensionFile, "manifest.json"); // NOI18N |
| int index = content.indexOf(VERSION); |
| if ( index == -1){ |
| return null; |
| } |
| index = content.indexOf(',',index); |
| return getValue(content, 0, index , VERSION); |
| } |
| |
| private String getValue(String content, int start , int end , String key){ |
| String part = content.substring( start , end ); |
| int index = part.indexOf(key); |
| if ( index == -1 ){ |
| return null; |
| } |
| String value = part.substring( index +key.length() ).trim(); |
| return Utils.unquote(value); |
| } |
| |
| private File getDefaultProfile() { |
| String[] userData = getUserData(); |
| LOGGER.log(Level.FINE, "Chrome user data: {0}", Arrays.toString(userData)); |
| if ( userData != null ){ |
| for (String dataDir : userData) { |
| File dir = new File(dataDir); |
| if (dir.isDirectory() && dir.exists()) { |
| File[] localState = dir.listFiles( |
| new FileFinder("local state")); // NOI18N |
| boolean guessDefault = localState == null || |
| localState.length == 0; |
| |
| if ( !guessDefault ) { |
| JSONObject localStateContent = Utils.readFile( localState[0]); |
| if (localStateContent != null) { |
| JSONObject profile = (JSONObject)localStateContent.get("profile"); |
| if (profile != null) { |
| String prof = (String)profile.get("last_used"); |
| if (prof == null) { |
| guessDefault = true; |
| } else { |
| prof = Utils.unquote(prof); |
| File[] listFiles = dir.listFiles( new FileFinder( |
| prof , true)); |
| if ( listFiles != null && listFiles.length >0 ){ |
| return listFiles[0]; |
| } else { |
| guessDefault = true; |
| } |
| } |
| } |
| } |
| } |
| |
| if( guessDefault ) { |
| File[] listFiles = dir.listFiles( |
| new FileFinder("default")); // NOI18N |
| if ( listFiles!= null && listFiles.length >0 ) { |
| return listFiles[0]; |
| } |
| } |
| |
| } |
| } |
| } |
| return null; |
| } |
| |
| protected String[] getUserData(){ |
| // see http://www.chromium.org/user-experience/user-data-directory |
| // TODO - this will not work for Chromium on Windows and Mac |
| if (Utilities.isWindows()) { |
| ArrayList<String> result = new ArrayList<String>(); |
| String localAppData = System.getenv("LOCALAPPDATA"); // NOI18N |
| if (localAppData != null) { |
| result.add(localAppData+"\\Google\\Chrome\\User Data"); |
| } else { |
| localAppData = Utils.getLOCALAPPDATAonWinXP(); |
| if (localAppData != null) { |
| result.add(localAppData+"\\Google\\Chrome\\User Data"); |
| } |
| } |
| String appData = System.getenv("APPDATA"); // NOI18N |
| if (appData != null) { |
| // we are in C:\Documents and Settings\<username>\Application Data\ on XP |
| File f = new File(appData); |
| if (f.exists()) { |
| String fName = f.getName(); |
| // #219824 - below code will not work on some localized WinXP where |
| // "Local Settings" name might be "Lokale Einstellungen"; |
| // no harm if we try though: |
| f = new File(f.getParentFile(),"Local Settings"); |
| f = new File(f, fName); |
| if (f.exists()) { |
| result.add(f.getPath()+"\\Google\\Chrome\\User Data"); |
| } |
| } |
| } |
| return result.toArray(new String[result.size()]); |
| } |
| else if (Utilities.isMac()) { |
| return Utils.getUserPaths("/Library/Application Support/Google/Chrome");// NOI18N |
| } |
| else { |
| return Utils.getUserPaths("/.config/google-chrome", "/.config/chrome");// NOI18N |
| } |
| } |
| |
| private boolean manualInstallPluginDialog( |
| ExtensionManager.ExtensitionStatus currentStatus, |
| File extensionFile ) |
| { |
| String path; |
| try { |
| path = extensionFile.getCanonicalPath(); |
| } |
| catch( IOException e ){ |
| Logger.getLogger( ChromeExtensionManager.class.getCanonicalName()). |
| log(Level.INFO , null ,e ); |
| return false; |
| } |
| JButton continueButton = new JButton(NbBundle.getMessage( |
| ChromeExtensionManager.class, |
| currentStatus == ExtensionManager.ExtensitionStatus.NEEDS_UPGRADE ? |
| "LBL_ContinueUpdate" : "LBL_Continue")); // NOI18N |
| continueButton.getAccessibleContext().setAccessibleName(NbBundle. |
| getMessage(ChromeExtensionManager.class, "ACSN_Continue")); // NOI18N |
| continueButton.getAccessibleContext().setAccessibleDescription(NbBundle. |
| getMessage(ChromeExtensionManager.class, "ACSD_Continue")); // NOI18N |
| DialogDescriptor descriptor = new DialogDescriptor( |
| new ChromeInfoPanel(path, currentStatus), |
| NbBundle.getMessage(ChromeExtensionManager.class, |
| currentStatus == ExtensionManager.ExtensitionStatus.NEEDS_UPGRADE ? |
| "TTL_UpdateExtension" : "TTL_InstallExtension"), true, |
| new Object[]{continueButton, |
| DialogDescriptor.CANCEL_OPTION}, continueButton, |
| DialogDescriptor.DEFAULT_ALIGN, null, null); |
| InstallInfoReceiver receiver = new InstallInfoReceiver(); |
| ExternalBrowserPlugin.getInstance().addMessageListener(receiver); |
| while (true) { |
| Object result = DialogDisplayer.getDefault().notify(descriptor); |
| if (result == continueButton) { |
| if ( receiver.isInstalled() ){ |
| return true; |
| } |
| ExtensitionStatus status = isInstalled(); |
| if (status == ExtensitionStatus.INSTALLED){ |
| return true; |
| } |
| } else { |
| return false; |
| } |
| } |
| } |
| |
| private boolean alertGoogleWebStore( |
| ExtensionManager.ExtensitionStatus currentStatus) |
| { |
| // #221325 |
| if (currentStatus == ExtensionManager.ExtensitionStatus.MISSING) { |
| return alertGoogleWebStoreInstall(currentStatus); |
| } |
| // update |
| return alertGoogleWebStoreUpdate(currentStatus); |
| } |
| |
| private boolean alertGoogleWebStoreInstall(final |
| ExtensionManager.ExtensitionStatus currentStatus) |
| { |
| final File extensionFile = InstalledFileLocator.getDefault().locate( |
| EXTENSION_PATH,PLUGIN_MODULE_NAME, false); |
| String path=""; |
| try { |
| path = extensionFile.getParentFile().toURI().toURL().toExternalForm(); |
| } |
| catch( MalformedURLException e ){ |
| Logger.getLogger(ChromeExtensionManager.class.getName()).log( |
| Level.WARNING, null, e); |
| } |
| final Dialog[] dialogs = new Dialog[1]; |
| final boolean result[] = new boolean[1]; |
| DialogDescriptor descriptor = new DialogDescriptor( |
| new WebStorePanel(false, path, new Runnable() { |
| |
| @Override |
| public void run() { |
| InstallInfoReceiver receiver = new InstallInfoReceiver(); |
| ExternalBrowserPlugin.getInstance(). |
| addMessageListener(receiver); |
| try { |
| // #228605 |
| openInProperBrowser(URI.create(PLUGIN_PAGE).toURL()); |
| } |
| catch( MalformedURLException e ){ |
| Logger.getLogger(ChromeExtensionManager.class.getName()).log( |
| Level.WARNING, null, e); |
| } |
| dialogs[0].setVisible(false); |
| dialogs[0].dispose(); |
| result[0] = createReRun(currentStatus, extensionFile, |
| receiver); |
| } |
| |
| private void openInProperBrowser(URL url) { |
| for (WebBrowser browser : WebBrowsers.getInstance().getAll(true, true, true)) { |
| if (browser.hasNetBeansIntegration()) { |
| // ignore it otherwise it will check the extension status and reopen just the same dialog |
| continue; |
| } |
| if (browser.getBrowserFamily() == getBrowserFamilyId()) { |
| browser.createNewBrowserPane().showURL(url); |
| return; |
| } |
| } |
| // fallback |
| HtmlBrowser.URLDisplayer.getDefault().showURL(url); |
| } |
| |
| }, new Runnable() { |
| |
| @Override |
| public void run() { |
| dialogs[0].setVisible(false); |
| dialogs[0].dispose(); |
| result[0] = manualInstallPluginDialog(currentStatus, |
| extensionFile); |
| } |
| }), |
| NbBundle.getMessage(ChromeExtensionManager.class, |
| "TTL_InstallExtension"), true, |
| new Object[]{DialogDescriptor.CANCEL_OPTION}, |
| DialogDescriptor.CANCEL_OPTION, |
| DialogDescriptor.DEFAULT_ALIGN, null, null); |
| Dialog dialog = DialogDisplayer.getDefault().createDialog(descriptor); |
| dialogs[0] = dialog; |
| dialog.setVisible(true); |
| return result[0]; |
| } |
| |
| private boolean createReRun(final |
| ExtensionManager.ExtensitionStatus currentStatus, |
| final File extensionFile, final InstallInfoReceiver receiver) |
| { |
| final Dialog[] dialogs = new Dialog[1]; |
| final boolean result[] = new boolean[1]; |
| DialogDescriptor descriptor = new DialogDescriptor( |
| new WebStorePanel(true, null, new Runnable() { |
| |
| @Override |
| public void run() { |
| ExtensitionStatus status = isInstalled(); |
| if ( receiver.isInstalled() || |
| status== ExtensitionStatus.INSTALLED) |
| { |
| result[0] = true; |
| dialogs[0].setVisible(false); |
| dialogs[0].dispose(); |
| } |
| } |
| }, new Runnable() { |
| |
| @Override |
| public void run() { |
| dialogs[0].setVisible(false); |
| dialogs[0].dispose(); |
| result[0] = manualInstallPluginDialog(currentStatus, extensionFile); |
| } |
| }), |
| NbBundle.getMessage(ChromeExtensionManager.class, |
| "TTL_InstallExtension"), true, |
| new Object[]{DialogDescriptor.CANCEL_OPTION}, |
| DialogDescriptor.CANCEL_OPTION, |
| DialogDescriptor.DEFAULT_ALIGN, null, null); |
| Dialog dialog = DialogDisplayer.getDefault().createDialog(descriptor); |
| dialogs[0] = dialog; |
| dialog.setVisible(true); |
| return result[0]; |
| } |
| |
| private boolean alertGoogleWebStoreUpdate( |
| final ExtensionManager.ExtensitionStatus currentStatus) |
| { |
| final Dialog[] dialogs = new Dialog[1]; |
| final boolean[] result = new boolean[1]; |
| DialogDescriptor descriptor = new DialogDescriptor( |
| new WebStorePanel( new Runnable() { |
| |
| @Override |
| public void run() { |
| ExtensitionStatus status = isInstalled(); |
| if ( status!= ExtensitionStatus.INSTALLED){ |
| return; |
| } |
| result[0] = true; |
| dialogs[0].setVisible(false); |
| dialogs[0].dispose(); |
| } |
| } ), |
| NbBundle.getMessage(ChromeExtensionManager.class, "TTL_UpdateExtension"), |
| true, |
| new Object[]{DialogDescriptor.CANCEL_OPTION}, |
| DialogDescriptor.CANCEL_OPTION, |
| DialogDescriptor.DEFAULT_ALIGN, null, null); |
| Dialog dialog = DialogDisplayer.getDefault().createDialog(descriptor); |
| dialogs[0] = dialog; |
| dialog.setVisible( true); |
| return result[0]; |
| } |
| |
| private static class FileFinder implements FileFilter { |
| FileFinder(String name){ |
| this( name, false ); |
| } |
| |
| FileFinder(String name , boolean caseSensitive ){ |
| myName = name; |
| isCaseSensitive = caseSensitive; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.io.FileFilter#accept(java.io.File) |
| */ |
| @Override |
| public boolean accept( File file ) { |
| if ( isCaseSensitive ){ |
| return file.getName().equals( myName); |
| } |
| else { |
| return file.getName().toLowerCase(Locale.US).equals( myName); |
| } |
| } |
| private String myName; |
| private boolean isCaseSensitive; |
| } |
| } |
| |
| static class InstallInfoReceiver implements MessageListener { |
| |
| /* (non-Javadoc) |
| * @see org.netbeans.modules.extbrowser.plugins.MessageListener#messageReceived(org.netbeans.modules.extbrowser.plugins.Message) |
| */ |
| @Override |
| public void messageReceived( Message message ) { |
| if ( message.getType()==MessageType.READY){ |
| isInstalled = true; |
| } |
| } |
| |
| public boolean isInstalled(){ |
| return isInstalled; |
| } |
| |
| private volatile boolean isInstalled; |
| } |
| } |