/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/kernel/trunk/kernel-util/src/main/java/org/sakaiproject/util/Web.java $
* $Id: Web.java 105621 2012-03-07 22:05:58Z aaronz@vt.edu $
***********************************************************************************
*
* Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 Sakai Foundation
*
* Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.util;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.tool.api.SessionManager;
/**
* <p>
* Web is a web (html, http, etc) technlogies collection of helper methods.
* </p>
* @deprecated use apache commons utils for {@link org.sakaiproject.util.api.FormattedText}, this will be removed after 2.9 - Dec 2011
*/
@Deprecated
public class Web
{
/** Our log (commons). */
private static Log M_log = LogFactory.getLog(Web.class);
// used to remove javascript from html
private static final String START_JAVASCRIPT = "<script";
private static final String END_JAVASCRIPT = "</script>";
private static SessionManager sessionManager = (SessionManager)
ComponentManager.get(SessionManager.class);
/**
* Escape a plaintext string so that it can be output as part of an HTML document. Amperstand, greater-than, less-than, newlines, etc, will be escaped so that they display (instead of being interpreted as formatting).
*
* @param value
* The string to escape.
* @return value fully escaped for HTML.
* @deprecated this is a passthrough for {@link FormattedText#escapeHtml(String, boolean)} so use that instead
*/
public static String escapeHtml(String value)
{
return FormattedText.escapeHtml(value, true);
}
/**
* Escape HTML-formatted text in preparation to include it in an HTML document.
*
* @param value
* The string to escape.
* @return value escaped for HTML.
* @deprecated this is a passthrough for {@link FormattedText#escapeHtmlFormattedText(String)} so use that instead
*/
public static String escapeHtmlFormattedText(String value)
{
return FormattedText.escapeHtmlFormattedText(value);
}
/**
* Escape the given value so that it appears as-is in HTML - that is, HTML meta-characters like '<' are escaped to HTML character entity references like '<'. Markup, amper, quote are escaped. Whitespace is not.
*
* @param value
* The string to escape.
* @param escapeNewlines
* Whether to escape newlines as "<br />\n" so that they appear as HTML line breaks.
* @return value fully escaped for HTML.
* @deprecated this is a passthrough for {@link FormattedText#escapeHtml(String, boolean)} so use that instead
*/
public static String escapeHtml(String value, boolean escapeNewlines) {
return FormattedText.escapeHtml(value, escapeNewlines);
}
/**
* Return a string based on value that is safe to place into a javascript value that is in single quiotes.
*
* @param value
* The string to escape.
* @return value escaped.
* @deprecated
* Use http://commons.apache.org/lang/api/org/apache/commons/lang/StringEscapeUtils.html instead (see KNL-69).
* @deprecated just a passthrough for {@link Validator#escapeJsQuoted(String)} so use that instead
*/
public static String escapeJsQuoted(String value)
{
return Validator.escapeJsQuoted(value);
}
/**
* Return a string based on id that is fully escaped using URL rules, using a UTF-8 underlying encoding.
*
* Note: java.net.URLEncode.encode() provides a more standard option
* FormattedText.decodeNumericCharacterReferences() undoes this op
*
* @param id
* The string to escape.
* @return id fully escaped using URL rules.
* @deprecated just a passthrough for {@link Validator#escapeUrl(String)} so use that instead
*/
public static String escapeUrl(String id)
{
return Validator.escapeUrl( id );
} // escapeUrl
/**
* Returns a String with HTML entity references converted to characters suitable for processing as formatted text.
*
* @param value
* The text containing entity references (e.g., a News item description).
* @return The HTML, ready for processing.
* @deprecated just a copy of {@link org.sakaiproject.util.api.FormattedText#unEscapeHtml(String)} so use that instead
*/
public static String unEscapeHtml(String value)
{
// FIXME delete this method
if (value == null) return "";
if (value.equals("")) return "";
value = value.replaceAll("<", "<");
value = value.replaceAll(">", ">");
value = value.replaceAll("&", "&");
value = value.replaceAll(""", "\"");
return value;
}
/**
* For converting plain-text URLs in a String to HTML <a> tags
* Any URLs in the source text that happen to be already in a <a> tag will be unaffected.
* @param text the plain text to convert
* @return the full source text with URLs converted to HTML.
* @deprecated just a copy of {@link org.sakaiproject.util.api.FormattedText#encodeUrlsAsHtml(String)} so use that instead
*/
public static String encodeUrlsAsHtml(String text)
{
Pattern p = Pattern.compile("(?<!href=['\"]{1})(((https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt)://|mailto:)[-:;@a-zA-Z0-9_.,~%+/?=]+(?<![.,?:]))");
Matcher m = p.matcher(text);
StringBuffer buf = new StringBuffer();
while(m.find()) {
String matchedUrl = m.group();
m.appendReplacement(buf, "<a href=\"" + Web.unEscapeHtml(matchedUrl) + "\">$1</a>");
}
m.appendTail(buf);
return buf.toString();
}
/**
* Return a string based on value that is safe to place into a javascript / html identifier: anything not alphanumeric change to 'x'. If the first character is not alphabetic, a letter 'i' is prepended.
*
* @param value
* The string to escape.
* @return value fully escaped using javascript / html identifier rules.
*/
public static String escapeJavascript(String value)
{
if (value == null || "".equals(value)) return "";
try
{
StringBuilder buf = new StringBuilder();
// prepend 'i' if first character is not a letter
if (!java.lang.Character.isLetter(value.charAt(0)))
{
buf.append("i");
}
// change non-alphanumeric characters to 'x'
for (int i = 0; i < value.length(); i++)
{
char c = value.charAt(i);
if (!java.lang.Character.isLetterOrDigit(c))
{
buf.append("x");
}
else
{
buf.append(c);
}
}
String rv = buf.toString();
return rv;
}
catch (Exception e)
{
M_log.warn("escapeJavascript: ", e);
return value;
}
}
/**
* Returns the hex digit cooresponding to a number between 0 and 15.
*
* @param i
* The number to get the hex digit for.
* @return The hex digit cooresponding to that number.
* @exception java.lang.IllegalArgumentException
* If supplied digit is not between 0 and 15 inclusive.
*/
protected static final char hexDigit(int i)
{
switch (i)
{
case 0:
return '0';
case 1:
return '1';
case 2:
return '2';
case 3:
return '3';
case 4:
return '4';
case 5:
return '5';
case 6:
return '6';
case 7:
return '7';
case 8:
return '8';
case 9:
return '9';
case 10:
return 'A';
case 11:
return 'B';
case 12:
return 'C';
case 13:
return 'D';
case 14:
return 'E';
case 15:
return 'F';
}
throw new IllegalArgumentException("Invalid digit:" + i);
}
/**
* Form a path string from the parts of the array starting at index start to the end, each with a '/' in front.
*
* @param parts
* The parts strings
* @param start
* The index of the first part to use
* @param end
* The index past the last part to use
* @return a path string from the parts of the array starting at index start to the end, each with a '/' in front.
*/
public static String makePath(String[] parts, int start, int end)
{
StringBuilder buf = new StringBuilder();
for (int i = start; i < end; i++)
{
buf.append('/');
buf.append(parts[i]);
}
if (buf.length() > 0) return buf.toString();
return null;
}
protected static void print(PrintWriter out, String name, int value)
{
out.print(" " + name + ": ");
if (value == -1)
{
out.println("none");
}
else
{
out.println(value);
}
}
protected static void print(PrintWriter out, String name, String value)
{
out.print(" " + name + ": ");
out.println(value == null ? "none" : value);
}
/**
* Compute the URL that would return to this servlet based on the current request, with the optional path and parameters
*
* @param req
* The request.
* @return The URL back to this servlet based on the current request.
*/
public static String returnUrl(HttpServletRequest req, String path)
{
StringBuilder url = new StringBuilder();
url.append(serverUrl(req));
url.append(req.getContextPath());
url.append(req.getServletPath());
if (path != null) url.append(path);
// TODO: params
return url.toString();
}
/**
* Send the HTML / Javascript to invoke an automatic update
*
* @param out
* @param req
* The request.
* @param placementId
* The tool's placement id / presence location / part of the delivery address
* @param updateTime
* The time (seconds) between courier checks
* @deprecated
* To avoid inappropriate kernel dependencies, construct this URL in the tool pending relocation of this to courier (see SAK-18481).
*/
public static void sendAutoUpdate(PrintWriter out, HttpServletRequest req, String placementId, int updateTime)
{
String userId = sessionManager.getCurrentSessionUserId();
StringBuilder url = new StringBuilder(serverUrl(req));
url.append("/courier/");
url.append(placementId);
url.append("?userId=");
url.append(userId);
out.println("<script type=\"text/javascript\" language=\"JavaScript\">");
out.println("updateTime = " + updateTime + "000;");
out.println("updateUrl = \"" + url.toString() + "\";");
out.println("scheduleUpdate();");
out.println("</script>");
}
/**
* Compute the URL that would return to this server based on the current request.
*
* Note: this method is duplicated in the /sakai-kernel-api/src/main/java/org/sakaiproject/util/RequestFilter.java
*
* @param req
* The request.
* @return The URL back to this server based on the current request.
* @deprecated use RequestFilter.serverUrl
*/
public static String serverUrl(HttpServletRequest req)
{
return RequestFilter.serverUrl(req);
}
public static String snoop(PrintWriter out, boolean html, ServletConfig config, HttpServletRequest req)
{
// if no out, send to system out
ByteArrayOutputStream ostream = null;
if (out == null)
{
ostream = new ByteArrayOutputStream();
out = new PrintWriter(ostream);
html = false;
}
String h1 = "";
String h1x = "";
String pre = "";
String prex = "";
String b = "";
String bx = "";
String p = "";
if (html)
{
h1 = "<h1>";
h1x = "</h1>";
pre = "<pre>";
prex = "</pre>";
b = "<b>";
bx = "</b>";
p = "<p>";
}
Enumeration<?> e = null;
out.println(h1 + "Snoop for request" + h1x);
out.println(req.toString());
if (config != null)
{
e = config.getInitParameterNames();
if (e != null)
{
boolean first = true;
while (e.hasMoreElements())
{
if (first)
{
out.println(h1 + "Init Parameters" + h1x);
out.println(pre);
first = false;
}
String param = (String) e.nextElement();
out.println(" " + param + ": " + config.getInitParameter(param));
}
out.println(prex);
}
}
out.println(h1 + "Request information:" + h1x);
out.println(pre);
print(out, "Request method", req.getMethod());
String requestUri = req.getRequestURI();
print(out, "Request URI", requestUri);
displayStringChars(out, requestUri);
print(out, "Request protocol", req.getProtocol());
String servletPath = req.getServletPath();
print(out, "Servlet path", servletPath);
displayStringChars(out, servletPath);
String contextPath = req.getContextPath();
print(out, "Context path", contextPath);
displayStringChars(out, contextPath);
String pathInfo = req.getPathInfo();
print(out, "Path info", pathInfo);
displayStringChars(out, pathInfo);
print(out, "Path translated", req.getPathTranslated());
print(out, "Query string", req.getQueryString());
print(out, "Content length", req.getContentLength());
print(out, "Content type", req.getContentType());
print(out, "Server name", req.getServerName());
print(out, "Server port", req.getServerPort());
print(out, "Remote user", req.getRemoteUser());
print(out, "Remote address", req.getRemoteAddr());
// print(out, "Remote host", req.getRemoteHost());
print(out, "Authorization scheme", req.getAuthType());
out.println(prex);
e = req.getHeaderNames();
if (e.hasMoreElements())
{
out.println(h1 + "Request headers:" + h1x);
out.println(pre);
while (e.hasMoreElements())
{
String name = (String) e.nextElement();
out.println(" " + name + ": " + req.getHeader(name));
}
out.println(prex);
}
e = req.getParameterNames();
if (e.hasMoreElements())
{
out.println(h1 + "Servlet parameters (Single Value style):" + h1x);
out.println(pre);
while (e.hasMoreElements())
{
String name = (String) e.nextElement();
out.println(" " + name + " = " + req.getParameter(name));
}
out.println(prex);
}
e = req.getParameterNames();
if (e.hasMoreElements())
{
out.println(h1 + "Servlet parameters (Multiple Value style):" + h1x);
out.println(pre);
while (e.hasMoreElements())
{
String name = (String) e.nextElement();
String vals[] = (String[]) req.getParameterValues(name);
if (vals != null)
{
out.print(b + " " + name + " = " + bx);
out.println(vals[0]);
for (int i = 1; i < vals.length; i++)
out.println(" " + vals[i]);
}
out.println(p);
}
out.println(prex);
}
e = req.getAttributeNames();
if (e.hasMoreElements())
{
out.println(h1 + "Request attributes:" + h1x);
out.println(pre);
while (e.hasMoreElements())
{
String name = (String) e.nextElement();
out.println(" " + name + ": " + req.getAttribute(name));
}
out.println(prex);
}
if (ostream != null)
{
out.flush();
return ostream.toString();
}
return "";
}
/**
* Returns a hex representation of a byte.
*
* @param b
* The byte to convert to hex.
* @return The 2-digit hex value of the supplied byte.
*/
protected static final String toHex(byte b)
{
char ret[] = new char[2];
ret[0] = hexDigit((b >>> 4) & (byte) 0x0F);
ret[1] = hexDigit((b >>> 0) & (byte) 0x0F);
return new String(ret);
}
/**
** Encode filename (accomodating UTF-8 characters) for specific browser download/access
** Sadly, Mozilla uses a different encoding scheme than everyone else
** Sadly, Safari has a known bug where doesn't correctly translate encoding for user
**
** This method require inclusion of the javamail mail package.
**/
public static String encodeFileName(HttpServletRequest req, String fileName )
{
String agent = req.getHeader("USER-AGENT");
try
{
if ( agent != null && agent.indexOf("MSIE")>=0 )
fileName = java.net.URLEncoder.encode(fileName, "UTF8");
else if ( agent != null && agent.indexOf("Mozilla")>=0 && agent.indexOf("Safari") == -1 )
fileName = javax.mail.internet.MimeUtility.encodeText(fileName, "UTF8", "B");
else
fileName = java.net.URLEncoder.encode(fileName, "UTF8");
}
catch (java.io.UnsupportedEncodingException e)
{
M_log.error(e);
}
return fileName;
}
private static String internalEscapeHtml(String value, boolean escapeNewlines) {
// FIXME this method needs to be removed entirely and is only here as a reference of how this used to work
if (value == null) return "";
try {
StringBuilder buf = new StringBuilder();
final int len = value.length();
for (int i = 0; i < len; i++)
{
char c = value.charAt(i);
switch (c)
{
case '<':
{
if (buf == null) buf = new StringBuilder(value.substring(0, i));
buf.append("<");
}
break;
case '>':
{
if (buf == null) buf = new StringBuilder(value.substring(0, i));
buf.append(">");
}
break;
case '&':
{
if (buf == null) buf = new StringBuilder(value.substring(0, i));
buf.append("&");
}
break;
case '"':
{
if (buf == null) buf = new StringBuilder(value.substring(0, i));
buf.append(""");
}
break;
case '\n':
{
if (escapeNewlines)
{
if (buf == null) buf = new StringBuilder(value.substring(0, i));
buf.append("<br />\n");
}
else
{
if (buf != null) buf.append(c);
}
}
break;
default:
{
if (c < 128)
{
if (buf != null) buf.append(c);
}
else
{
// escape higher Unicode characters using an
// HTML numeric character entity reference like "㴸"
if (buf == null) buf = new StringBuilder(value.substring(0, i));
buf.append("");
buf.append(Integer.toString((int) c));
buf.append(";");
}
}
break;
}
} // for
return (buf == null) ? value : buf.toString();
}
catch (Exception e)
{
return value;
}
}
/**
** Make sure any HTML is 'clean' (no javascript, invalid image tags)
**/
public static String cleanHtml( String htmlStr )
{
//KNL-610 if a null String is passed return to avoid NPE -DH
if (htmlStr == null)
{
return null;
}
// handle embedded images
htmlStr = htmlStr.replaceAll("<img ", "<img alt='' ");
// remove all javascript (risk of exploit)
// note that String.replaceAll() does not reliably handle line terminators,
// so javascript is removed string by string
while ( htmlStr.indexOf(START_JAVASCRIPT) != -1 )
{
int badStart = htmlStr.indexOf(START_JAVASCRIPT);
int badEnd = htmlStr.indexOf(END_JAVASCRIPT);
String badHtml;
if ( badStart > -1 && badEnd == -1)
badHtml = htmlStr.substring( badStart );
else
badHtml = htmlStr.substring( badStart, badEnd+END_JAVASCRIPT.length() );
// use replace( CharSequence, CharSequence) -- no regexp
htmlStr = htmlStr.replace( new StringBuilder(badHtml), new StringBuilder() );
}
return htmlStr;
}
protected static void displayStringChars(PrintWriter out, String str)
{
if (str == null)
{
out.print("null");
}
else
for (int i = 0; i < str.length(); i++)
{
int c = (int) str.charAt(i);
out.print(Integer.toHexString(c) + " ");
}
out.println();
}
}