1 /* 2 * Copyright (C) 2010 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.clearsilver.jsilver.functions.html; 18 19 import com.google.clearsilver.jsilver.functions.TextFilter; 20 21 import java.io.IOException; 22 import java.lang.Character.UnicodeBlock; 23 24 /** 25 * Validates that a given string is either something that looks like a relative URI, or looks like 26 * an absolute URI using one of a set of allowed schemes (http, https, ftp, mailto). If the string 27 * is valid according to these criteria, the string is escaped with an appropriate escaping 28 * function. Otherwise, the string "#" is returned. 29 * 30 * Subclasses will apply the necessary escaping function to the string by overriding {@code 31 * applyEscaping}. 32 * 33 * <p> 34 * Note: this function does <em>not</em> validate that the URI is well-formed beyond the scheme part 35 * (and if the URI appears to be relative, not even then). Note in particular that this function 36 * considers strings of the form "www.google.com:80" to be invalid. 37 */ 38 public abstract class BaseUrlValidateFunction implements TextFilter { 39 40 @Override filter(String in, Appendable out)41 public void filter(String in, Appendable out) throws IOException { 42 if (!isValidUri(in)) { 43 out.append('#'); 44 return; 45 } 46 applyEscaping(in, out); 47 } 48 49 /** 50 * Called by {@code filter} after verifying that the input is a valid URI. Should apply any 51 * appropriate escaping to the input string. 52 * 53 * @throws IOException 54 */ applyEscaping(String in, Appendable out)55 protected abstract void applyEscaping(String in, Appendable out) throws IOException; 56 57 /** 58 * @return true if a given string either looks like a relative URI, or like an absolute URI with 59 * an allowed scheme. 60 */ isValidUri(String in)61 protected boolean isValidUri(String in) { 62 // Quick check for the allowed absolute URI schemes. 63 String maybeScheme = toLowerCaseAsciiOnly(in.substring(0, Math.min(in.length(), 8))); 64 if (maybeScheme.startsWith("http://") || maybeScheme.startsWith("https://") 65 || maybeScheme.startsWith("ftp://") || maybeScheme.startsWith("mailto:")) { 66 return true; 67 } 68 69 // If it's an absolute URI with a different scheme, it's invalid. 70 // ClearSilver defines an absolute URI as one that contains a colon prior 71 // to any slash. 72 int slashPos = in.indexOf('/'); 73 if (slashPos != -1) { 74 // only colons before this point are bad. 75 return in.lastIndexOf(':', slashPos - 1) == -1; 76 } else { 77 // then any colon is bad. 78 return in.indexOf(':') == -1; 79 } 80 } 81 82 /** 83 * Converts an ASCII string to lowercase. Non-ASCII characters are replaced with '?'. 84 */ toLowerCaseAsciiOnly(String string)85 private String toLowerCaseAsciiOnly(String string) { 86 char[] ca = string.toCharArray(); 87 for (int i = 0; i < ca.length; i++) { 88 char ch = ca[i]; 89 ca[i] = 90 (Character.UnicodeBlock.of(ch) == UnicodeBlock.BASIC_LATIN) 91 ? Character.toLowerCase(ch) 92 : '?'; 93 } 94 return new String(ca); 95 } 96 } 97