001package com.hfg.webapp;
002
003
004import java.io.BufferedReader;
005import java.io.ByteArrayInputStream;
006import java.io.ByteArrayOutputStream;
007import java.io.File;
008import java.io.IOException;
009import java.io.InputStream;
010import java.io.OutputStream;
011import java.io.UnsupportedEncodingException;
012import java.net.SocketTimeoutException;
013import java.net.URLDecoder;
014import java.security.Principal;
015import java.util.Collection;
016import java.util.Collections;
017import java.util.Enumeration;
018import java.util.List;
019import java.util.Locale;
020import java.util.Map;
021import java.util.zip.GZIPInputStream;
022import java.util.zip.GZIPOutputStream;
023import javax.servlet.AsyncContext;
024import javax.servlet.DispatcherType;
025import javax.servlet.RequestDispatcher;
026import javax.servlet.ServletContext;
027import javax.servlet.ServletException;
028import javax.servlet.ServletInputStream;
029import javax.servlet.ServletRequest;
030import javax.servlet.ServletResponse;
031import javax.servlet.http.Cookie;
032import javax.servlet.http.HttpServletRequest;
033import javax.servlet.http.HttpServletResponse;
034import javax.servlet.http.HttpSession;
035import javax.servlet.http.Part;
036
037import com.hfg.util.StringUtil;
038import com.hfg.util.collection.OrderedMap;
039import com.hfg.util.io.RuntimeIOException;
040import com.hfg.util.mime.MimeEntity;
041import com.hfg.util.mime.MimeType;
042import com.hfg.util.mime.MultipartMimeParser;
043
044//------------------------------------------------------------------------------
045/**
046 Wrapper for the standard HttpServletRequest that adds support for multi-part mime uploads.
047 <div>
048 @author J. Alex Taylor, hairyfatguy.com
049 </div>
050 */
051//------------------------------------------------------------------------------
052// com.hfg XML/HTML Coding Library
053//
054// This library is free software; you can redistribute it and/or
055// modify it under the terms of the GNU Lesser General Public
056// License as published by the Free Software Foundation; either
057// version 2.1 of the License, or (at your option) any later version.
058//
059// This library is distributed in the hope that it will be useful,
060// but WITHOUT ANY WARRANTY; without even the implied warranty of
061// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
062// Lesser General Public License for more details.
063//
064// You should have received a copy of the GNU Lesser General Public
065// License along with this library; if not, write to the Free Software
066// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
067//
068// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
069// jataylor@hairyfatguy.com
070//------------------------------------------------------------------------------
071
072public class HfgHttpServletRequest implements HttpServletRequest
073{
074   private HttpServletRequest mRequest;
075   private File mTmpDir;
076   private File mTmpDirRoot = sDefaultTmpDirRoot;
077
078   private Map<String, String[]> mParameterMap;
079   private CachedInputStream mInputStream;
080   private byte[] mCompressedInput;
081
082   private static File sDefaultTmpDirRoot = new File("/tmp");
083
084   //###########################################################################
085   // CONSTRUCTORS
086   //###########################################################################
087
088   //---------------------------------------------------------------------------
089   public HfgHttpServletRequest(HttpServletRequest inRequest)
090         throws IOException
091   {
092      mRequest = inRequest;
093
094      init();
095   }
096
097   //---------------------------------------------------------------------------
098   private void init()
099      throws IOException
100   {
101      mParameterMap = new OrderedMap<>(50);
102
103       // GET params?
104      if (StringUtil.isSet(mRequest.getQueryString()))
105      {
106         extractParamsFromQueryString(mRequest.getQueryString());
107      }
108
109      // POST params?
110      if (mRequest.getMethod().equalsIgnoreCase("POST"))
111      {
112         if (null == mRequest.getContentType()
113             || mRequest.getContentType().startsWith(MimeType.APPLICATION_WWW_FORM_URLENCODED.toString()))
114         {
115            for (String key : mRequest.getParameterMap().keySet())
116            {
117               setParameter(key, mRequest.getParameterValues(key));
118            }
119         }
120         else if (mRequest.getContentType().startsWith(MimeType.MULTIPART_FORM_DATA.toString()))
121         {
122            try
123            {
124               // Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryqLdKEwR9tfg86rj1
125               if (MultipartMimeParser.isMultipartMimeRequest(mRequest))
126               {
127                  MultipartMimeParser multipartMimeParser = new MultipartMimeParser().setFileCacheDir(getTmpDir());
128                  List<MimeEntity> mimeEntities = multipartMimeParser.parse(mRequest);
129                  for (MimeEntity mimeEntity : mimeEntities)
130                  {
131                     if (mimeEntity.getCachedContentFile() != null)
132                     {
133                        addParameter(mimeEntity.getContentDisposition().getName(), mimeEntity.getCachedContentFile().getPath());
134                     }
135                     else
136                     {
137                        addParameter(mimeEntity.getContentDisposition().getName(), mimeEntity.getContent());
138                     }
139                  }
140               }
141            }
142            catch (SocketTimeoutException e)
143            {
144               throw e; // This isn't our fault
145            }
146            catch (IOException e)
147            {
148               throw new RuntimeIOException("Problem extracting multi-part mime data!", e);
149            }
150         }
151      }
152   }
153
154   //###########################################################################
155   // PUBLIC METHODS
156   //###########################################################################
157
158   //---------------------------------------------------------------------------
159   public static void setDefaultTmpDirRoot(File inDir)
160   {
161      sDefaultTmpDirRoot = inDir;
162   }
163
164   //---------------------------------------------------------------------------
165   public static File getDefaultTmpDirRoot()
166   {
167      return sDefaultTmpDirRoot;
168   }
169
170   //---------------------------------------------------------------------------
171   public void setTmpDirRoot(File inDir)
172   {
173      mTmpDirRoot = inDir;
174   }
175
176   //---------------------------------------------------------------------------
177   public File getTmpDirRoot()
178   {
179      return mTmpDirRoot;
180   }
181
182   //---------------------------------------------------------------------------
183   public String[] removeParameter(String inName)
184   {
185      String[] result = null;
186      if (mParameterMap != null)
187      {
188         result = mParameterMap.remove(inName);
189      }
190
191      return result;
192   }
193
194   //---------------------------------------------------------------------------
195   public File getFileParameter(String inName)
196   {
197      File file = null;
198
199      String filename = getParameter(inName);
200      if (StringUtil.isSet(filename))
201      {
202         file = new File(filename);
203      }
204
205      return file;
206   }
207
208   //---------------------------------------------------------------------------
209   public HfgHttpServletRequest setParameter(String inName, String[] inValues)
210   {
211      if (null == mParameterMap)
212      {
213         mParameterMap = new OrderedMap<>();
214      }
215
216      mParameterMap.put(inName, inValues);
217
218      return this;
219   }
220
221   //---------------------------------------------------------------------------
222   public HfgHttpServletRequest addParameter(String inName, String inValue)
223   {
224      String[] existingValues = getParameterValues(inName);
225      String[] newValues = new String[existingValues != null ? existingValues.length + 1 : 1];
226      if (existingValues != null)
227      {
228         System.arraycopy(existingValues, 0, newValues, 0, existingValues.length);
229      }
230
231      newValues[newValues.length - 1] = inValue;
232
233      setParameter(inName, newValues);
234
235      return this;
236   }
237
238   //---------------------------------------------------------------------------
239   public void resetCachedInputStream()
240   {
241      mInputStream = null;
242   }
243
244   //---------------------------------------------------------------------------
245   public InputStream getCachedInputStream()
246         throws IOException
247   {
248      if (null == mInputStream)
249      {
250         if (null == mCompressedInput)
251         {
252            // Read the whole input stream
253            InputStream inStream = mRequest.getInputStream();
254
255            ByteArrayOutputStream compressedByteStream = new ByteArrayOutputStream();
256            OutputStream outStream = new GZIPOutputStream(compressedByteStream);
257
258            int read = 0;
259            byte[] buffer = new byte[1024];
260            while ((read = inStream.read(buffer)) != -1)
261            {
262               outStream.write(buffer, 0, read);
263            }
264            outStream.close();
265            mCompressedInput = compressedByteStream.toByteArray();
266         }
267
268         mInputStream = new CachedInputStream(new GZIPInputStream(new ByteArrayInputStream(mCompressedInput)));
269      }
270
271      return mInputStream;
272   }
273
274   //---------------------------------------------------------------------------
275   @Override
276   public String getAuthType()
277   {
278      return mRequest.getAuthType();
279   }
280
281   @Override
282   public Cookie[] getCookies()
283   {
284      return mRequest.getCookies();
285   }
286
287   @Override
288   public long getDateHeader(String s)
289   {
290      return mRequest.getDateHeader(s);
291   }
292
293   @Override
294   public String getHeader(String s)
295   {
296      return mRequest.getHeader(s);
297   }
298
299   @Override
300   public Enumeration<String> getHeaders(String s)
301   {
302      return mRequest.getHeaders(s);
303   }
304
305   @Override
306   public Enumeration<String> getHeaderNames()
307   {
308      return mRequest.getHeaderNames();
309   }
310
311   @Override
312   public int getIntHeader(String s)
313   {
314      return mRequest.getIntHeader(s);
315   }
316
317   @Override
318   public String getMethod()
319   {
320      return mRequest.getMethod();
321   }
322
323   @Override
324   public String getPathInfo()
325   {
326      return mRequest.getPathInfo();
327   }
328
329   @Override
330   public String getPathTranslated()
331   {
332      return mRequest.getPathTranslated();
333   }
334
335   @Override
336   public String getContextPath()
337   {
338      return mRequest.getContextPath();
339   }
340
341   @Override
342   public String getQueryString()
343   {
344      return mRequest.getQueryString();
345   }
346
347   @Override
348   public String getRemoteUser()
349   {
350      return mRequest.getRemoteUser();
351   }
352
353   @Override
354   public boolean isUserInRole(String s)
355   {
356      return mRequest.isUserInRole(s);
357   }
358
359   @Override
360   public Principal getUserPrincipal()
361   {
362      return mRequest.getUserPrincipal();
363   }
364
365   @Override
366   public String getRequestedSessionId()
367   {
368      return mRequest.getRequestedSessionId();
369   }
370
371   @Override
372   public String getRequestURI()
373   {
374      return mRequest.getRequestURI();
375   }
376
377   @Override
378   public StringBuffer getRequestURL()
379   {
380      return mRequest.getRequestURL();
381   }
382
383   @Override
384   public String getServletPath()
385   {
386      return mRequest.getServletPath();
387   }
388
389   @Override
390   public HttpSession getSession(boolean b)
391   {
392      return mRequest.getSession(b);
393   }
394
395   @Override
396   public HttpSession getSession()
397   {
398      return mRequest.getSession();
399   }
400
401   @Override
402   public boolean isRequestedSessionIdValid()
403   {
404      return mRequest.isRequestedSessionIdValid();
405   }
406
407   @Override
408   public boolean isRequestedSessionIdFromCookie()
409   {
410      return mRequest.isRequestedSessionIdFromCookie();
411   }
412
413   @Override
414   public boolean isRequestedSessionIdFromURL()
415   {
416      return mRequest.isRequestedSessionIdFromURL();
417   }
418
419   @Override
420   @SuppressWarnings("deprecation")
421   public boolean isRequestedSessionIdFromUrl()
422   {
423      return mRequest.isRequestedSessionIdFromUrl();
424   }
425
426   @Override
427   public boolean authenticate(HttpServletResponse httpServletResponse)
428         throws IOException, ServletException
429   {
430      return mRequest.authenticate(httpServletResponse);
431   }
432
433   @Override
434   public void login(String s, String s1)
435         throws ServletException
436   {
437      mRequest.login(s, s1);
438   }
439
440   @Override
441   public void logout()
442         throws ServletException
443   {
444      mRequest.logout();
445   }
446
447   @Override
448   public Collection<Part> getParts()
449         throws IOException, ServletException
450   {
451      return mRequest.getParts();
452   }
453
454   @Override
455   public Part getPart(String s)
456         throws IOException, ServletException
457   {
458      return mRequest.getPart(s);
459   }
460
461   @Override
462   public Object getAttribute(String s)
463   {
464      return mRequest.getAttribute(s);
465   }
466
467   @Override
468   public Enumeration<String> getAttributeNames()
469   {
470      return mRequest.getAttributeNames();
471   }
472
473   @Override
474   public String getCharacterEncoding()
475   {
476      return mRequest.getCharacterEncoding();
477   }
478
479   @Override
480   public void setCharacterEncoding(String s)
481         throws UnsupportedEncodingException
482   {
483      mRequest.setCharacterEncoding(s);
484   }
485
486   @Override
487   public int getContentLength()
488   {
489      return mRequest.getContentLength();
490   }
491
492   @Override
493   public String getContentType()
494   {
495      return mRequest.getContentType();
496   }
497
498   @Override
499   public ServletInputStream getInputStream()
500         throws IOException
501   {
502      return (mInputStream != null ? mInputStream : mRequest.getInputStream());
503   }
504
505   @Override
506   public String getParameter(String s)
507   {
508      String[] values = mParameterMap.get(s);
509      return (values != null ? values[0] : null);
510   }
511
512   @Override
513   public Enumeration<String> getParameterNames()
514   {
515      return mRequest.getParameterNames();
516   }
517
518   @Override
519   public String[] getParameterValues(String s)
520   {
521      return mParameterMap.get(s);
522   }
523
524   @Override
525   public Map<String, String[]> getParameterMap()
526   {
527      return mParameterMap != null ? Collections.unmodifiableMap(mParameterMap) : null;
528   }
529
530   @Override
531   public String getProtocol()
532   {
533      return mRequest.getProtocol();
534   }
535
536   @Override
537   public String getScheme()
538   {
539      return mRequest.getScheme();
540   }
541
542   @Override
543   public String getServerName()
544   {
545      return mRequest.getServerName();
546   }
547
548   @Override
549   public int getServerPort()
550   {
551      return mRequest.getServerPort();
552   }
553
554   @Override
555   public BufferedReader getReader()
556         throws IOException
557   {
558      return mRequest.getReader();
559   }
560
561   @Override
562   public String getRemoteAddr()
563   {
564      return mRequest.getRemoteAddr();
565   }
566
567   @Override
568   public String getRemoteHost()
569   {
570      return mRequest.getRemoteHost();
571   }
572
573   @Override
574   public void setAttribute(String s, Object o)
575   {
576      mRequest.setAttribute(s, o);
577   }
578
579   @Override
580   public void removeAttribute(String s)
581   {
582      mRequest.removeAttribute(s);
583   }
584
585   @Override
586   public Locale getLocale()
587   {
588      return mRequest.getLocale();
589   }
590
591   @Override
592   public Enumeration<Locale> getLocales()
593   {
594      return mRequest.getLocales();
595   }
596
597   @Override
598   public boolean isSecure()
599   {
600      return mRequest.isSecure();
601   }
602
603   @Override
604   public RequestDispatcher getRequestDispatcher(String s)
605   {
606      return mRequest.getRequestDispatcher(s);
607   }
608
609   @Override
610   @SuppressWarnings("deprecation")
611   public String getRealPath(String s)
612   {
613      return mRequest.getRealPath(s);
614   }
615
616   @Override
617   public int getRemotePort()
618   {
619      return mRequest.getRemotePort();
620   }
621
622   @Override
623   public String getLocalName()
624   {
625      return mRequest.getLocalName();
626   }
627
628   @Override
629   public String getLocalAddr()
630   {
631      return mRequest.getLocalAddr();
632   }
633
634   @Override
635   public int getLocalPort()
636   {
637      return mRequest.getLocalPort();
638   }
639
640   @Override
641   public ServletContext getServletContext()
642   {
643      return mRequest.getServletContext();
644   }
645
646   @Override
647   public AsyncContext startAsync()
648         throws IllegalStateException
649   {
650      return mRequest.startAsync();
651   }
652
653   @Override
654   public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse)
655         throws IllegalStateException
656   {
657      return mRequest.startAsync(servletRequest, servletResponse);
658   }
659
660   @Override
661   public boolean isAsyncStarted()
662   {
663      return mRequest.isAsyncStarted();
664   }
665
666   @Override
667   public boolean isAsyncSupported()
668   {
669      return mRequest.isAsyncSupported();
670   }
671
672   @Override
673   public AsyncContext getAsyncContext()
674   {
675      return mRequest.getAsyncContext();
676   }
677
678   @Override
679   public DispatcherType getDispatcherType()
680   {
681      return mRequest.getDispatcherType();
682   }
683
684   //###########################################################################
685   // PRIVATE METHODS
686   //###########################################################################
687
688   //---------------------------------------------------------------------------
689   private File getTmpDir()
690   {
691      if (null == mTmpDir)
692      {
693         mTmpDir = new File(getTmpDirRoot(), "tmp_request_" + System.currentTimeMillis());
694      }
695
696      return mTmpDir;
697   }
698
699   //---------------------------------------------------------------------------
700   private void extractParamsFromQueryString(String inQueryString)
701   {
702      String fields[] = inQueryString.split("&");
703
704      try
705      {
706         for (String field : fields)
707         {
708            String pieces[] = field.split("=");
709            addParameter(URLDecoder.decode(pieces[0], "UTF-8"), (pieces.length > 1 ? URLDecoder.decode(pieces[1], "UTF-8") : null));
710         }
711      }
712      catch (Exception e)
713      {
714         throw new RuntimeException("Problem during extraction of GET parameters!", e);
715      }
716   }
717
718   //###########################################################################
719   // INNER CLASS
720   //###########################################################################
721
722   private class CachedInputStream extends ServletInputStream
723   {
724      private InputStream mInnerStream;
725
726      //------------------------------------------------------------------------
727      public CachedInputStream(InputStream inStream)
728      {
729         mInnerStream = inStream;
730      }
731
732      //------------------------------------------------------------------------
733      @Override
734      public int read()
735            throws IOException
736      {
737         return mInnerStream.read();
738      }
739   }
740
741}