http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImpl.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImpl.java b/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImpl.java deleted file mode 100644 index 7dad28c..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImpl.java +++ /dev/null @@ -1,154 +0,0 @@ -/** - * 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.waveprotocol.wave.client.clipboard; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.Style.Unit; -import org.waveprotocol.wave.client.common.util.JsoView; -import org.waveprotocol.wave.client.common.util.QuirksConstants; -import org.waveprotocol.wave.client.common.util.UserAgent; -import org.waveprotocol.wave.client.editor.selection.html.NativeSelectionUtil; - -import org.waveprotocol.wave.model.document.util.Point; - -/** - * Provides an offscreen, HTML paste buffer for various browsers. - * - */ -public class PasteBufferImpl { - - /** - * The actual element that contains the paste. - */ - protected Element element = null; - - private static final boolean SHOW_DEBUG_PASTEBUFFER = false; - - /** - * Factory constructor, creates and attaches the buffer to the DOM. - * - * @return Browser specific implementation of a paste buffer. - */ - static PasteBufferImpl create() { - PasteBufferImpl pasteBuffer; - - if (UserAgent.isSafari() || QuirksConstants.FIREFOX_GREATER_THAN_VER_15) { - pasteBuffer = new PasteBufferImplSafariAndNewFirefox(); - } else if (UserAgent.isFirefox() && !QuirksConstants.SANITIZES_PASTED_CONTENT) { - // Older versions of firefox doesn't sanitize pasted content and requires the - // paste buffer to be an iframe to prevent XSS. - pasteBuffer = new PasteBufferImplOldFirefox(); - } else { - pasteBuffer = new PasteBufferImpl(); - } - - pasteBuffer.setupDom(); - return pasteBuffer; - } - - /** - * Empty protected constructor. Use static create for instantiation. - */ - protected PasteBufferImpl() { - } - - /** - * Clears and sets the content of the paste element. - * - * @param node The DOM to append. - */ - void setContent(Node node) { - element.setInnerHTML(""); - element.appendChild(node); - } - - /** - * Use this to get the root level container. - * - * @return The offscreen element. - */ - public Element getContainer() { - return element; - } - - /** - * Use this to get the paste contents (from innerHTML). - * - * @return The pasted contents. - */ - public Element getPasteContainer() { - return element; - } - - /** - * Prepare the buffer to accept a paste event by setting the selection and - * focus to the container. - */ - public void prepareForPaste() { - element.setInnerHTML(""); - element.appendChild(Document.get().createTextNode("")); - - NativeSelectionUtil.setCaret(Point.inText(element.getFirstChild(), 0)); - } - - protected void positionPasteBuffer(Element element) { - if (SHOW_DEBUG_PASTEBUFFER) { - element.getStyle().setPosition(Position.ABSOLUTE); - element.getStyle().setHeight(150, Unit.PX); - element.getStyle().setLeft(1000, Unit.PX); - element.getStyle().setTop(10, Unit.PX); - } else { - element.getStyle().setPosition(Position.ABSOLUTE); - element.getStyle().setTop(-100, Unit.PX); // arbitrary numbers - element.getStyle().setHeight(50, Unit.PX); - } - } - - /** - * Sets up the PasteBuffer DOM. - * - * Implementations should call positionPasteBuffer - */ - protected void setupDom() { - element = Document.get().createDivElement(); - // For some reason setting display to none prevents this trick from working - // instead, we move it away from view, so it's still "visible" - // NOTE(user): We setwhitespace pre-wrap prevents the browser from - // collapsing sequences of whitespace. This is important to ensure that the - // spaces after a start tag, or before an end tag are preserved through copy/paste. - // Also, we can't use DomHelper.setContentEditable as setting -webkit-user-modify - // to read-write-plaintext-only will force the pasted content to plain text and - // kill all formatting and semantic paste. - // This trick doesn't work in Firefox, because the pre-wrap attribute is not - // preserved through copy, to fix this in Firefox, we'll need to manually - // replace spaces with - element.setAttribute("contentEditable", "true"); - JsoView.as(element.getStyle()).setString("white-space", "pre-wrap"); - // DomHelper.setContentEditable(element, true, false); - element.getStyle().setOverflow(Overflow.HIDDEN); - - positionPasteBuffer(element); - Document.get().getBody().appendChild(element); - } -}
http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImplOldFirefox.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImplOldFirefox.java b/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImplOldFirefox.java deleted file mode 100644 index afd4e1e..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImplOldFirefox.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * 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.waveprotocol.wave.client.clipboard; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.IFrameElement; -import com.google.gwt.dom.client.Node; -import org.waveprotocol.wave.client.common.util.DomHelper; -import org.waveprotocol.wave.client.editor.selection.html.NativeSelectionUtil; -import org.waveprotocol.wave.model.document.util.Point; - -/** - * Firefox old implementation of the paste buffer. We cannot use a standard div set - * to contentEditable because pasting any javascript will automatically - * execute it. Instead, use an offscreen iframe whose document is set to - * "designMode". This is roughly equivalent to contentEditable with the - * exception of being more sandbox-like and prevents script execution. - * - * NOTE(user): There's actually a lot of blackmagic in this class. In setupDom, - * the real contentDocument of the iframe is actually created asynchronously. In - * this code are actually using a transient element inside a transient - * contentDocument, that just happens to work. - * - * Why don't we get contentDocument().getBody() after a delay then? - * - * If we get the use the real element, i.e. assign element from - * iframe.getContentDocument().getBody() asynchronously we'll suffers from paste - * self-xss. This seems strange, because we're using designMode in an IFrame, - * which is supposed to guard against javascript execution. If we make this - * iframe visible and paste in html that executes js directly, we are protected - * from js execution. However, if we are doing it programmatically inside a copy - * event, we are not protected. - * - * Tested on: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2) - * Gecko/20100115 Firefox/3.6 GTB7.0 < 15.0 - * - */ -class PasteBufferImplOldFirefox extends PasteBufferImpl { - - private final IFrameElement iframe; - - /** - * Protected empty constructor. Will be created by factory constructor in - * PasteBufferImpl. - */ - protected PasteBufferImplOldFirefox() { - iframe = Document.get().createIFrameElement(); - } - - @Override - protected void setupDom() { - Document.get().getBody().appendChild(iframe); - - positionPasteBuffer(iframe); - element = iframe.getContentDocument().getBody(); - setDesignMode(iframe.getContentDocument()); - } - - private static native void setDesignMode(Document doc) /*-{ - doc.designMode = "on"; - }-*/; - - @Override - public void prepareForPaste() { - super.prepareForPaste(); - // N.B.(davidbyttow): In FF3, focus is not implicitly set by setting the - // selection when appending a DOM element dynamically. So we must explicitly - // set the focus. - DomHelper.focus(iframe); - NativeSelectionUtil.setCaret(Point.<Node>end(element)); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImplSafariAndNewFirefox.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImplSafariAndNewFirefox.java b/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImplSafariAndNewFirefox.java deleted file mode 100644 index db5ba68..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/clipboard/PasteBufferImplSafariAndNewFirefox.java +++ /dev/null @@ -1,116 +0,0 @@ -/** - * 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.waveprotocol.wave.client.clipboard; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.Text; -import org.waveprotocol.wave.client.common.util.DomHelper; -import org.waveprotocol.wave.client.editor.selection.html.NativeSelectionUtil; -import org.waveprotocol.wave.model.document.util.Point; - -/** - * Safari implementation of the paste buffer. In Safari, we need to have the - * paste occur inside of a text node in order to consistently get the - * leading and trailing inline text. If that is not present, we sometimes - * cannot tell the difference between a new paragraph or inline text. - * - * Also works for Firefox >= 15 - * - */ -class PasteBufferImplSafariAndNewFirefox extends PasteBufferImpl { - - private boolean markersStripped = false; - - private static final char MARKER_CHAR = '\u007F'; - private static final String MARKER_NODE_STRING = String.valueOf(MARKER_CHAR) + MARKER_CHAR; - - /** - * Use this to get the paste contents (from innerHTML). Note, this - * implementation will strip the extra markers injected for Safari. - * - * @return The pasted contents. - */ - @Override - public Element getPasteContainer() { - stripMarkers(); - return element; - } - - @Override - public void prepareForPaste() { - element.setInnerHTML(""); - element.appendChild(Document.get().createTextNode(MARKER_NODE_STRING)); - NativeSelectionUtil.setCaret(Point.inText(element.getFirstChild(), 1)); - markersStripped = false; - } - - private void stripMarkers() { - if (markersStripped) { - return; - } - - // Remove the leading and trailing markers. - maybeStripMarker(element.getFirstChild(), element, true); - maybeStripMarker(element.getLastChild(), element, false); - markersStripped = true; - } - - // TODO(user): Remove this when we can confirm this no longer happens. - private void logEndNotFound(String detail) { - Clipboard.LOG.error().log("end not found: " + detail); - } - - private void maybeStripMarker(Node node, Element parent, boolean leading) { - if (node == null) { - logEndNotFound("node is null"); - return; - } - if (DomHelper.isTextNode(node)) { - Text textNode = node.cast(); - String text = textNode.getData(); - if (!text.isEmpty()) { - if (leading) { - if (text.charAt(0) == MARKER_CHAR) { - textNode.setData(text.substring(1)); - } - } else { - if (text.charAt(text.length() - 1) == MARKER_CHAR) { - textNode.setData(text.substring(0, text.length() - 1)); - } else { - logEndNotFound("last character is not marker"); - } - } - } else { - logEndNotFound("text node is empty"); - } - if (textNode.getData().isEmpty()) { - parent.removeChild(textNode); - } - } else { - // In some cases, Safari will put the marker inside of a div, so this - // traverses down the left or right side of the tree to find it. - // For example: x<div><span>pasted</span>x</div> - maybeStripMarker(leading ? node.getFirstChild() : node.getLastChild(), node.<Element> cast(), - leading); - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/EscapeUtils.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/EscapeUtils.java b/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/EscapeUtils.java deleted file mode 100644 index 9bdece0..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/EscapeUtils.java +++ /dev/null @@ -1,201 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.safehtml; - -// NOTE: In the near future, the files in this package will be open sourced as -// part of a different project. Do not rely on them staying here. - -/** - * Utility class containing static methods for escaping and sanitizing strings. - */ -// TODO(user): The naming of this class and the methods herein isn't exactly -// consistent anymore; clean this up. -public final class EscapeUtils { - - private static final String HTML_ENTITY_REGEX = "[a-z]+|#[0-9]+|#x[0-9a-fA-F]+"; - - public static final SafeHtml EMPTY_SAFE_HTML = new SafeHtmlString(""); - - // prevent instantiation - private EscapeUtils() { - } - - /** - * Returns a SafeHtml constructed from a safe string, i.e. without escaping the string. - */ - public static SafeHtml fromSafeConstant(String s) { - return new SafeHtmlString(s); - } - - /** - * Returns a SafeHtml constructed from a plain string that does not contain any HTML markup. - */ - public static SafeHtml fromPlainText(String s) { - // TODO(user) assert that there are no HTML elements in the string - // TODO(user) verify that this is actually faster than calling htmlEscape() - return new SafeHtmlString(s); - } - - /** - * Returns a SafeHtml containing the escaped string. - */ - public static SafeHtml fromString(String s) { - return new SafeHtmlString(htmlEscape(s)); - } - - /** - * HTML-escapes a string. - * - * @param s the string to be escaped - * @return the input string, with all occurrences of HTML meta-characters replaced with their - * corresponding HTML Entity References - */ - public static String htmlEscape(String s) { - // TODO(user): GWT does not seem to have java.util.regex, so leave this out for now. - /* - if (!HTML_META_CHARS.matcher(s).find()) { - // short cirquit and bail out if no work to be done, without allocating objects. - return s; - } - */ - - // TODO(user): maybe do some benchmarking and work out if this is the most efficient way to go - // about escaping. - return s.replaceAll("&", "&") - .replaceAll("\"", """) - .replaceAll("\'", "'") - .replaceAll("<", "<") - .replaceAll(">", ">"); - } - - /** - * HTML-escapes a string, but does not double-escape HTML-entities already present in the string. - * - * @param text the string to be escaped - * @return the input string, with all occurrences of HTML meta-characters replaced with their - * corresponding HTML Entity References, with the exception that ampersand characters are - * not double-escaped if they form the start of an HTML Entity Reference - */ - public static String htmlEscapeAllowEntities(String text) { - StringBuilder escaped = new StringBuilder(); - - boolean firstSegment = true; - for (String segment : text.split("&", -1)) { - if (firstSegment) { - // The first segment is never part of an entity reference, so we always escape it. - // Note that if the input starts with an ampersand, we will get an empty segment - // before that. - firstSegment = false; - escaped.append(htmlEscape(segment)); - continue; - } - - int entityEnd = segment.indexOf(';'); - if (entityEnd > 0 && - segment.substring(0, entityEnd).matches(HTML_ENTITY_REGEX)) { - // Append the entity without escaping. - escaped.append("&") - .append(segment.substring(0, entityEnd + 1)); - - // Append the rest of the segment, escaped. - escaped.append(htmlEscape(segment.substring(entityEnd + 1))); - } else { - // The segment did not start with an entity reference, so escape the whole segment. - escaped.append("&") - .append(htmlEscape(segment)); - } - } - - return escaped.toString(); - } - - /* - * Methods to validate/sanitize URIs. - */ - - // TODO(user): Figure out if GWT supports some parsed representation of URIs, - // and add equivalent methods that operate on those rather than string (which - // would likely be more efficient in cases where URIs are constructed with a - // common base). I tried java.net.URI, but alas it's not supported at this - // time. - - /** - * Extracts the scheme of a URI. - * - * @param uri the URI to extract the scheme from - * @return the URI's scheme, or {@code null} if the URI does not have one - */ - public static String extractScheme(String uri) { - int colonPos = uri.indexOf(':'); - if (colonPos < 0) { - return null; - } - String scheme = uri.substring(0, colonPos); - if (scheme.indexOf('/') >= 0 || scheme.indexOf('#') >= 0) { - // The URI's prefix up to the first ':' contains other URI special - // chars, and won't be interpreted as a scheme. - // TODO(user): Consider basing this on URL#isValidProtocol or similar; - // however I'm worried that being too strict here will effectively - // allow dangerous schemes accepted in loosely parsing browsers. - return null; - } - return scheme; - } - - /** - * Determines if a {@link String} is safe to use as the value of a URI-valued - * HTML attribute such as {@code src} or {@code href}. - * - * <p>In this context, a URI is safe if it can be established that using it as - * the value of a URI-valued HTML attribute such as {@code src} or {@code - * href} cannot result in script execution. Specifically, this method deems a - * URI safe if it either does not have a scheme, or its scheme is one of - * {@code http, https, ftp, mailto}. - * - * @param uri the URI to validate - * @return {@code true} if {@code uri} is safe in the above sense; {@code - * false} otherwise - */ - public static boolean isSafeUri(String uri) { - String scheme = extractScheme(uri); - return (scheme == null - || "http".equalsIgnoreCase(scheme) - || "https".equalsIgnoreCase(scheme) - || "mailto".equalsIgnoreCase(scheme) - || "ftp".equalsIgnoreCase(scheme)); - } - - /** - * Sanitizes a URI. - * - * <p>This method returns the URI provided if it is safe to use as the the - * value of a URI-valued HTML attribute according to {@link #isSafeUri}, or - * the URI "{@code #}" otherwise. - * - * @param uri the URI to sanitize. - */ - public static String sanitizeUri(String uri) { - if (isSafeUri(uri)) { - return uri; - } else { - return "#"; - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtml.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtml.java b/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtml.java deleted file mode 100644 index 06074ad..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtml.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.safehtml; - -import java.io.Serializable; - -//NOTE: In the near future, the files in this package will be open sourced as -//part of a different project. Do not rely on them staying here. - -/** - * An object that implements this interface encapsulates HTML that is guaranteed to be safe to use - * (with respect to potential Cross-Site-Scripting vulnerabilities) in an HTML context. - * - * <p>All implementing classes must maintain the class invariant (by design and implementation - * and/or convention of use), that invoking {@link #asString()} on any instance will return a string - * that is safe to assign to the {@code .innerHTML} DOM property in a browser (or to use similarly - * in an "inner HTML" context), in the sense that doing so must not cause execution of script in the - * browser. - * - * <p>Implementations of this interface must not implement - * {@link com.google.gwt.user.client.rpc.IsSerializable}, since deserialization can result in - * violation of the class invariant. - */ -public interface SafeHtml extends Serializable { -/* Notes regading serialization: - * - It may be reasonable to allow deserialization on the client of objects - * serialized on the server (i.e. RPC responses), based on the assumption that - * server code is trusted and would not provide a malicious serialized form - * (if a MitM were able to modify server responses, the client would be - * fully compromised in any case). However, the GWT RPC framework currently - * does not seem to provide a facility for restricting deserialization on the - * Server only (thought this shouldn't be difficult to implement through a - * custom SerializationPolicy) - * - * - Some implementations of SafeHtml would in principle be able to enforce - * their class invariant on deserialization (e.g., SimpleHtmlSanitizer could apply - * HTML sanitization on deserialization). However, the GWT RPC framework does - * not provide for an equivalent of readResolve() to enforce the class - * invariant on deserialization. - * - */ - - - /** - * Returns this object's contained HTML as a string. Based on this class' contract, the returned - * string will be safe to use in an HTML context. - */ - String asString(); -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtmlBuilder.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtmlBuilder.java b/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtmlBuilder.java deleted file mode 100644 index 27e4219..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtmlBuilder.java +++ /dev/null @@ -1,213 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.safehtml; - -//NOTE: In the near future, the files in this package will be open sourced as -//part of a different project. Do not rely on them staying here. - -/** A builder that facilitates the building up of XSS-safe HTML from text - * snippets. It is used essentially like a {@link StringBuilder}; unlike a - * {@link StringBuilder}, it automatically HTML-escapes appended input where - * necessary. - * - * <p>In addition, it supports methods that allow strings with HTML markup to be - * appended without escaping: One can append other {@link SafeHtml} objects, and - * one can append constant strings. The method that appends constant strings - * ({@link #appendHtmlConstant(String)}) requires a convention of use to be - * adhered to in order for this class to adhere to the contract required by - * {@link SafeHtml}. - * - * <p>The accumulated XSS-safe HTML can be obtained in the form of a {@link - * SafeHtml} via the {@link #toSafeHtml()} method. - * - * <p>This class is not thread-safe. - */ -public final class SafeHtmlBuilder { - - private final StringBuilder sb = new StringBuilder(); - - /** - * Constructs an empty SafeHtmlBuilder. - */ - public SafeHtmlBuilder() { - } - - /** - * Returns the safe HTML accumulated in the builder as a {@link SafeHtml}. - */ - public SafeHtml toSafeHtml() { - return new SafeHtmlString(sb.toString()); - } - - /** - * Appends a compile-time-constant string, which will <em>not</em> be escaped. - * - * <p><b>Important</b>: For this class to be able to honour its contract as required by {@link - * SafeHtml}, all uses of this method must satisfy the following requirements: - * - * <ul> - * - * <li>The argument expression must be fully determined and known to be safe at - * compile time. - * - * <li>The value of the argument must not contain incomplete HTML tags. I.e., the following is not - * a correct use of this method, because the {@code <a>} tag is incomplete: - * <pre class="code">{@code shb.appendConstantHtml("<a href='").append(url)}</pre> - * - * </ul> - * - * @param html the HTML snippet to be appended - * @return a reference to this object - */ - public SafeHtmlBuilder appendHtmlConstant(String html) { - // TODO(user): (hosted-mode only) assert that html satisfies the second constraint. - sb.append(html); - return this; - } - - /** - * Appends the contents of another {@link SafeHtml} object, without applying HTML-escaping to it. - * - * @param html the {@link SafeHtml} to append - * @return a reference to this object - */ - public SafeHtmlBuilder append(SafeHtml html) { - sb.append(html.asString()); - return this; - } - - /** - * Appends a string after HTML-escaping it. - * - * @param text the string to append - * @return a reference to this object - */ - public SafeHtmlBuilder appendEscaped(String text) { - sb.append(EscapeUtils.htmlEscape(text)); - return this; - } - - /** - * Appends a string consisting of several newline-separated lines - * after HTML-escaping it. Newlines in the original string are - * converted to {@code <br>}. - * - * @param text the string to append - * @return a reference to this object - */ - public SafeHtmlBuilder appendEscapedLines(String text) { - sb.append(EscapeUtils.htmlEscape(text).replaceAll("\n", "<br>")); - return this; - } - - /** - * Appends a plain text string that does not contain any HTML elements. - * - * @param text the string to append - * @return a reference to this object - */ - public SafeHtmlBuilder appendPlainText(String text) { - // TODO(user) assert text does not contain any HTML elements - // TODO(user) verify that this is actually faster than calling htmlEscape() - sb.append(text); - return this; - } - - /* - * Boolean and numeric types converted to String are always HTML safe -- no escaping necessary. - */ - - /** - * Appends the string representation of a boolean. - * - * @param b the boolean whose string representation to append - * @return a reference to this object - */ - public SafeHtmlBuilder append(boolean b) { - sb.append(b); - return this; - } - - /** - * Appends the string representation of a char. - * - * @param num the number whose string representation to append - * @return a reference to this object - */ - public SafeHtmlBuilder append(char num) { - sb.append(num); - return this; - } - - /** - * Appends the string representation of a number. - * - * @param num the number whose string representation to append - * @return a reference to this object - */ - public SafeHtmlBuilder append(int num) { - sb.append(num); - return this; - } - - /** - * Appends the string representation of a number. - * - * @param num the number whose string representation to append - * @return a reference to this object - */ - public SafeHtmlBuilder append(byte num) { - sb.append(num); - return this; - } - - /** - * Appends the string representation of a number. - * - * @param num the number whose string representation to append - * @return a reference to this object - */ - public SafeHtmlBuilder append(long num) { - sb.append(num); - return this; - } - - /** - * Appends the string representation of a number. - * - * @param num the number whose string representation to append - * @return a reference to this object - */ - public SafeHtmlBuilder append(float num) { - sb.append(num); - return this; - } - - /** - * Appends the string representation of a number. - * - * @param num the number whose string representation to append - * @return a reference to this object - */ - public SafeHtmlBuilder append(double num) { - sb.append(num); - return this; - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtmlString.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtmlString.java b/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtmlString.java deleted file mode 100644 index 85777fe..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/safehtml/SafeHtmlString.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.safehtml; - -//NOTE: In the near future, the files in this package will be open sourced as -//part of a different project. Do not rely on them staying here. - -/** - * A string wrapped as an object of type {@link SafeHtml}. - * - * <p>This class is package-private and intended for internal use by the - * {@link org.waveprotocol.wave.client.common.safehtml} package. - */ -class SafeHtmlString implements SafeHtml { - private String html; - - /** - * Constructs a {@link SafeHtmlString} from a string. Callers are responsible for ensuring that - * the string passed as the argument to this constructor satisfies the constraints of the contract - * imposed by the {@link SafeHtml} interface. - * - * @param html the string to be wrapped as a {@link SafeHtml} - */ - SafeHtmlString(String html) { - this.html = html; - } - - /** - * No-arg constructor for compatibility with GWT serialization. - */ - SafeHtmlString() { - } - - /** {@inheritDoc} */ - @Override - public String asString() { - return html; - } -} - http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/scrub/Scrub.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/scrub/Scrub.java b/wave/src/main/java/org/waveprotocol/wave/client/common/scrub/Scrub.java deleted file mode 100644 index a35f472..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/scrub/Scrub.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.scrub; - -import com.google.gwt.http.client.URL; - -import org.waveprotocol.wave.client.common.safehtml.EscapeUtils; - -/** - * Helper for scrubbing URLs - * - * @author [email protected] (Daniel Danilatos) - */ -public class Scrub { - - /** If true, then we scrub URLs */ - private static boolean enableScrubbing = false; - - public static void setEnableScrubbing(final boolean enableScrubbing) { - Scrub.enableScrubbing = enableScrubbing; - } - - /** Scrubbing prefix */ - public static final String REFERRER_SCRUBBING_URL = - "http://www.google.com/url?sa=D&q="; - - /** - * Scrub a url if scrubbing is turned on - * - * Does not scrub urls with leading hashes - * - * @param url - * @return The scrubbed version of the url, if it's not already scrubbed - */ - public static String scrub(String url) { - if (enableScrubbing) { - if (url.startsWith("#") || url.startsWith(REFERRER_SCRUBBING_URL)) { - // NOTE(user): The caller should be responsible for url encoding if - // neccessary. There is no XSS risk here as it is a fragment. - return url; - } else { - String x = REFERRER_SCRUBBING_URL + URL.encodeComponent(url); - return x; - } - } else { - // If we are not scrubbing the url, then we still need to sanitize it, - // to protect against e.g. javascript. - String sanitizedUri = EscapeUtils.sanitizeUri(url); - return sanitizedUri; - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/AsyncHolder.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/AsyncHolder.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/AsyncHolder.java deleted file mode 100644 index 317bb41..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/AsyncHolder.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.util; - -import com.google.common.base.Preconditions; - -import org.waveprotocol.wave.model.util.CollectionUtils; - -import java.util.Queue; - -/** - * Something that holds a value, revealed by potentially asynchronous access. - * - * @param <T> type of held value - */ -public interface AsyncHolder<T> { - - /** Accessor of a value in this holder. */ - public interface Accessor<T> { - void use(T x); - } - - /** - * Reveals the value held by this holder to a consumer. The value may be - * revealed synchronously or asynchronously. - * - * @param accessor procedure to apply - */ - void call(Accessor<T> accessor); - - public abstract class Impl<T> implements AsyncHolder<T>, Accessor<T> { - private Queue<Accessor<T>> waiting; - private T value; - - @Override - public final void call(Accessor<T> accessor) { - if (value == null) { - addWaiter(accessor); - } else { - accessor.use(value); - } - } - - private void addWaiter(Accessor<T> accessor) { - assert value == null; - if (waiting == null) { - waiting = CollectionUtils.createQueue(); - waiting.add(accessor); - create(this); - } else { - waiting.add(accessor); - } - } - - protected abstract void create(Accessor<T> whenReady); - - @Override - public void use(T x) { - Preconditions.checkState(value == null && waiting != null); - value = x; - for (Accessor<T> waiter : waiting) { - waiter.use(value); - } - waiting = null; - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/ChainComparator.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/ChainComparator.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/ChainComparator.java deleted file mode 100644 index 50dc0cf..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/ChainComparator.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.util; - -import java.util.Comparator; - -/** - * Used to chain comparators. First compare the arguments using the first - * comparator. If non-zero, return the result. Return the results of the second - * comparator. - * - * This can be nested to combine arbitrary number of comparators. - * - * - * @param <T> - */ -public final class ChainComparator<T> implements Comparator<T> { - private final Comparator<? super T> firstComparator; - private final Comparator<? super T> secondComparator; - - public ChainComparator(Comparator<? super T> firstComparator, - Comparator<? super T> secondComparator) { - this.firstComparator = firstComparator; - this.secondComparator = secondComparator; - } - - /** {@inheritDoc} **/ - public int compare(T o1, T o2) { - int cmp = firstComparator.compare(o1, o2); - return cmp != 0 ? cmp : secondComparator.compare(o1, o2); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/ChecksComparability.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/ChecksComparability.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/ChecksComparability.java deleted file mode 100644 index 153e91d..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/ChecksComparability.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.util; - -/** - * Interface for volatile objects that may or may not be comparable to one - * another at any given moment. - * - * @author [email protected] (Daniel Danilatos) - */ -public interface ChecksComparability { - - /** - * @return true if it makes sense for this object to be compared to a similar - * object. - */ - boolean isComparable(); -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/ClientDebugException.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/ClientDebugException.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/ClientDebugException.java deleted file mode 100644 index 7b4d0df..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/ClientDebugException.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.util; - -/** - * We use this exception class for debugging purposes. Mainly, this class is - * used to wrap NPEs that are caught with a try/catch around code that is - * suspected of generating them. NOTE: Once we'll have stack traces for these - * NPEs in all browsers the use of this exception should cease. - * - */ -public class ClientDebugException extends RuntimeException { - - /** - * @param message Some useful message about the exception - */ - public ClientDebugException(String message) { - super(message); - } - - /** - * @param message Some useful message about the exception - * @param cause The original exception to be wrapped. - */ - public ClientDebugException(String message, Throwable cause) { - super(message, cause); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/ClientPercentEncoderDecoder.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/ClientPercentEncoderDecoder.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/ClientPercentEncoderDecoder.java deleted file mode 100644 index ebaf41b..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/ClientPercentEncoderDecoder.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.util; - -import com.google.gwt.http.client.URL; - -import org.waveprotocol.wave.model.id.URIEncoderDecoder; - -/** - * Uses GWT {@link URL} to percent encode. - */ -public final class ClientPercentEncoderDecoder implements URIEncoderDecoder.PercentEncoderDecoder { - @Override - public String decode(String encodedValue) throws URIEncoderDecoder.EncodingException { - String ret = URL.decodePathSegment(encodedValue); - if (ret.indexOf(0xFFFD) != -1) { - throw new URIEncoderDecoder.EncodingException("Unable to decode value " + encodedValue - + " it contains invalid UTF-8"); - } - return ret; - } - - @Override - public String encode(String decodedValue) { - return URL.encode(decodedValue); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/CountdownLatch.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/CountdownLatch.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/CountdownLatch.java deleted file mode 100644 index 4319f4b..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/CountdownLatch.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.util; - -import com.google.common.base.Preconditions; -import com.google.gwt.user.client.Command; - -/** - * Simple synchronization tool that waits for some fixed number of calls to - * {@link #tick()}, and invokes a command on the last one. Any further calls to - * {@link #tick()} produce an error. - * <p> - * The intended use case for this class is a gate that waits for some set of - * asynchronous tasks to complete. - * - */ -public final class CountdownLatch { - /** Command to execute when the all ticks have occurred. */ - private final Command whenZero; - - /** Number of expected ticks. */ - private int count; - - private CountdownLatch(int count, Command whenZero) { - this.whenZero = whenZero; - this.count = count; - } - - /** - * Creates a countdown latch. - * - * @param count number of ticks - * @param whenZero command to execute after {@code count} ticks - * @return a new latch. - */ - public static CountdownLatch create(int count, Command whenZero) { - return new CountdownLatch(count, whenZero); - } - - /** - * Ticks this counter. - * - * @throws IllegalStateException if this counter has already been ticked the - * expected number of times. - */ - public void tick() { - Preconditions.checkState(count > 0); - count--; - if (count == 0) { - whenZero.execute(); - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/DateUtils.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/DateUtils.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/DateUtils.java deleted file mode 100644 index 515a478..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/DateUtils.java +++ /dev/null @@ -1,171 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.util; - -import com.google.gwt.core.client.Duration; -import com.google.gwt.core.client.GWT; -import com.google.gwt.i18n.client.DateTimeFormat; - -import java.util.Date; - -/** - * Date formatting utilities. - * - */ -@SuppressWarnings("deprecation") // GWT supports Date.getXXX, but java doesn't anymore -public final class DateUtils { - - private final static long SEC_MS = 1000; - private final static long MIN_MS = 60 * SEC_MS; - private final static long HOUR_MS = 60 * MIN_MS; - private final static long DAY_MS = 24 * HOUR_MS; - - // Singleton class. - private DateUtils() { } - private static final DateUtils INSTANCE = new DateUtils(); - - /** - * Please avoid invoking getInstance() inside methods but rather inject the - * instance into the class through the constructor. This will - * - make it easier to move to GIN in the future, and - * - make the class easier to test since DateUtils needs to be mocked out - * in non-GWT tests. - * - * @return the shared DateUtils instance - */ - public static DateUtils getInstance() { - return INSTANCE; - } - - /** - * Formats a date in the past, taking into account how long ago it was - * - * @param time The date to format, in ms since whenever - * @return The formatted date - */ - public String formatPastDate(long time) { - return formatPastDate(new Date(time), new Date()); - } - - private static DateTimeFormat monthDayFormat; - - private static DateTimeFormat getMonthDayFormat() { - if (monthDayFormat == null) { - monthDayFormat = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.MONTH_ABBR_DAY); - } - return monthDayFormat; - } - - /** - * Package-private version, takes a fixed "now" time - used for testing - */ - String formatPastDate(Date date, Date now) { - - // NOTE(zdwang): For now skip it for junit code; also see formatDateTime() - if (!GWT.isClient()) { - return "formatPastDate is not yet implemented in unit test code"; - } - - if (!isValid(date, now)) { - GWT.log("formatPastDate can only format time in the past, trying anyway", null); - } - - if (isRecent(date, now, 6 * HOUR_MS) || onSameDay(date, now)) { - return formatTime(date); - } else if (isRecent(date, now, 30 * DAY_MS)) { - return getMonthDayFormat().format(date) + " " + formatTime(date); - } else if (isSameYear(date, now)) { - return getMonthDayFormat().format(date); - } else { - return DateTimeFormat.getMediumDateFormat().format(date); - } - } - - /** - * Formats the specified time as a String. - */ - public String formatTime(Date date) { - // NOTE(zdwang): For now skip it for junit code; also see formatPastDate() - if (!GWT.isClient()) { - return "formatDateTime is not yet implemented in unit test code"; - } - - // AM/PM -> am/pm for consistency with formatPastDate() - return DateTimeFormat.getShortTimeFormat().format(date).toLowerCase(); - } - - /** - * Formats the specified date and time as a String. - */ - public String formatDateTime(Date date) { - // NOTE(zdwang): For now skip it for junit code; also see formatPastDate() - if (!GWT.isClient()) { - return "formatDateTime is not yet implemented in unit test code"; - } - - // AM/PM -> am/pm for consistency with formatPastDate() - return DateTimeFormat.getShortDateTimeFormat().format(date).toLowerCase(); - } - - /** - * @return true if a date is approximately in the past (i.e., before a - * minute in the future). - */ - private boolean isValid(Date date, Date now) { - long diff = (now.getTime() + MIN_MS) - date.getTime(); - return diff >= 0; - } - - /** - * @return true if a duration is less than x ms. - */ - private boolean isRecent(Date date, Date now, long ms) { - return (now.getTime() - date.getTime()) < ms; - } - - /** - * @return true if a date occurs on the same day as today. - */ - private boolean onSameDay(Date date, Date now) { - return (date.getDate() == now.getDate()) - && (date.getMonth() == now.getMonth()) - && (date.getYear() == now.getYear()); - } - - /** - * @return true if a date occurs in the same year as this year. - */ - private boolean isSameYear(Date date, Date now) { - return date.getYear() == now.getYear(); - } - - /** - * This is used to get a efficient time for JS. - * Warning! Use TimerService if you want to actually test and control the time. - */ - public double currentTimeMillis() { - // Use an optimised time for JS when running in JS. - if (!GWT.isClient()) { - return System.currentTimeMillis(); - } else { - return Duration.currentTimeMillis(); - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/DomHelper.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/DomHelper.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/DomHelper.java deleted file mode 100644 index 518a9b3..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/DomHelper.java +++ /dev/null @@ -1,730 +0,0 @@ -/** - * 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.waveprotocol.wave.client.common.util; - -import com.google.gwt.core.client.JavaScriptException; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArray; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.impl.FocusImpl; - -import org.waveprotocol.wave.model.document.util.Point; -import org.waveprotocol.wave.model.util.CollectionUtils; -import org.waveprotocol.wave.model.util.IdentitySet; -import org.waveprotocol.wave.model.util.Preconditions; -import org.waveprotocol.wave.model.util.ReadableStringSet; -import org.waveprotocol.wave.model.util.StringSet; - - -/** - * Helper methods - * - * Some adapted from UIElement, so the interface could do with increasing consistency - * - * TODO(danilatos,user): Clean up / organise methods in this class - * - * @author [email protected] (Daniel Danilatos) - */ -public class DomHelper { - - /** - * Describes the editability of an element, ignoring its context (ancestor nodes, etc). - */ - public enum ElementEditability { - /** The element is definitely editable */ - EDITABLE, - /** The element is not editable */ - NOT_EDITABLE, - /** The element is "neutral", which means its editability is inherited */ - NEUTRAL - } - - /** Webkit editability controlling css property */ - public static final String WEBKIT_USER_MODIFY = "-webkit-user-modify"; - - /** - * Interface for receiving low-level javascript events - */ - public interface JavaScriptEventListener { - - /** - * @param name The event name, without any leading "on-" prefix - * @param event The native event object - */ - void onJavaScriptEvent(String name, Event event); - } - - private DomHelper() {} - - /** - * Return true if the element is a text box - * @param element - * @return true if the element is a text box - */ - public static boolean isTextBox(Element element) { - return "input".equalsIgnoreCase(element.getTagName()) - && "text".equalsIgnoreCase(element.getAttribute("type")); - } - - /** - * @param element - * @param styleName - * @return true if the element or an ancestor has the given stylename - */ - public static boolean hasStyleOrAncestorHasStyle(Element element, String styleName) { - while (element != null) { - if (element.getClassName().indexOf(styleName) >= 0) { - return true; - } - element = element.getParentElement(); - } - return false; - } - - /** - * Cast to old-style Element. - * - * TODO(danilatos): Deprecate this method when GWT has updated everything to not require - * the old style Element. - * - * @param element new style element - * @return old style element - */ - public static com.google.gwt.user.client.Element castToOld(Element element) { - return element.cast(); - } - - /** - * Create a div with the given style name set. Convenience method because - * this is such a common task - * @param styleName - * @return The created div element - */ - public static DivElement createDivWithStyle(String styleName) { - DivElement d = Document.get().createDivElement(); - d.setClassName(styleName); - return d; - } - - /** - * Focus the element, if possible - * @param element - */ - public static void focus(Element element) { - // NOTE(user): This may not work for divs, rather use getFocusImplForPanel - // for divs. - try { - FocusImpl.getFocusImplForWidget().focus(castToOld(element)); - } catch (Exception e) { - // Suppress null pointer condition - } - } - - /** - * Blur the element, if possible - * @param element - * - * NOTE(user): Dan thinks this method should be deprecated, but is not - * sure why... Dan, please update once you remember. - */ - public static void blur(Element element) { - FocusImpl.getFocusImplForWidget().blur(castToOld(element)); - } - - /** - * Sets display:none on the given element if isVisible is false, and clears - * the display css property if isVisible is true. - * - * This idiom is commonly switched on a boolean, so this method takes care of - * the 5 lines of boilerplate. - * - * @param element - * @param isVisible - */ - public static void setDisplayVisible(Element element, boolean isVisible) { - if (isVisible) { - element.getStyle().clearDisplay(); - } else { - element.getStyle().setDisplay(Display.NONE); - } - } - - /** - * Finds the index of an element in its parent's list of child elements. - * This is not the same as {@link #findChildIndex(Node)}, since it ignores - * non-element nodes. It is in line with the element-only view of a collection - * of children exposed by {@link Element#getFirstChildElement()} and - * {@link Element#getNextSiblingElement()}. - * - * @param child an element - * @return the index of {@code child}, or -1 if {@code child} is not a child - * of its parent. - * @see #findChildIndex(Node) - */ - public static final int findChildElementIndex(Element child) { - Element parent = child.getParentElement(); - Element e = parent.getFirstChildElement(); - int i = 0; - while (e != null) { - if (e.equals(child)) { - return i; - } else { - e = e.getNextSiblingElement(); - i++; - } - } - return -1; - } - - /** - * Wrap at least one node - * @param with The element in which to wrap the nodes - * @param from First node to wrap - * @param toExcl Node after end of wrap range - */ - public static void wrap(Element with, Node from, Node toExcl) { - from.getParentNode().insertBefore(with, from); - moveNodes(with, from, toExcl, null); - } - - /** - * @param element The element to unwrap. If not attached, does nothing. - */ - public static void unwrap(Element element) { - if (element.hasParentElement()) { - moveNodes(element.getParentElement(), - element.getFirstChild(), null, element.getNextSibling()); - element.removeFromParent(); - } - } - - /** - * Insert before, but for a range of adjacent siblings - * - * TODO(danilatos): Apparently safari and firefox let you do this in one - * go using ranges, which could be a lot faster than iterating manually. - * Create a deferred binding implementation. - * @param parent - * @param from - * @param toExcl - * @param refChild - */ - public static void moveNodes(Element parent, Node from, Node toExcl, Node refChild) { - for (Node n = from; n != toExcl; ) { - Node m = n; - n = n.getNextSibling(); - parent.insertBefore(m, refChild); - } - } - - /** - * Remove nodes in the given range from the DOM - * @param from - * @param toExcl - */ - public static void removeNodes(Node from, Node toExcl) { - if (from == null || !from.hasParentElement()) { - return; - } - for (Node n = from; n != toExcl && n != null;) { - Node r = n; - n = n.getNextSibling(); - r.removeFromParent(); - } - } - - /** - * Remove all children from an element - * @param element - */ - public static void emptyElement(Element element) { - while (element.getFirstChild() != null) { - element.removeChild(element.getFirstChild()); - } - } - - /** - * Ensures the given container contains exactly one child, the given one. - * Provides the important property that if the container is already the parent - * of the given child, then the child is not removed and re-added, it is left - * there; any siblings, if present, are removed. - * - * @param container - * @param child - */ - public static void setOnlyChild(Element container, Node child) { - if (child.getParentElement() != container) { - // simple case - emptyElement(container); - container.appendChild(child); - } else { - // tricky case - avoid removing then re-appending the same child - while (child.getNextSibling() != null) { - child.getNextSibling().removeFromParent(); - } - while (child.getPreviousSibling() != null) { - child.getPreviousSibling().removeFromParent(); - } - } - } - - /** - * Swaps out the old element for the new element. - * The old element's children are added to the new element - * - * @param oldElement - * @param newElement - */ - public static void replaceElement(Element oldElement, Element newElement) { - - // TODO(danilatos): Profile this to see if it is faster to move the nodes first, - // and then remove, or the other way round. Profile and optimise some of these - // other methods too. Take dom mutation event handlers being registered into account. - - if (oldElement.hasParentElement()) { - oldElement.getParentElement().insertBefore(newElement, oldElement); - oldElement.removeFromParent(); - } - - DomHelper.moveNodes(newElement, oldElement.getFirstChild(), null, null); - } - - /** - * Make an element editable or not - * - * @param element - * @param whiteSpacePreWrap Whether to additionally turn on the white space - * pre wrap property. If in doubt, set to true. This is what we use for - * the editor. So for any concurrently editable areas and such, we must - * use true. If false, does nothing (it does not clear the property). - * @param isEditable - * @return the same element for convenience - */ - public static Element setContentEditable(Element element, boolean isEditable, - boolean whiteSpacePreWrap) { - if (UserAgent.isSafari()) { - // We MUST use the "plaintext-only" variant to prevent nasty things like - // Apple+B munging our dom without giving us a key event. - - // Assertion in GWT stuffs this up... fix GWT, in the meantime use a string map - // element.getStyle().setProperty("-webkit-user-modify", - // isEditable ? "read-write-plaintext-only" : "read-only"); - - JsoView.as(element.getStyle()).setString("-webkit-user-modify", - isEditable ? "read-write-plaintext-only" : "read-only"); - } else { - element.setAttribute("contentEditable", isEditable ? "true" : "false"); - } - - if (whiteSpacePreWrap) { - // More GWT assertion fun! - JsoView.as(element.getStyle()).setString("white-space", "pre-wrap"); - } - - return element; - } - - /** - * Checks whether the given DOM element is editable, either explicitly or - * inherited from its ancestors. - * @param e Element to check - */ - public static boolean isEditable(Element e) { - // special early-exit for problematic shadow dom: - if (isUnreadable(e)) { - return true; - } - - Element docElement = Document.get().getDocumentElement(); - do { - ElementEditability editability = getElementEditability(e); - if (editability == ElementEditability.NEUTRAL) { - if (e == docElement) { - return false; - } - e = e.getParentElement(); - } else { - return editability == ElementEditability.EDITABLE ? true : false; - } - } while (e != null); - - // NOTE(danilatos): We didn't hit the body. The only way I know that this can happen - // is if the browser gave us a text node from its SHADOW dom, e.g. in a text box, - // which doesn't have any text node children. I've observed the parent of this text node - // to be reported as a div, and the parent of that div to be null. - return true; - } - - public static ElementEditability getElementEditability(Element elem) { - // NOTE(danilatos): This is not necessarily accurate in 100% of situations, with weird - // combinations of editability/enabled etc attributes and tagnames... - - String tagName = null; - try { - tagName = elem.getTagName(); - } catch (Exception exception) { - // Couldn't get access to the tag name for some reason (see b/2314641). - } - - if (tagName != null) { - tagName = tagName.toLowerCase(); - if (tagName.equals("input") || tagName.equals("textarea")) { - return ElementEditability.EDITABLE; - } - } - - return getContentEditability(elem); - } - - /** - * @param element - * @return editability in terms of content-editable only (ignore tag names) - */ - public static ElementEditability getContentEditability(Element element) { - String editability = null; - if (UserAgent.isSafari()) { - JsoView style = JsoView.as(element.getStyle()); - editability = style.getString(WEBKIT_USER_MODIFY); - if ("read-write-plaintext-only".equalsIgnoreCase(editability) || - "read-write".equalsIgnoreCase(editability)) { - return ElementEditability.EDITABLE; - } else if (editability != null && !editability.isEmpty()) { - return ElementEditability.NOT_EDITABLE; - } - - // NOTE(danilatos): The css property overrides the contentEditable attribute. - // Still keep going just to check the content editable prop, if no css property set. - } - try { - editability = element.getAttribute("contentEditable"); - } catch (JavaScriptException e) { - String elementString = "<couldn't get element string>"; - String elementTag = "<couldn't get element tag>"; - try { - elementString = element.toString(); - } catch (Exception exception) { } - try { - elementTag = element.getTagName(); - } catch (Exception exception) { } - - StringBuilder sb = new StringBuilder(); - sb.append("Couldn't get the 'contentEditable' attribute for element '"); - sb.append(elementString).append("' tag name = ").append(elementTag); - throw new RuntimeException(sb.toString(), e); - } - if (editability == null || editability.isEmpty()) { - return ElementEditability.NEUTRAL; - } else { - return "true".equalsIgnoreCase(editability) - ? ElementEditability.EDITABLE : ElementEditability.NOT_EDITABLE; - } - } - - /** - * Sets the spell check attribute on the element. - * @param enabled true to enable spell check, false to disable. - */ - public static void setNativeSpellCheck(Element element, boolean enabled) { - element.setAttribute("spellcheck", enabled ? "true" : "false"); - } - - /** - * Makes an element, and all its descendant elements, unselectable. - */ - public static void makeUnselectable(Element e) { - if (UserAgent.isIE()) { - e.setAttribute("unselectable", "on"); - e = e.getFirstChildElement(); - while (e != null) { - makeUnselectable(e); - e = e.getNextSiblingElement(); - } - } - } - - /** - * Used to remove event handlers from elements - * - * @see DomHelper#registerEventHandler(Element, String, JavaScriptEventListener) - */ - public static final class HandlerReference extends JavaScriptObject { - - /***/ - protected HandlerReference() {} - - /** - * Unregister a handler registered with - * {@link #registerEventHandler(Element, String, JavaScriptEventListener)} or - * {@link #registerEventHandler(Element, String, boolean, JavaScriptEventListener)} - * - * @return true if the handler was unregistered, false if unregister had - * already been called. - */ - public native boolean unregister() /*-{ - var el = this.$el; - if (el == null) { - return false; - } - - if (el.removeEventListener) { - el.removeEventListener(this.$ev, this, this.$cp); - } else if (el.detachEvent) { - el.detachEvent('on' + this.$ev, this); - } else { - el['on' + this.$ev] = null; - } - - this.$ev = null; - return true; - }-*/; - } - - /** - * A set of {@link HandlerReference} for when registering and unregistering a - * handler on multiple events at once. - */ - public static final class HandlerReferenceSet { - public IdentitySet<HandlerReference> references = CollectionUtils.createIdentitySet(); - - public void unregister() { - Preconditions.checkState(references != null, "References already unregistered"); - references.each(new IdentitySet.Proc<HandlerReference>() { - @Override - public void apply(HandlerReference ref) { - ref.unregister(); - } - }); - references = null; - } - } - - /** - * A low level way to register event handlers on dom elements. This differs - * from sinkEvents in that it has nothing to do with widgets, and also allows - * specifying any event name as a string. - * - * NOTE(danilatos): Care must be taken when using this low-level technique, - * you will need to handle your own cleanup to avoid memory leaks. - * - * @param el The dom element on which to listen to events - * @param eventName The name of the event, without any "on-" prefix - * @param listener - * @return a handler to be used with de-registering - */ - public static HandlerReference registerEventHandler(Element el, - String eventName, JavaScriptEventListener listener) { - return registerEventHandler(el, eventName, false, listener); - } - - // TODO(danilatos): Split the implementation out into browser-specific versions - - /** - * Same as {@link #registerEventHandler(Element, String, JavaScriptEventListener)} - * except provides the (non-cross-browser) capture parameter - */ - public static native HandlerReference registerEventHandler(Element el, - String eventName, boolean capture, JavaScriptEventListener listener) /*-{ - - var func = $entry(function(e) { - var evt = e || $wnd.event; - listener. - @org.waveprotocol.wave.client.common.util.DomHelper.JavaScriptEventListener::onJavaScriptEvent(Ljava/lang/String;Lcom/google/gwt/user/client/Event;) - (eventName, evt); - }); - - if (el.addEventListener) { - el.addEventListener(eventName, func, capture); - } else if (el.attachEvent) { - el.attachEvent('on' + eventName, func); - } else { - el['on' + eventName.toLowerCase()] = func; - } - - // Setup handler reference object - func.$ev = eventName; - func.$cp = capture; - func.$el = el; - return func; - }-*/; - - /** - * Registers a listener for multiple browser events in one go - * - * @param el element to listen on - * @param eventNames set of events - * @param listener - * @return a reference set to be used for unregistering the handler for all - * events in one go - */ - public static HandlerReferenceSet registerEventHandler(final Element el, - ReadableStringSet eventNames, final JavaScriptEventListener listener) { - Preconditions.checkArgument(!eventNames.isEmpty(), "registerEventHandler: Event set is empty"); - final HandlerReferenceSet referenceSet = new HandlerReferenceSet(); - eventNames.each(new StringSet.Proc() { - @Override - public void apply(String eventName) { - referenceSet.references.add(registerEventHandler(el, eventName, listener)); - } - }); - return referenceSet; - } - - /** - * @return true if it is an element - */ - public static boolean isElement(Node n) { - return n.getNodeType() == Node.ELEMENT_NODE; - } - - /** - * @return true if it is a text node - */ - public static boolean isTextNode(Node n) { - return n.getNodeType() == Node.TEXT_NODE; - } - - /** - * Finds the index of an element among its parent's children, including - * text nodes. - * @param toFind the node to retrieve the index for - * @return index of element - * - * TODO(danilatos): This could probably be done faster with - * a binary search using text ranges. - * TODO(lars): adapt to non standard browsers. - * TODO(lars): is there a single js call that does this? - */ - public static native int findChildIndex(Node toFind) /*-{ - var parent = toFind.parentNode; - var count = 0, child = parent.firstChild; - while (child) { - if (child == toFind) - return count; - if (child.nodeType == 1 || child.nodeType == 3) - ++count; - child = child.nextSibling; - } - - return -1; - }-*/; - - /** - * The last child of element this element. If there is no such element, this - * returns null. - */ - // GWT forgot to add Element.getLastChildElement(), to be symmetric with - // Element.getFirstChildElement(). - public static native Element getLastChildElement(Element elem) /*-{ - var child = elem.lastChild; - while (child && child.nodeType != 1) - child = child.previousSibling; - return child; - }-*/; - - /** - * Gets a list of descendants of e that match the given class name. - * - * If the browser has the native method, that will be called. Otherwise, it - * traverses descendents of the given element and returns the list of elements - * with matching classname. - * - * @param e - * @param className - */ - public static NodeList<Element> getElementsByClassName(Element e, String className) { - if (QuirksConstants.SUPPORTS_GET_ELEMENTS_BY_CLASSNAME) { - return getElementsByClassNameNative(e, className); - } else { - NodeList<Element> all = e.getElementsByTagName("*"); - if (all == null) { - return null; - } - JsArray<Element> ret = JavaScriptObject.createArray().cast(); - for (int i = 0; i < all.getLength(); ++i) { - Element item = all.getItem(i); - if (className.equals(item.getClassName())) { - ret.push(item); - } - } - return ret.cast(); - } - } - - private static native NodeList<Element> getElementsByClassNameNative( - Element e, String className) /*-{ - return e.getElementsByClassName(className); - }-*/; - - - /** - * Checks whether the properties of given node cannot be accessed (by testing the nodeType). - * - * It is sometimes the case where we need to access properties of a Node, but the properties - * on that node are not readable (for example, a shadow node like a div created to hold the - * selection within an input field). - * - * In these cases, when the javascript cannot access the node's properties, any attempt to do - * so may cause an internal permissions exception. This method swallows the exception and uses - * its existence to indicate whether or not the node is actually readable. - * - * @param n Node to check - * @return Whether or not the node can have properties read. - */ - public static boolean isUnreadable(Node n) { - try { - n.getNodeType(); - return false; - } catch (RuntimeException e) { - return true; - } - } - - /** - * Converts a nodelet/offset pair to a Point of Node. - * Just a simple mapping, it is agnostic to inconsistencies, filtered views, etc. - * @param node - * @param offset - * @return html node point - */ - public static Point<Node> nodeOffsetToNodeletPoint(Node node, int offset) { - if (isTextNode(node)) { - return Point.inText(node, offset); - } else { - Element container = node.<Element>cast(); - return Point.inElement(container, nodeAfterFromOffset(container, offset)); - } - } - - /** - * Given a node/offset pair, return the node after the point. - * - * @param container - * @param offset - */ - public static Node nodeAfterFromOffset(Element container, int offset) { - return offset >= container.getChildCount() ? null : container.getChild(offset); - } -}
