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}