001package com.hfg.webapp; 002 003import java.io.File; 004import java.io.IOException; 005import java.io.PrintWriter; 006import java.net.URI; 007 008import javax.servlet.http.Cookie; 009import javax.servlet.http.HttpServletResponse; 010 011import com.hfg.javascript.JsCollection; 012import com.hfg.util.StringUtil; 013import com.hfg.util.mime.MimeType; 014import com.hfg.xml.XMLDoc; 015 016//------------------------------------------------------------------------------ 017/** 018 Webapp-related static functions. 019 <div> 020 @author J. Alex Taylor, hairyfatguy.com 021 </div> 022 */ 023//------------------------------------------------------------------------------ 024// com.hfg XML/HTML Coding Library 025// 026// This library is free software; you can redistribute it and/or 027// modify it under the terms of the GNU Lesser General Public 028// License as published by the Free Software Foundation; either 029// version 2.1 of the License, or (at your option) any later version. 030// 031// This library is distributed in the hope that it will be useful, 032// but WITHOUT ANY WARRANTY; without even the implied warranty of 033// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 034// Lesser General Public License for more details. 035// 036// You should have received a copy of the GNU Lesser General Public 037// License along with this library; if not, write to the Free Software 038// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 039// 040// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 041// [email protected] 042//------------------------------------------------------------------------------ 043 044 045public class WebappUtil 046{ 047 048 //--------------------------------------------------------------------------- 049 public static void setupResponseForFileDownload(HttpServletResponse inResponse, MimeType inMimeType, File inFile) 050 throws Exception 051 { 052 URI filenameURI = new URI(null, null, inFile.getName(), null); 053 054 inResponse.setContentType(inMimeType.toString()); 055 String disposition = "attachment; filename=\"" + filenameURI.toString() + "\""; 056 inResponse.setHeader("Content-disposition", disposition); 057 } 058 059 //--------------------------------------------------------------------------- 060 /** 061 Encodes the cookie value according to section 4.1.1 of RFC 6265. 062 (See <a href='http://www.rfc-editor.org/rfc/rfc6265.txt'></a>http://www.rfc-editor.org/rfc/rfc6265.txt</a>) 063 Note that the cookie-value is limited to US-ASCII but this implementation will include 064 characters above ASCII without escaping them. 065 */ 066 public static String encodeCookieValue(String inUnencodedValue) 067 { 068 StringBuilder encodedValue = new StringBuilder(); 069 if (StringUtil.isSet(inUnencodedValue)) 070 { 071 for (int i = 0; i < inUnencodedValue.length();) 072 { 073 int codePoint = inUnencodedValue.codePointAt(i); 074 075 if ( ((codePoint == '!') // %x21 076 || (codePoint >= '#' && codePoint <= '+') // %x23-2B 077 || (codePoint >= '-' && codePoint <= ':') // %x2D-3A 078 || (codePoint >= '<' && codePoint <= '[') // %x3C-5B 079 || (codePoint >= ']' && codePoint <= '~') // %x5D-7E 080 || (codePoint > 127)) 081 && (codePoint != '%')) // Need to encode '%' due to it's use in the hex encoding 082 { 083 // The character doesn't need to be encoded 084 encodedValue.append((char)codePoint); 085 } 086 else 087 { 088 // Encode the character as the hex-equivalent value 089 encodedValue.append(String.format("%%%02X", codePoint)); 090 } 091 092 i += Character.charCount(codePoint); 093 } 094 } 095 096 return encodedValue.toString(); 097 } 098 099 //--------------------------------------------------------------------------- 100 /** 101 Trys to determine if the cookie value needs to be encoded according to section 4.1.1 of RFC 6265. 102 (See <a href='http://www.rfc-editor.org/rfc/rfc6265.txt'></a>http://www.rfc-editor.org/rfc/rfc6265.txt</a>) 103 */ 104 public static boolean cookieNeedsEncoding(Cookie inCookie) 105 { 106 boolean needsEncoding = false; 107 108 String cookieValue = inCookie.getValue(); 109 110 if (StringUtil.isSet(cookieValue)) 111 { 112 for (int i = 0; i < cookieValue.length(); i++) 113 { 114 int codePoint = cookieValue.codePointAt(i); 115 if (codePoint > 255 116 || Character.isWhitespace(codePoint) 117 || Character.isISOControl(codePoint) 118 || codePoint == '"' 119 || codePoint == ',' 120 || codePoint == ';' 121 || codePoint == '/') 122 { 123 needsEncoding = true; 124 break; 125 } 126 } 127 } 128 129 return needsEncoding; 130 } 131 132 //--------------------------------------------------------------------------- 133 /** 134 Trys to determine if the cookie value has been encoded according to section 4.1.1 of RFC 6265. 135 (See <a href='http://www.rfc-editor.org/rfc/rfc6265.txt'></a>http://www.rfc-editor.org/rfc/rfc6265.txt</a>) 136 */ 137 public static boolean isCookieEncoded(Cookie inCookie) 138 { 139 return isCookieEncoded(inCookie.getValue()); 140 } 141 142 //--------------------------------------------------------------------------- 143 /** 144 Trys to determine if the cookie value has been encoded according to section 4.1.1 of RFC 6265. 145 (See <a href='http://www.rfc-editor.org/rfc/rfc6265.txt'></a>http://www.rfc-editor.org/rfc/rfc6265.txt</a>) 146 */ 147 public static boolean isCookieEncoded(String inCookieValue) 148 { 149 boolean isEncoded = false; 150 if (StringUtil.isSet(inCookieValue)) 151 { 152 for (int i = 0; i < inCookieValue.length();) 153 { 154 int codePoint = inCookieValue.codePointAt(i); 155 156 if (codePoint == '%') 157 { 158 // Is the '%' encoding a character that should be encoded? 159 if (i + 2 < inCookieValue.length()) 160 { 161 try 162 { 163 char potentialDecodedChar = (char) Integer.parseInt(inCookieValue.substring(i + 1, i + 3), 16); 164 165 if (potentialDecodedChar == '%' 166 || (potentialDecodedChar != '!' 167 && !(potentialDecodedChar >= '#' && potentialDecodedChar <= '+') 168 && !(potentialDecodedChar >= '-' && potentialDecodedChar <= ':') 169 && !(potentialDecodedChar >= '<' && potentialDecodedChar <= '[') 170 && !(potentialDecodedChar >= ']' && potentialDecodedChar <= '~'))) 171 { 172 isEncoded = true; 173 break; 174 } 175 } 176 catch (NumberFormatException e) 177 { 178 // The string must not be encoded 179 break; 180 } 181 } 182 } 183 184 i += Character.charCount(codePoint); 185 } 186 } 187 188 return isEncoded; 189 } 190 191 //--------------------------------------------------------------------------- 192 /** 193 Decodes the cookie value according to section 4.1.1 of RFC 6265. 194 (See <a href='http://www.rfc-editor.org/rfc/rfc6265.txt'></a>http://www.rfc-editor.org/rfc/rfc6265.txt</a>) 195 @param inEncodedValue the encoded cookie value to be decoded 196 @return the decoded cookie value 197 */ 198 public static String decodeCookieValue(String inEncodedValue) 199 { 200 StringBuilder decodedValue = new StringBuilder(); 201 if (StringUtil.isSet(inEncodedValue)) 202 { 203 for (int i = 0; i < inEncodedValue.length();) 204 { 205 int codePoint = inEncodedValue.codePointAt(i); 206 207 if (codePoint == '%') 208 { 209 decodedValue.append((char)Integer.parseInt(inEncodedValue.substring(i + 1, i + 3), 16)); 210 i += 3; 211 } 212 else 213 { 214 decodedValue.append((char)codePoint); 215 i += Character.charCount(codePoint); 216 } 217 } 218 } 219 220 return decodedValue.toString(); 221 } 222 223 224 225 //--------------------------------------------------------------------------- 226 /** 227 Writes the specified JSON to the servlet response after setting the mime type and character encoding. 228 @param inServletResponse the servlet response to which the JSON should be written 229 @param inJSON the JSON to be written to the servlet response 230 */ 231 public static void emitJSON(HttpServletResponse inServletResponse, JsCollection inJSON) 232 throws IOException 233 { 234 inServletResponse.setContentType(MimeType.TEXT_PLAIN.toString()); 235 inServletResponse.setCharacterEncoding("UTF-8"); // If we don't specify the encoding as UTF-8, Unicode chars will get sent as '?' 236 PrintWriter writer = inServletResponse.getWriter(); 237 inJSON.toJavascript(writer); 238 239 writer.close(); 240 } 241 242 //--------------------------------------------------------------------------- 243 /** 244 Writes the specified XML file to the servlet response after setting the mime type and character encoding. 245 @param inServletResponse the servlet response to which the XML file should be written 246 @param inXMLDoc the XML file to be written to the servlet response 247 */ 248 public static void emitXMLDoc(HttpServletResponse inServletResponse, XMLDoc inXMLDoc) 249 throws Exception 250 { 251 setupResponseForFileDownload(inServletResponse, MimeType.TEXT_XML, new File(inXMLDoc.name())); 252 253 inServletResponse.setCharacterEncoding("UTF-8"); // If we don't specify the encoding as UTF-8, Unicode chars will get sent as '?' 254 PrintWriter writer = inServletResponse.getWriter(); 255 inXMLDoc.toIndentedXML(writer, 0, 2); 256 257 writer.close(); 258 } 259 260}