/**
* Copyright 2010 Google Inc.
*
* Licensed 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.doodad.link;
import com.google.common.base.Preconditions;
import org.waveprotocol.wave.client.common.safehtml.EscapeUtils;
import org.waveprotocol.wave.client.common.util.WaveRefConstants;
import org.waveprotocol.wave.model.id.DualIdSerialiser;
import org.waveprotocol.wave.model.id.InvalidIdException;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.ReadableStringSet;
import org.waveprotocol.wave.model.waveref.InvalidWaveRefException;
import org.waveprotocol.wave.model.waveref.WaveRef;
import org.waveprotocol.wave.util.escapers.GwtWaverefEncoder;
import javax.annotation.Nullable;
/**
* Link utilities
*
*/
public final class Link {
private static final ReadableStringSet WEB_SCHEMES = CollectionUtils.newStringSet(
"http", "https", "ftp", "mailto");
private static final String INVALID_LINK_MSG =
"Invalid link. Should either be a web url\n" +
"or, a Wave ref in the form: wave://example.com/w+1234/~/conv+root/b+abcd\n" +
"or be a valid serialized wave id";
public static class InvalidLinkException extends Exception {
public InvalidLinkException(String message, Throwable cause) {
super(message, cause);
}
public InvalidLinkException(String message) {
super(message);
}
public InvalidLinkException(Throwable cause) {
super(cause);
}
}
/** Key prefix */
public static final String PREFIX = "link";
/** Key for 'linky' agent created links. */
public static final String AUTO_KEY = PREFIX + "/auto";
/** Key for manually created links. */
public static final String MANUAL_KEY = PREFIX + "/manual";
/**
* Key for wave links.
*
* @deprecated Use link/manual with value of the form:
* wave://example.com/w+1234/~/conv+root/b+abcd
*/
@Deprecated
public static final String WAVE_KEY = PREFIX + "/wave";
/**
* Array of all link keys
*
* NOTE(user): Code may implicitly depend on the order of these keys. We
* should expose LINK_KEYS as a set, and have ORDERED_LINK_KEYS for code that
* relies on specific ordering.
*/
public static final String[] LINK_KEYS = {AUTO_KEY, MANUAL_KEY, WAVE_KEY};
private Link() {
}
/**
* Adapts the value of manual/link to the actual value that browser will use
* to navigate
*
* @param uri
* @return actual value that will be used by browser, or null if no link
* should be rendered.
*/
public static @Nullable String toHrefFromUri(String uri) {
// First, normalise it in case we have junk data.
// We can optionally replace this step with something that just
// returns null if the uri is not supported.
try {
uri = normalizeLink(uri);
} catch (InvalidLinkException e1) {
return null;
}
// If it's a wave link, use # for local navigation
if (uri.startsWith(WaveRefConstants.WAVE_URI_PREFIX)) {
try {
WaveRef ref = GwtWaverefEncoder.decodeWaveRefFromPath(
uri.substring(WaveRefConstants.WAVE_URI_PREFIX.length()));
return "#" + GwtWaverefEncoder.encodeToUriPathSegment(ref);
} catch (InvalidWaveRefException e) {
return null;
}
}
assert EscapeUtils.extractScheme(uri) != null;
// Otherwise, just return the given link.
return uri;
}
/**
* @return best guess link annotation value from an arbitrary string. feeding
* the return value back through this method should always return the
* input.
* @throws InvalidLinkException
*/
@SuppressWarnings("deprecation")
public static String normalizeLink(String rawLinkValue) throws InvalidLinkException {
Preconditions.checkNotNull(rawLinkValue);
rawLinkValue = rawLinkValue.trim();
String[] parts = splitUri(rawLinkValue);
String scheme = parts != null ? parts[0] : null;
// Normal web url
if (scheme != null && WEB_SCHEMES.contains(scheme)) {
return rawLinkValue;
}
// Try to interpret a wave URI or naked waveid/waveref
try {
// NOTE(danilatos): Pasting in the raw serialized form of a wave ref is
// not supported here. In practice this doesn't really matter.
WaveRef ref;
if (WaveRefConstants.WAVE_SCHEME.equals(scheme)) {
ref = GwtWaverefEncoder.decodeWaveRefFromPath(parts[1]);
} else if (scheme == null) {
ref = inferWaveRef(rawLinkValue);
} else if (WaveRefConstants.WAVE_SCHEME_OLD.equals(scheme)) {
ref = inferWaveRef(parts[1]);
} else {
// Scheme is not a regular web scheme nor a wave scheme.
throw new InvalidLinkException("Unsupported URL scheme: " + scheme);
}
return WaveRefConstants.WAVE_URI_PREFIX + GwtWaverefEncoder.encodeToUriPathSegment(ref);
} catch (InvalidWaveRefException e) {
throw new InvalidLinkException(INVALID_LINK_MSG, e);
}
}
/**
* Splits a URI string into its scheme and suffix components, if it matches.
*
* @return [scheme, suffix] for scheme://suffix, or null if it doesn't match.
*/
private static String[] splitUri(String uri) {
int sepLength = "://".length();
String scheme = EscapeUtils.extractScheme(uri);
if (scheme == null || uri.length() <= scheme.length() + sepLength) {
return null;
}
return new String[] {scheme, uri.substring(scheme.length() + sepLength)};
}
private static WaveRef inferWaveRef(String rawString) throws InvalidWaveRefException {
try {
return GwtWaverefEncoder.decodeWaveRefFromPath(rawString);
} catch (InvalidWaveRefException e) {
// Let's try decoding it as a serialized wave id instead
try {
return WaveRef.of(DualIdSerialiser.MODERN.deserialiseWaveId(rawString));
} catch (InvalidIdException e1) {
// Didn't work. Just re-throw the original exception
throw e;
}
}
}
}