/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.help.internal.protocols; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.Date; import java.util.HashMap; import java.util.Hashtable; import java.util.Locale; import java.util.StringTokenizer; import java.util.Vector; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.IProduct; import org.eclipse.core.runtime.Platform; import org.eclipse.help.internal.base.remote.HttpsUtility; import org.eclipse.help.internal.base.remote.PreferenceFileHandler; import org.eclipse.help.internal.base.remote.RemoteContentLocator; import org.eclipse.help.internal.base.remote.RemoteHelp; import org.eclipse.help.internal.base.remote.RemoteHelpInputStream; import org.eclipse.help.internal.base.util.ProxyUtil; import org.eclipse.help.internal.util.ResourceLocator; import org.eclipse.help.internal.util.URLCoder; import org.osgi.framework.Bundle; /** * URLConnection to help documents in plug-ins */ public class HelpURLConnection extends URLConnection { private final static String PARAM_LANG = "lang"; //$NON-NLS-1$ private final static String PRODUCT_PLUGIN = "PRODUCT_PLUGIN"; //$NON-NLS-1$ public final static String PLUGINS_ROOT = "PLUGINS_ROOT/"; //$NON-NLS-1$ private final static String PATH_RTOPIC = "/rtopic"; //$NON-NLS-1$ private static final String PROTOCOL_HTTP = "http://"; //$NON-NLS-1$ private static Hashtable<String, String[]> templates = new Hashtable<>(); // document caching - disabled if running in dev mode protected static boolean cachingEnabled = true; static { String[] args = Platform.getCommandLineArgs(); for (int i = 0; i < args.length; i++) { if ("-dev".equals(args[i])) { //$NON-NLS-1$ cachingEnabled = false; break; } } } protected String pluginAndFile; // plugin/file protected String query; // after ? protected HashMap<String, Object> arguments; protected Bundle plugin; // file in a plug-in protected String file; protected String locale; private static String appserverImplPluginId; private boolean localOnly; /** * Constructor for HelpURLConnection */ public HelpURLConnection(URL url) { this(url, false); } public HelpURLConnection(URL url, boolean localOnly) { super(url); this.localOnly = localOnly; String urlFile = url.getFile(); // Strip off everything before and including the PLUGINS_ROOT int index = urlFile.indexOf(PLUGINS_ROOT); if (index != -1) urlFile = urlFile.substring(index + PLUGINS_ROOT.length()); // Strip off the leading "/" and the query if (urlFile.startsWith("/")) //$NON-NLS-1$ urlFile = urlFile.substring(1); int indx = urlFile.indexOf("?"); //$NON-NLS-1$ if (indx != -1) { query = urlFile.substring(indx + 1); urlFile = urlFile.substring(0, indx); } this.pluginAndFile = urlFile; parseQuery(); setDefaultUseCaches(isCacheable()); } /** * @see URLConnection#connect() */ @Override public void connect() throws IOException { } /** * see URLConnection#getInputStream(); Note: this method can throw IOException, but should never * return null */ @Override public InputStream getInputStream() throws IOException { // must override parent implementation, since it does nothing. Bundle plugin = getPlugin(); if (plugin != null && plugin.getSymbolicName().equals(getAppserverImplPluginId())) { // Do not return documents from app server implementation plug-in throw new IOException("Resource not found."); //$NON-NLS-1$ } if (getFile() == null || "".equals(getFile()) || getFile().indexOf("..\\") >= 0) { //$NON-NLS-1$ //$NON-NLS-2$ throw new IOException("Resource not found."); //$NON-NLS-1$ } int helpOption=localOnly ? PreferenceFileHandler.LOCAL_HELP_ONLY : PreferenceFileHandler.getEmbeddedHelpOption(); InputStream in = null; if (plugin != null && (helpOption==PreferenceFileHandler.LOCAL_HELP_ONLY || helpOption==PreferenceFileHandler.LOCAL_HELP_PRIORITY)) { in = getLocalHelp(plugin); } if (in == null && (helpOption==PreferenceFileHandler.LOCAL_HELP_PRIORITY || helpOption==PreferenceFileHandler.REMOTE_HELP_PRIORITY)) { in = openFromRemoteServer(getHref(), getLocale()); if( in != null ){ in = new RemoteHelpInputStream(in); } if(in==null && plugin!=null && helpOption==PreferenceFileHandler.REMOTE_HELP_PRIORITY) { in = getLocalHelp(plugin); } } if (in == null) { throw new IOException("Resource not found."); //$NON-NLS-1$ } return in; } private InputStream getLocalHelp(Bundle plugin) { // first try using content provider, then try to find the file // inside doc.zip, and finally try the file system InputStream in = ResourceLocator.openFromProducer(plugin, query == null ? getFile() : getFile() + "?" + query, //$NON-NLS-1$ getLocale()); if (in == null) { in = ResourceLocator.openFromPlugin(plugin, getFile(), getLocale()); } if (in == null) { in = ResourceLocator.openFromZip(plugin, "doc.zip", //$NON-NLS-1$ getFile(), getLocale()); } return in; } @Override public long getExpiration() { return isCacheable() ? new Date().getTime() + 10000 : 0; } public static void parseQuery(String query, HashMap<String, Object> arguments) { StringTokenizer stok = new StringTokenizer(query, "&"); //$NON-NLS-1$ while (stok.hasMoreTokens()) { String aQuery = stok.nextToken(); int equalsPosition = aQuery.indexOf("="); //$NON-NLS-1$ if (equalsPosition > -1) { // well formed name/value pair String arg = aQuery.substring(0, equalsPosition); String val = aQuery.substring(equalsPosition + 1); Object existing = arguments.get(arg); if (existing == null) arguments.put(arg, val); else if (existing instanceof Vector) { @SuppressWarnings("unchecked") Vector<String> vector = (Vector<String>) existing; vector.add(val); arguments.put(arg, existing); } else { Vector<Object> v = new Vector<>(2); v.add(existing); v.add(val); arguments.put(arg, v); } } } } /** * NOTE: need to add support for multi-valued parameters (like filtering) Multiple values are * added as vectors */ protected void parseQuery() { if (query != null && !"".equals(query)) { //$NON-NLS-1$ if (arguments == null) { arguments = new HashMap<>(5); } parseQuery(query, arguments); } } @Override public String getContentType() { // Check if the file is hypertext or plain text String file = pluginAndFile.toLowerCase(Locale.US); if (file.endsWith(".html") || file.endsWith(".htm") //$NON-NLS-1$ //$NON-NLS-2$ || file.endsWith(".xhtml")) //$NON-NLS-1$ return "text/html"; //$NON-NLS-1$ else if (file.endsWith(".css")) //$NON-NLS-1$ return "text/css"; //$NON-NLS-1$ else if (file.endsWith(".gif")) //$NON-NLS-1$ return "image/gif"; //$NON-NLS-1$ else if (file.endsWith(".jpg")) //$NON-NLS-1$ return "image/jpeg"; //$NON-NLS-1$ else if (file.endsWith(".pdf")) //$NON-NLS-1$ return "application/pdf"; //$NON-NLS-1$ else if (file.endsWith(".xml")) //$NON-NLS-1$ return "application/xml"; //$NON-NLS-1$ else if (file.endsWith(".xsl")) //$NON-NLS-1$ return "application/xsl"; //$NON-NLS-1$ return "text/plain"; //$NON-NLS-1$ } /** * */ public Vector<String> getMultiValue(String name) { if (arguments != null) { Object value = arguments.get(name); if (value instanceof Vector) { @SuppressWarnings("unchecked") Vector<String> vector = (Vector<String>) value; return vector; } return null; } return null; } /** * */ public String getValue(String name) { if (arguments == null) return null; Object value = arguments.get(name); String stringValue = null; if (value instanceof String) stringValue = (String) value; else if (value instanceof Vector) { @SuppressWarnings("unchecked") Vector<String> vector = (Vector<String>) value; stringValue = vector.firstElement(); } else return null; try { return URLCoder.decode(stringValue); } catch (Exception e) { return null; } } /** * Returns the locale specified by client. */ protected String getLocale() { if (locale == null) { locale = getValue(PARAM_LANG); if (locale == null) { locale = Platform.getNL(); } } return locale; } protected String getFile() { if (file == null) { // Strip the plugin id int start = pluginAndFile.indexOf("/") + 1; //$NON-NLS-1$ // Strip query string or anchor bookmark int end = pluginAndFile.indexOf("?"); //$NON-NLS-1$ if (end == -1) end = pluginAndFile.indexOf("#"); //$NON-NLS-1$ if (end == -1) end = pluginAndFile.length(); file = pluginAndFile.substring(start, end); file = URLCoder.decode(file); } return file; } protected Bundle getPlugin() { if (plugin == null) { // Assume the url is pluginID/path_to_topic.html int i = pluginAndFile.indexOf('/'); String pluginId = i == -1 ? "" : pluginAndFile.substring(0, i); //$NON-NLS-1$ pluginId = URLCoder.decode(pluginId); if (PRODUCT_PLUGIN.equals(pluginId)) { IProduct product = Platform.getProduct(); if (product != null) { plugin = product.getDefiningBundle(); return plugin; } } plugin = Platform.getBundle(pluginId); } return plugin; } private String getHref() { return '/' + pluginAndFile; } public boolean isCacheable() { if (getValue("resultof") != null) //$NON-NLS-1$ return false; return cachingEnabled; } @Override public String toString() { return pluginAndFile; } /** * Obtains ID of plugin that contributes appserver implementation. * * * @return plug-in ID or null */ private static String getAppserverImplPluginId() { if (appserverImplPluginId == null) { // This part mimics AppserverPlugin.createWebappServer() // get the app server extension from the system plugin registry IExtensionRegistry pluginRegistry = Platform.getExtensionRegistry(); IExtensionPoint point = pluginRegistry.getExtensionPoint("org.eclipse.help.appserver.server"); //$NON-NLS-1$ if (point != null) { IExtension[] extensions = point.getExtensions(); if (extensions.length != 0) { // We need to pick up the non-default configuration IConfigurationElement[] elements = extensions[0].getConfigurationElements(); if (elements.length == 0) return null; IConfigurationElement serverElement = null; for (int i = 0; i < elements.length; i++) { String defaultValue = elements[i].getAttribute("default"); //$NON-NLS-1$ if (defaultValue == null || defaultValue.equals("false")) { //$NON-NLS-1$ serverElement = elements[i]; break; } } // if all the servers are default, then pick the first one if (serverElement == null) { serverElement = elements[0]; } // appserverImplPluginId = serverElement.getContributor().getName(); } } } return appserverImplPluginId; } /* * Opens a connection to the document on the remote help server, if one was specified. If the * document doesn't exist on the remote server, returns null. */ private InputStream openFromRemoteServer(String href, String locale) { if (RemoteHelp.isEnabled()) { String pathSuffix = PATH_RTOPIC + href + '?' + PARAM_LANG + '=' + locale; /* * Get the URL that maps to the contributorID Assume the url is * pluginID/path_to_topic.html */ int i = pluginAndFile.indexOf('/'); String pluginId = i == -1 ? "" : pluginAndFile.substring(0, i); //$NON-NLS-1$ pluginId = URLCoder.decode(pluginId); String remoteURL = RemoteContentLocator.getUrlForContent(pluginId); InputStream in; if (remoteURL == null) { in = tryOpeningAllServers(pathSuffix); } else { in = openRemoteStream(remoteURL, pathSuffix); } return in; } return null; } private InputStream getUnverifiedStream(String remoteURL,String pathSuffix) { URL url; InputStream in = null; try { if(remoteURL.startsWith(PROTOCOL_HTTP)) { url = new URL(remoteURL + pathSuffix); HttpURLConnection connection = (HttpURLConnection) ProxyUtil.getConnection(url); in = connection.getInputStream(); } else { url = HttpsUtility.getHttpsURL(remoteURL + pathSuffix); in = HttpsUtility.getHttpsStream(url); } } catch (Exception e) { // File not found on this server } return in; } private InputStream openRemoteStream(String remoteURL, String pathSuffix) { InputStream in = getUnverifiedStream(remoteURL,pathSuffix); String errPage[] = templates.get(remoteURL); if (errPage==null) { String error = getPageText(getUnverifiedStream(remoteURL,"/rtopic/fakeurltogetatestpage/_ACEGIKMOQ246.html")); //$NON-NLS-1$ if (error!=null) { errPage = error.split("\n"); //$NON-NLS-1$ templates.put(remoteURL,errPage); } else { errPage = new String[0]; templates.put(remoteURL,errPage); } } // No error page, InfoCenter is at least 3.6, so it is // returning null already. if (errPage.length==0) return in; // Check to see if the URL is the error page for the // remote IC. If so, return null. if (compare(errPage,getUnverifiedStream(remoteURL,pathSuffix))) { try{ in.close(); }catch(Exception ex){} return null; } return in; } private boolean compare(String lines[],InputStream in) { try{ if (in!=null) { try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) { String line; int count = 0; while ((line = br.readLine()) != null) { if (count > lines.length) return false; if (!lines[count].equals(line)) return false; count++; } } in.close(); return true; } }catch(Exception ex) {} return false; } private String getPageText(InputStream in) { try{ if (in!=null) { String line, result = ""; //$NON-NLS-1$ try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) { while ((line = br.readLine()) != null) { result += line + '\n'; } } in.close(); return result; } }catch(Exception ex){} return null; } private InputStream tryOpeningAllServers(String pathSuffix) { PreferenceFileHandler prefHandler = new PreferenceFileHandler(); String host[] = prefHandler.getHostEntries(); String port[] = prefHandler.getPortEntries(); String protocol[] = prefHandler.getProtocolEntries(); String path[] = prefHandler.getPathEntries(); String isEnabled[] = prefHandler.isEnabled(); int numICs = host.length; for (int i = 0; i < numICs; i++) { if (isEnabled[i].equalsIgnoreCase("true")) { //$NON-NLS-1$ String urlStr = protocol[i]+"://" + host[i] + ':' + port[i] + path[i]; //$NON-NLS-1$ InputStream is = openRemoteStream(urlStr, pathSuffix); if (is != null) { return is; } } } return null; } }