Update Codabar style and disable it as its causing too many false positives
[zxing.git] / zxingorg / src / com / google / zxing / web / DecodeServlet.java
index 7041175..8419c7a 100644 (file)
 
 package com.google.zxing.web;
 
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.ChecksumException;
 import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.LuminanceSource;
 import com.google.zxing.MultiFormatReader;
+import com.google.zxing.NotFoundException;
 import com.google.zxing.Reader;
 import com.google.zxing.ReaderException;
 import com.google.zxing.Result;
-import com.google.zxing.BarcodeFormat;
-import com.google.zxing.client.j2se.BufferedImageMonochromeBitmapSource;
-import com.google.zxing.client.result.ParsedResult;
-import com.google.zxing.client.result.ResultParser;
+import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
+import com.google.zxing.common.GlobalHistogramBinarizer;
+import com.google.zxing.common.HybridBinarizer;
+
+import com.google.zxing.multi.GenericMultipleBarcodeReader;
+import com.google.zxing.multi.MultipleBarcodeReader;
 import org.apache.commons.fileupload.FileItem;
 import org.apache.commons.fileupload.FileUploadException;
 import org.apache.commons.fileupload.disk.DiskFileItemFactory;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
-import org.apache.commons.lang.StringEscapeUtils;
 import org.apache.http.Header;
-import org.apache.http.HttpException;
 import org.apache.http.HttpMessage;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpVersion;
+import org.apache.http.HttpEntity;
 import org.apache.http.client.HttpClient;
-import org.apache.http.client.params.HttpClientParams;
 import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.conn.scheme.PlainSocketFactory;
 import org.apache.http.conn.scheme.Scheme;
 import org.apache.http.conn.scheme.SchemeRegistry;
 import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.conn.ClientConnectionManager;
 import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.impl.conn.SingleClientConnManager;
 import org.apache.http.params.BasicHttpParams;
 import org.apache.http.params.HttpParams;
 import org.apache.http.params.HttpProtocolParams;
 
-import javax.imageio.ImageIO;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import java.awt.color.CMMException;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
-import java.net.SocketException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Vector;
 import java.util.logging.Logger;
 
+import javax.imageio.ImageIO;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 /**
  * {@link HttpServlet} which decodes images containing barcodes. Given a URL, it will
  * retrieve the image and decode it. It can also process image files uploaded via POST.
- * 
+ *
  * @author Sean Owen
  */
 public final class DecodeServlet extends HttpServlet {
 
-  private static final long MAX_IMAGE_SIZE = 500000L;
+  // No real reason to let people upload more than a 2MB image
+  private static final long MAX_IMAGE_SIZE = 2000000L;
+  // No real reason to deal with more than maybe 2 megapixels
+  private static final int MAX_PIXELS = 1 << 21;
 
   private static final Logger log = Logger.getLogger(DecodeServlet.class.getName());
 
   static final Hashtable<DecodeHintType, Object> HINTS;
+  static final Hashtable<DecodeHintType, Object> HINTS_PURE;
 
   static {
     HINTS = new Hashtable<DecodeHintType, Object>(5);
     HINTS.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
-    Vector possibleFormats = new Vector();
+    Collection<BarcodeFormat> possibleFormats = new Vector<BarcodeFormat>(17);
     possibleFormats.add(BarcodeFormat.UPC_A);
     possibleFormats.add(BarcodeFormat.UPC_E);
     possibleFormats.add(BarcodeFormat.EAN_8);
     possibleFormats.add(BarcodeFormat.EAN_13);
     possibleFormats.add(BarcodeFormat.CODE_39);
+    possibleFormats.add(BarcodeFormat.CODE_93);    
     possibleFormats.add(BarcodeFormat.CODE_128);
+    //possibleFormats.add(BarcodeFormat.CODABAR);
     possibleFormats.add(BarcodeFormat.ITF);
+    possibleFormats.add(BarcodeFormat.RSS14);    
     possibleFormats.add(BarcodeFormat.QR_CODE);
     possibleFormats.add(BarcodeFormat.DATAMATRIX);
+    possibleFormats.add(BarcodeFormat.PDF417);
     HINTS.put(DecodeHintType.POSSIBLE_FORMATS, possibleFormats);
+    HINTS_PURE = new Hashtable<DecodeHintType, Object>(HINTS);
+    HINTS_PURE.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
   }
 
-  private HttpClient client;
+  private HttpParams params;
+  private SchemeRegistry registry;
   private DiskFileItemFactory diskFileItemFactory;
 
   @Override
@@ -107,29 +130,30 @@ public final class DecodeServlet extends HttpServlet {
     Logger logger = Logger.getLogger("com.google.zxing");
     logger.addHandler(new ServletContextLogHandler(servletConfig.getServletContext()));
 
-    HttpParams params = new BasicHttpParams();
+    params = new BasicHttpParams();
     HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
 
-    SchemeRegistry registry = new SchemeRegistry();
+    registry = new SchemeRegistry();
     registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
     registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
 
-    client = new DefaultHttpClient(new ThreadSafeClientConnManager(params, registry), params);
-
     diskFileItemFactory = new DiskFileItemFactory();
 
     log.info("DecodeServlet configured");
   }
 
   @Override
-  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-
+  protected void doGet(HttpServletRequest request, HttpServletResponse response)
+      throws ServletException, IOException {
     String imageURIString = request.getParameter("u");
     if (imageURIString == null || imageURIString.length() == 0) {
+      log.fine("URI was empty");
       response.sendRedirect("badurl.jspx");
       return;
     }
 
+    imageURIString = imageURIString.trim();
+
     if (!(imageURIString.startsWith("http://") || imageURIString.startsWith("https://"))) {
       imageURIString = "http://" + imageURIString;
     }
@@ -138,41 +162,63 @@ public final class DecodeServlet extends HttpServlet {
     try {
       imageURI = new URI(imageURIString);
     } catch (URISyntaxException urise) {
+      log.fine("URI was not valid: " + imageURIString);
       response.sendRedirect("badurl.jspx");
       return;
     }
 
-    HttpGet getRequest = new HttpGet(imageURI);
+    ClientConnectionManager connectionManager = new SingleClientConnManager(params, registry);
+    HttpClient client = new DefaultHttpClient(connectionManager, params);
+
+    HttpUriRequest getRequest = new HttpGet(imageURI);
     getRequest.addHeader("Connection", "close"); // Avoids CLOSE_WAIT socket issue?
 
     try {
-      HttpResponse getResponse = client.execute(getRequest);
+
+      HttpResponse getResponse;
+      try {
+        getResponse = client.execute(getRequest);
+      } catch (IllegalArgumentException iae) {
+        // Thrown if hostname is bad or null
+        log.fine(iae.toString());
+        getRequest.abort();
+        response.sendRedirect("badurl.jspx");
+        return;
+      } catch (IOException ioe) {
+        // Encompasses lots of stuff, including
+        //  java.net.SocketException, java.net.UnknownHostException,
+        //  javax.net.ssl.SSLPeerUnverifiedException,
+        //  org.apache.http.NoHttpResponseException,
+        //  org.apache.http.client.ClientProtocolException,
+        log.fine(ioe.toString());
+        getRequest.abort();
+        response.sendRedirect("badurl.jspx");
+        return;
+      }
+
       if (getResponse.getStatusLine().getStatusCode() != HttpServletResponse.SC_OK) {
+        log.fine("Unsuccessful return code: " + getResponse.getStatusLine().getStatusCode());
         response.sendRedirect("badurl.jspx");
         return;
       }
       if (!isSizeOK(getResponse)) {
+        log.fine("Too large");
         response.sendRedirect("badimage.jspx");
         return;
       }
+
       log.info("Decoding " + imageURI);
-      InputStream is = getResponse.getEntity().getContent();
+      HttpEntity entity = getResponse.getEntity();
+      InputStream is = entity.getContent();
       try {
         processStream(is, request, response);
       } finally {
+        entity.consumeContent();
         is.close();
       }
-    } catch (IllegalArgumentException iae) {
-      // Thrown if hostname is bad or null
-      getRequest.abort();
-      response.sendRedirect("badurl.jspx");
-    } catch (SocketException se) {
-      // Thrown if hostname is bad or null
-      getRequest.abort();
-      response.sendRedirect("badurl.jspx");
-    } catch (UnknownHostException uhe) {
-      getRequest.abort();
-      response.sendRedirect("badurl.jspx");
+
+    } finally {
+      connectionManager.shutdown();
     }
 
   }
@@ -182,6 +228,7 @@ public final class DecodeServlet extends HttpServlet {
           throws ServletException, IOException {
 
     if (!ServletFileUpload.isMultipartContent(request)) {
+      log.fine("File upload was not multipart");
       response.sendRedirect("badimage.jspx");
       return;
     }
@@ -202,32 +249,108 @@ public final class DecodeServlet extends HttpServlet {
               is.close();
             }
           } else {
+            log.fine("Too large");
             response.sendRedirect("badimage.jspx");
           }
           break;
         }
       }
     } catch (FileUploadException fue) {
+      log.fine(fue.toString());
       response.sendRedirect("badimage.jspx");
     }
 
   }
 
-  private static void processStream(InputStream is, HttpServletRequest request, HttpServletResponse response)
-      throws ServletException, IOException {
-    BufferedImage image = ImageIO.read(is);
+  private static void processStream(InputStream is, ServletRequest request,
+      HttpServletResponse response) throws ServletException, IOException {
+
+    BufferedImage image;
+    try {
+      image = ImageIO.read(is);
+    } catch (IOException ioe) {
+      log.fine(ioe.toString());
+      // Includes javax.imageio.IIOException
+      response.sendRedirect("badimage.jspx");
+      return;
+    } catch (CMMException cmme) {
+      log.fine(cmme.toString());
+      // Have seen this in logs
+      response.sendRedirect("badimage.jspx");
+      return;
+    } catch (IllegalArgumentException iae) {
+      log.fine(iae.toString());
+      // Have seen this in logs for some JPEGs
+      response.sendRedirect("badimage.jspx");
+      return;
+    }
     if (image == null) {
+      response.sendRedirect("badimage.jspx");
+      return;      
+    }
+    if (image.getHeight() <= 1 || image.getWidth() <= 1 ||
+        image.getHeight() * image.getWidth() > MAX_PIXELS) {
+      log.fine("Dimensions too large: " + image.getWidth() + 'x' + image.getHeight());        
       response.sendRedirect("badimage.jspx");
       return;
     }
 
     Reader reader = new MultiFormatReader();
-    Result result;
+    LuminanceSource source = new BufferedImageLuminanceSource(image);
+    BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
+    Collection<Result> results = new ArrayList<Result>(1);
+    ReaderException savedException = null;
+
     try {
-      result = reader.decode(new BufferedImageMonochromeBitmapSource(image), HINTS);
+      // Look for multiple barcodes
+      MultipleBarcodeReader multiReader = new GenericMultipleBarcodeReader(reader);
+      Result[] theResults = multiReader.decodeMultiple(bitmap, HINTS);
+      if (theResults != null) {
+        results.addAll(Arrays.asList(theResults));
+      }
     } catch (ReaderException re) {
-      log.info("DECODE FAILED: " + re.toString());
-      response.sendRedirect("notfound.jspx");
+      savedException = re;
+    }
+
+    if (results.isEmpty()) {
+      try {
+        // Look for pure barcode
+        Result theResult = reader.decode(bitmap, HINTS_PURE);
+        if (theResult != null) {
+          results.add(theResult);
+        }
+      } catch (ReaderException re) {
+        savedException = re;
+      }
+    }
+
+    if (results.isEmpty()) {
+      try {
+        // Look for normal barcode in photo
+        Result theResult = reader.decode(bitmap, HINTS);
+        if (theResult != null) {
+          results.add(theResult);
+        }
+      } catch (ReaderException re) {
+        savedException = re;
+      }
+    }
+
+    if (results.isEmpty()) {
+      try {
+        // Try again with other binarizer
+        BinaryBitmap hybridBitmap = new BinaryBitmap(new HybridBinarizer(source));
+        Result theResult = reader.decode(hybridBitmap, HINTS);
+        if (theResult != null) {
+          results.add(theResult);
+        }
+      } catch (ReaderException re) {
+        savedException = re;
+      }
+    }
+
+    if (results.isEmpty()) {
+      handleException(savedException, response);
       return;
     }
 
@@ -236,36 +359,35 @@ public final class DecodeServlet extends HttpServlet {
       response.setCharacterEncoding("UTF8");
       Writer out = new OutputStreamWriter(response.getOutputStream(), "UTF8");
       try {
-        out.write(result.getText());
+        for (Result result : results) {
+          out.write(result.getText());
+          out.write('\n');
+        }
       } finally {
         out.close();
       }
     } else {
-      request.setAttribute("result", result);
-      byte[] rawBytes = result.getRawBytes();
-      if (rawBytes != null) {
-        request.setAttribute("rawBytesString", arrayToString(rawBytes));
-      } else {
-        request.setAttribute("rawBytesString", "(Not applicable)");
-      }
-      String text = result.getText();
-      if (text != null) {
-        request.setAttribute("text", StringEscapeUtils.escapeXml(text));
-      } else {
-        request.setAttribute("text", "(Not applicable)");
-      }
-      ParsedResult parsedResult = ResultParser.parseResult(result);
-      request.setAttribute("parsedResult", parsedResult);
-      String displayResult = parsedResult.getDisplayResult();
-      if (displayResult != null) {
-        request.setAttribute("displayResult", StringEscapeUtils.escapeXml(displayResult));
-      } else {
-        request.setAttribute("displayResult", "(Not applicable)");
-      }
+      request.setAttribute("results", results);
       request.getRequestDispatcher("decoderesult.jspx").forward(request, response);
     }
   }
 
+  private static void handleException(ReaderException re, HttpServletResponse response) throws IOException {
+    if (re instanceof NotFoundException) {
+      log.info("Not found: " + re);
+      response.sendRedirect("notfound.jspx");
+    } else if (re instanceof FormatException) {
+      log.info("Format problem: " + re);
+      response.sendRedirect("format.jspx");
+    } else if (re instanceof ChecksumException) {
+      log.info("Checksum problem: " + re);
+      response.sendRedirect("format.jspx");
+    } else {
+      log.info("Unknown problem: " + re);
+      response.sendRedirect("notfound.jspx");
+    }
+  }
+
   private static boolean isSizeOK(HttpMessage getResponse) {
     Header lengthHeader = getResponse.getLastHeader("Content-Length");
     if (lengthHeader != null) {
@@ -277,28 +399,6 @@ public final class DecodeServlet extends HttpServlet {
     return true;
   }
 
-  private static String arrayToString(byte[] bytes) {
-    int length = bytes.length;
-    StringBuilder result = new StringBuilder(length << 2);
-    int i = 0;
-    while (i < length) {
-      int max = Math.min(i + 8, length);
-      for (int j = i; j < max; j++) {
-        int value = bytes[j] & 0xFF;
-        result.append(Integer.toHexString(value / 16));
-        result.append(Integer.toHexString(value % 16));
-        result.append(' ');
-      }
-      result.append('\n');
-      i += 8;
-    }
-    for (int j = i - 8; j < length; j++) {
-      result.append(Integer.toHexString(bytes[j] & 0xFF));
-      result.append(' ');
-    }
-    return result.toString();
-  }
-
   @Override
   public void destroy() {
     log.config("DecodeServlet shutting down...");