| /******************************************************************************* |
| * Copyright (c) 2009 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.webapp.servlet; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| |
| import javax.servlet.http.HttpServletRequest; |
| |
| import org.eclipse.help.internal.search.HTMLDocParser; |
| |
| /** |
| * This class replaces PLUGINS_ROOT with a relative path to eliminate redirects. |
| * It also performs preprocessing to add child links at runtime. |
| */ |
| public class PluginsRootResolvingStream extends OutputStream { |
| |
| protected OutputStream out; |
| |
| private int state = INITIAL_STATE; |
| private int charsMatched = 0; |
| private int lastKeywordMatch = 0; |
| private static final int INITIAL_STATE = 0; |
| private static final int IN_TAG = 1; |
| private static final int IN_QUOTE = 2; |
| private static final int IN_QUOTE_NOT_PLUGINS_ROOT = 3; |
| private static final int MAY_BE_INCLUDE = 4; |
| private static final int IN_METATAG = 5; |
| private static final String PLUGINS_ROOT = "PLUGINS_ROOT/"; //$NON-NLS-1$ |
| private static final String UTF8 = "UTF8"; //$NON-NLS-1$ |
| private static final String INSERT_CHILD_LINKS = "<!--INSERT_CHILD_LINKS-->"; //$NON-NLS-1$ |
| private static final String INSERT_CHILD_LINK_STYLE = "<!--INSERT_CHILD_LINK_STYLE-->"; //$NON-NLS-1$ |
| private final String[] keywords = { INSERT_CHILD_LINKS, INSERT_CHILD_LINK_STYLE }; |
| private boolean[] possibleKeywordMatches; |
| private String pathPrefix; |
| private StringBuffer tag; |
| private ByteArrayOutputStream metaTagBuffer; |
| private boolean tagRead; |
| private HttpServletRequest req; |
| private String charset; |
| |
| public PluginsRootResolvingStream(OutputStream out, HttpServletRequest req, String prefix) { |
| this.out = out; |
| this.pathPrefix = prefix; |
| this.req = req; |
| } |
| |
| @Override |
| public void write(int b) throws IOException { |
| switch(state) { |
| case INITIAL_STATE: |
| if (b == '<') { |
| state = IN_TAG; |
| charsMatched = 0; |
| tag = new StringBuffer(); |
| tagRead = false; |
| } else { |
| out.write(b); |
| } |
| break; |
| case IN_TAG: |
| if (charsMatched == 0) { |
| if (b == '!') { |
| state = MAY_BE_INCLUDE; |
| possibleKeywordMatches = new boolean[keywords.length]; |
| for (int i = 0; i < possibleKeywordMatches.length; i++) { |
| possibleKeywordMatches[i] = true; |
| } |
| charsMatched = 2; // Chars matched in "<!--INCLUDE" |
| lastKeywordMatch = 0; |
| break; |
| } else { |
| out.write('<'); |
| } |
| } |
| if (b == '>') { |
| state = INITIAL_STATE; |
| } else if (b == '"') { |
| state = IN_QUOTE; |
| charsMatched = 0; |
| } else { |
| charsMatched++; |
| if (!tagRead) { |
| if (b >= 0 && b < 128 && tag.length() < 20) { |
| // ASCII |
| char c = (char)b; |
| if (Character.isLetter(c)) { |
| tag.append(c); |
| } else if (Character.isWhitespace(c)) { |
| tagRead = true; |
| if (tag.toString().equalsIgnoreCase("meta")) { //$NON-NLS-1$ |
| state = IN_METATAG; |
| metaTagBuffer = new ByteArrayOutputStream(7); |
| metaTagBuffer.write("<meta ".getBytes()); //$NON-NLS-1$ |
| } |
| } else { |
| tag.append(c); |
| } |
| } |
| } |
| } |
| out.write(b); |
| break; |
| case IN_QUOTE_NOT_PLUGINS_ROOT: |
| if (b == '>') { |
| state = INITIAL_STATE; |
| } else if (b == '"') { |
| state = IN_TAG; |
| charsMatched = 1; |
| } |
| out.write(b); |
| break; |
| case IN_QUOTE: |
| // In a quote which may start with PLUGINS_ROOT |
| if (b == PLUGINS_ROOT.charAt(charsMatched)) { |
| charsMatched++; |
| if (charsMatched == PLUGINS_ROOT.length()) { |
| out.write(pathPrefix.getBytes()); |
| state = IN_QUOTE_NOT_PLUGINS_ROOT; |
| } |
| } else { |
| // We just discovered that this is not "PLUGINS_ROOT/ |
| // flush out the characters |
| state = IN_QUOTE_NOT_PLUGINS_ROOT; |
| flushPluginsRootCharacters(); |
| out.write(b); |
| } |
| break; |
| case MAY_BE_INCLUDE: |
| // Compare against all possible keywords |
| boolean canStillMatch = false; |
| int perfectMatch = -1; |
| for (int i = 0; i < keywords.length; i++) { |
| if (possibleKeywordMatches[i]) { |
| if (keywords[i].charAt(charsMatched) == b) { |
| canStillMatch = true; |
| lastKeywordMatch = i; |
| if (keywords[i].length() == charsMatched + 1) { |
| perfectMatch = i; |
| } |
| } else { |
| possibleKeywordMatches[i] = false; |
| } |
| } |
| } |
| if (perfectMatch != -1) { |
| insertBasedOnKeyword(perfectMatch); |
| state=INITIAL_STATE; |
| } else if (canStillMatch) { |
| charsMatched++; |
| } else { |
| state = INITIAL_STATE; |
| flushKeywordCharacters(); |
| out.write(b); |
| } |
| break; |
| case IN_METATAG: |
| out.write(b); |
| metaTagBuffer.write(b); |
| if (b=='>') { |
| parseMetaTag(metaTagBuffer); |
| metaTagBuffer = null; |
| state = INITIAL_STATE; |
| } |
| break; |
| default: |
| out.write(b); |
| } |
| } |
| |
| private void parseMetaTag(ByteArrayOutputStream buffer) { |
| ByteArrayInputStream is = new ByteArrayInputStream(buffer.toByteArray()); |
| String value = HTMLDocParser.getCharsetFromHTML(is); |
| try { |
| is.close(); |
| } |
| catch (IOException e) { |
| } |
| if (value!=null) { |
| this.charset = value; |
| } |
| } |
| |
| protected void insertBasedOnKeyword(int index) throws IOException { |
| if (index == 0 ) { |
| ChildLinkInserter inserter = new ChildLinkInserter(req, out); |
| inserter.addContents(getCharset()); |
| } else { |
| ChildLinkInserter inserter = new ChildLinkInserter(req, out); |
| inserter.addStyle(); |
| } |
| } |
| |
| private void flushPluginsRootCharacters() throws IOException { |
| out.write(PLUGINS_ROOT.substring(0, charsMatched).getBytes(UTF8)); |
| } |
| |
| private void flushKeywordCharacters() throws IOException { |
| String matchingCharacters = keywords[lastKeywordMatch].substring(0, charsMatched); |
| out.write(matchingCharacters.getBytes(UTF8)); |
| } |
| |
| |
| @Override |
| public void close() throws IOException { |
| if (state == IN_QUOTE) { |
| flushPluginsRootCharacters(); |
| } else if (state == MAY_BE_INCLUDE) { |
| flushKeywordCharacters(); |
| } |
| out.close(); |
| super.close(); |
| } |
| |
| public String getCharset() { |
| return charset; |
| } |
| } |