Better separated out email decoding functionality
[zxing.git] / zxingorg / src / com / google / zxing / web / DecodeServlet.java
1 /*
2  * Copyright 2008 ZXing authors
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.zxing.web;
18
19 import com.google.zxing.DecodeHintType;
20 import com.google.zxing.MultiFormatReader;
21 import com.google.zxing.Reader;
22 import com.google.zxing.ReaderException;
23 import com.google.zxing.Result;
24 import com.google.zxing.client.j2se.BufferedImageMonochromeBitmapSource;
25 import com.google.zxing.client.result.ParsedResult;
26 import com.google.zxing.client.result.ResultParser;
27 import org.apache.commons.fileupload.FileItem;
28 import org.apache.commons.fileupload.FileUploadException;
29 import org.apache.commons.fileupload.disk.DiskFileItemFactory;
30 import org.apache.commons.fileupload.servlet.ServletFileUpload;
31 import org.apache.commons.lang.StringEscapeUtils;
32 import org.apache.http.Header;
33 import org.apache.http.HttpException;
34 import org.apache.http.HttpMessage;
35 import org.apache.http.HttpResponse;
36 import org.apache.http.HttpVersion;
37 import org.apache.http.client.HttpClient;
38 import org.apache.http.client.methods.HttpGet;
39 import org.apache.http.conn.scheme.PlainSocketFactory;
40 import org.apache.http.conn.scheme.Scheme;
41 import org.apache.http.conn.scheme.SchemeRegistry;
42 import org.apache.http.conn.ssl.SSLSocketFactory;
43 import org.apache.http.impl.client.DefaultHttpClient;
44 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
45 import org.apache.http.params.BasicHttpParams;
46 import org.apache.http.params.HttpParams;
47 import org.apache.http.params.HttpProtocolParams;
48
49 import javax.imageio.ImageIO;
50 import javax.servlet.ServletConfig;
51 import javax.servlet.ServletException;
52 import javax.servlet.http.HttpServlet;
53 import javax.servlet.http.HttpServletRequest;
54 import javax.servlet.http.HttpServletResponse;
55 import java.awt.image.BufferedImage;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.io.OutputStreamWriter;
59 import java.io.Writer;
60 import java.net.SocketException;
61 import java.net.URI;
62 import java.net.URISyntaxException;
63 import java.net.UnknownHostException;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.logging.Logger;
67
68 /**
69  * {@link HttpServlet} which decodes images containing barcodes. Given a URL, it will
70  * retrieve the image and decode it. It can also process image files uploaded via POST.
71  * 
72  * @author Sean Owen (srowen@google.com)
73  */
74 public final class DecodeServlet extends HttpServlet {
75
76   private static final long MAX_IMAGE_SIZE = 500000L;
77
78   private static final Logger log = Logger.getLogger(DecodeServlet.class.getName());
79
80   static final Hashtable<DecodeHintType, Object> HINTS;
81
82   static {
83     HINTS = new Hashtable<DecodeHintType, Object>(3);
84     HINTS.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
85   }
86
87   private HttpClient client;
88   private DiskFileItemFactory diskFileItemFactory;
89
90   @Override
91   public void init(ServletConfig servletConfig) {
92
93     Logger logger = Logger.getLogger("com.google.zxing");
94     logger.addHandler(new ServletContextLogHandler(servletConfig.getServletContext()));
95
96     HttpParams params = new BasicHttpParams();
97     HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
98
99     SchemeRegistry registry = new SchemeRegistry();
100     registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
101     registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
102
103     client = new DefaultHttpClient(new ThreadSafeClientConnManager(params, registry), params);
104
105     diskFileItemFactory = new DiskFileItemFactory();
106
107     log.info("DecodeServlet configured");
108   }
109
110   @Override
111   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
112
113     String imageURIString = request.getParameter("u");
114     if (imageURIString == null || imageURIString.length() == 0) {
115       response.sendRedirect("badurl.jspx");
116       return;
117     }
118
119     if (!(imageURIString.startsWith("http://") || imageURIString.startsWith("https://"))) {
120       imageURIString = "http://" + imageURIString;
121     }
122
123     URI imageURI;
124     try {
125       imageURI = new URI(imageURIString);
126     } catch (URISyntaxException urise) {
127       response.sendRedirect("badurl.jspx");
128       return;
129     }
130
131     HttpGet getRequest = new HttpGet(imageURI);
132
133     try {
134       HttpResponse getResponse = client.execute(getRequest);
135       if (getResponse.getStatusLine().getStatusCode() != HttpServletResponse.SC_OK) {
136         response.sendRedirect("badurl.jspx");
137         return;
138       }
139       if (!isSizeOK(getResponse)) {
140         response.sendRedirect("badimage.jspx");
141         return;
142       }
143       log.info("Decoding " + imageURI);
144       InputStream is = getResponse.getEntity().getContent();
145       try {
146         processStream(is, request, response);
147       } finally {
148         is.close();
149       }
150     } catch (IllegalArgumentException iae) {
151       // Thrown if hostname is bad or null
152       getRequest.abort();
153       response.sendRedirect("badurl.jspx");
154     } catch (SocketException se) {
155       // Thrown if hostname is bad or null
156       getRequest.abort();
157       response.sendRedirect("badurl.jspx");
158     } catch (HttpException he) {
159       getRequest.abort();
160       response.sendRedirect("badurl.jspx");
161     } catch (UnknownHostException uhe) {
162       getRequest.abort();
163       response.sendRedirect("badurl.jspx");
164     }
165
166   }
167
168   @Override
169   protected void doPost(HttpServletRequest request, HttpServletResponse response)
170           throws ServletException, IOException {
171
172     if (!ServletFileUpload.isMultipartContent(request)) {
173       response.sendRedirect("badimage.jspx");
174       return;
175     }
176
177     ServletFileUpload upload = new ServletFileUpload(diskFileItemFactory);
178     upload.setFileSizeMax(MAX_IMAGE_SIZE);
179
180     // Parse the request
181     try {
182       for (FileItem item : (List<FileItem>) upload.parseRequest(request)) {
183         if (!item.isFormField()) {
184           if (item.getSize() <= MAX_IMAGE_SIZE) {
185             log.info("Decoding uploaded file");
186             InputStream is = item.getInputStream();
187             try {
188               processStream(is, request, response);
189             } finally {
190               is.close();
191             }
192           } else {
193             response.sendRedirect("badimage.jspx");
194           }
195           break;
196         }
197       }
198     } catch (FileUploadException fue) {
199       response.sendRedirect("badimage.jspx");
200     }
201
202   }
203
204   private static void processStream(InputStream is, HttpServletRequest request, HttpServletResponse response)
205       throws ServletException, IOException {
206     BufferedImage image = ImageIO.read(is);
207     if (image == null) {
208       response.sendRedirect("badimage.jspx");
209       return;
210     }
211
212     Reader reader = new MultiFormatReader();
213     Result result;
214     try {
215       result = reader.decode(new BufferedImageMonochromeBitmapSource(image), HINTS);
216     } catch (ReaderException re) {
217       log.info("DECODE FAILED: " + re.toString());
218       response.sendRedirect("notfound.jspx");
219       return;
220     }
221
222     if (request.getParameter("full") == null) {
223       response.setContentType("text/plain");
224       response.setCharacterEncoding("UTF8");
225       Writer out = new OutputStreamWriter(response.getOutputStream(), "UTF8");
226       try {
227         out.write(result.getText());
228       } finally {
229         out.close();
230       }
231     } else {
232       request.setAttribute("result", result);
233       byte[] rawBytes = result.getRawBytes();
234       if (rawBytes != null) {
235         request.setAttribute("rawBytesString", arrayToString(rawBytes));
236       } else {
237         request.setAttribute("rawBytesString", "(Not applicable)");
238       }
239       String text = result.getText();
240       if (text != null) {
241         request.setAttribute("text", StringEscapeUtils.escapeXml(text));
242       } else {
243         request.setAttribute("text", "(Not applicable)");
244       }
245       ParsedResult parsedResult = ResultParser.parseResult(result);
246       request.setAttribute("parsedResult", parsedResult);
247       String displayResult = parsedResult.getDisplayResult();
248       if (displayResult != null) {
249         request.setAttribute("displayResult", StringEscapeUtils.escapeXml(displayResult));
250       } else {
251         request.setAttribute("displayResult", "(Not applicable)");
252       }
253       request.getRequestDispatcher("decoderesult.jspx").forward(request, response);
254     }
255   }
256
257   private static boolean isSizeOK(HttpMessage getResponse) {
258     Header lengthHeader = getResponse.getLastHeader("Content-Length");
259     if (lengthHeader != null) {
260       long length = Long.parseLong(lengthHeader.getValue());
261       if (length > MAX_IMAGE_SIZE) {
262         return false;
263       }
264     }
265     return true;
266   }
267
268   private static String arrayToString(byte[] bytes) {
269     int length = bytes.length;
270     StringBuilder result = new StringBuilder(length << 2);
271     int i = 0;
272     while (i < length) {
273       int max = Math.min(i + 8, length);
274       for (int j = i; j < max; j++) {
275         int value = bytes[j] & 0xFF;
276         result.append(Integer.toHexString(value / 16));
277         result.append(Integer.toHexString(value % 16));
278         result.append(' ');
279       }
280       result.append('\n');
281       i += 8;
282     }
283     for (int j = i - 8; j < length; j++) {
284       result.append(Integer.toHexString(bytes[j] & 0xFF));
285       result.append(' ');
286     }
287     return result.toString();
288   }
289
290   @Override
291   public void destroy() {
292     log.config("DecodeServlet shutting down...");
293   }
294
295 }