changeset 1206:ad17716ebc54

made IIIF parameter parser more standards compliant.
author robcast
date Mon, 22 Jul 2013 19:26:43 +0200
parents a5aea37fac03
children 89880f24a007
files common/src/main/java/digilib/conf/DigilibRequest.java servlet/src/main/java/digilib/conf/DigilibServletRequest.java servlet/src/main/java/digilib/servlet/ServletOps.java servlet3/src/main/java/digilib/servlet/Scaler.java
diffstat 4 files changed, 106 insertions(+), 42 deletions(-) [+]
line wrap: on
line diff
--- a/common/src/main/java/digilib/conf/DigilibRequest.java	Mon Jul 22 13:27:43 2013 +0200
+++ b/common/src/main/java/digilib/conf/DigilibRequest.java	Mon Jul 22 19:26:43 2013 +0200
@@ -66,6 +66,9 @@
 
     /** IIIF path prefix (taken from config) */
     protected String iiifPrefix = "IIIF";
+    
+    /** error message while configuring */
+    public String errorMessage = null;
 
     /** ImageJobDescription for this request */
     protected ImageJobDescription ticket;
@@ -160,7 +163,7 @@
         newParameter("img.pix_x", new Integer(0), null, 'c');
         // hires image size y
         newParameter("img.pix_y", new Integer(0), null, 'c');
-        
+
         /*
          * set local variables from config
          */
@@ -275,9 +278,9 @@
      * @see <a href="http://www-sul.stanford.edu/iiif/image-api/1.1/">IIIF Image
      *      API</a>
      */
-    public void setWithIiifPath(String path) {
+    public boolean setWithIiifPath(String path) {
         if (path == null) {
-            return;
+            return false;
         }
         // alway set HTTP status code error reporting
         options.setOption("errcode");
@@ -288,10 +291,11 @@
          * first parameter prefix
          */
         if (query.hasMoreTokens()) {
-            token = query.nextToken();
+            token = getNextDecodedToken(query);
             if (!token.equals(iiifPrefix)) {
-                logger.error("IIIF path doesn't start with prefix!");
-                // what now?
+                errorMessage = "IIIF path doesn't start with prefix!";
+                logger.error(errorMessage);
+                return false;
             }
             // skip /
             if (query.hasMoreTokens()) {
@@ -302,12 +306,18 @@
          * second parameter FN (encoded)
          */
         if (query.hasMoreTokens()) {
-            token = query.nextToken();
+            token = getNextDecodedToken(query);
             if (!token.equals("/")) {
                 try {
-                    setValueFromString("fn", URLDecoder.decode(token, "UTF-8"));
+                    if (token.contains("%")) {
+                        // still escape chars -- decode again
+                        token = URLDecoder.decode(token, "UTF-8");
+                    }
+                    setValueFromString("fn", token);
                 } catch (UnsupportedEncodingException e) {
-                    logger.error("Error decoding identifier in IIIF path!");
+                    errorMessage = "Error decoding identifier in IIIF path!";
+                    logger.error(errorMessage);
+                    return false;
                 }
                 // skip /
                 if (query.hasMoreTokens()) {
@@ -319,15 +329,15 @@
          * third parameter region
          */
         if (query.hasMoreTokens()) {
-            token = query.nextToken();
+            token = getNextDecodedToken(query);
             if (!token.equals("/")) {
                 if (token.equals("info.json")) {
                     // info request
                     options.setOption("info");
-                    return;
+                    return true;
                 } else if (token.equals("full")) {
                     // full region -- default
-                } else if (token.startsWith("pct:")){
+                } else if (token.startsWith("pct:")) {
                     // pct:x,y,w,h -- region in % of original image
                     String[] parms = token.substring(4).split(",");
                     try {
@@ -340,19 +350,23 @@
                         float h = Float.parseFloat(parms[3]);
                         setValue("wh", h / 100f);
                     } catch (Exception e) {
-                        logger.error("Error parsing range parameter in IIIF path!", e);
+                        errorMessage = "Error parsing range parameter in IIIF path!";
+                        logger.error(errorMessage, e);
+                        return false;
                     }
                 } else {
                     // x,y,w,h -- region in pixel of original image :-(
                     String[] parms = token.split(",");
                     if (parms.length != 4) {
-                        logger.error("Error parsing range parameter in IIIF path!");
+                        errorMessage = "Error parsing range parameter in IIIF path!";
+                        logger.error(errorMessage);
+                        return false;
                     } else {
                         options.setOption("pxarea");
-                        setValueFromString("wx", parms[0]);                            
-                        setValueFromString("wy", parms[1]);                            
-                        setValueFromString("ww", parms[2]);                            
-                        setValueFromString("wh", parms[3]);                            
+                        setValueFromString("wx", parms[0]);
+                        setValueFromString("wy", parms[1]);
+                        setValueFromString("ww", parms[2]);
+                        setValueFromString("wh", parms[3]);
                     }
                 }
                 // skip /
@@ -363,47 +377,53 @@
         } else {
             // region omitted -- assume info request
             options.setOption("info");
-            return;
+            return true;
         }
         /*
          * fourth parameter size
          */
         if (query.hasMoreTokens()) {
-            token = query.nextToken();
+            token = getNextDecodedToken(query);
             if (!token.equals("/")) {
                 if (token.equals("full")) {
                     // full -- size of original
                     options.setOption("ascale");
                     setValue("scale", 1f);
-                } else if (token.startsWith("pct:")){
+                } else if (token.startsWith("pct:")) {
                     // pct:n -- n% size of original
                     try {
                         float pct = Float.parseFloat(token.substring(4));
                         options.setOption("ascale");
                         setValue("scale", pct / 100);
                     } catch (NumberFormatException e) {
-                        logger.error("Error parsing size parameter in IIIF path!", e);
+                        errorMessage = "Error parsing size parameter in IIIF path!";
+                        logger.error(errorMessage, e);
+                        return false;
                     }
                 } else {
                     // w,h -- pixel size
                     try {
-                        String[] parms = token.split(",");
-                        if (parms[0] != "") {
+                        String[] parms = token.split(",", 2);
+                        if (parms[0].length() > 0) {
                             // width param
                             if (parms[0].startsWith("!")) {
                                 // width (in digilib-like bounding box)
                                 setValueFromString("dw", parms[0].substring(1));
                             } else {
-                                // according to spec, we should distort the image to match ;-(
-                                setValueFromString("dw", parms[0]);                            
+                                // according to spec, we should distort the
+                                // image to match ;-(
+                                setValueFromString("dw", parms[0]);
                             }
                         }
-                        if (parms[1] != "") {
-                            // height param (according to spec, we should distort the image to match ;-()
+                        if (parms[1].length() > 0) {
+                            // height param (according to spec, we should
+                            // distort the image to match ;-()
                             setValueFromString("dh", parms[1]);
                         }
                     } catch (Exception e) {
-                        logger.error("Error parsing size parameter in IIIF path!", e);
+                        errorMessage = "Error parsing size parameter in IIIF path!";
+                        logger.error(errorMessage, e);
+                        return false;
                     }
                 }
                 // skip /
@@ -415,15 +435,22 @@
             // size omitted -- assume "full"
             options.setOption("ascale");
             setValue("scale", 1f);
-            return;
+            return true;
         }
         /*
          * fifth parameter rotation
          */
         if (query.hasMoreTokens()) {
-            token = query.nextToken();
+            token = getNextDecodedToken(query);
             if (!token.equals("/")) {
-                setValueFromString("rot", token);
+                try {
+                    float rot = Float.parseFloat(token);
+                    setValue("rot", rot);
+                } catch (NumberFormatException e) {
+                    errorMessage = "Error parsing rotation parameter in IIIF path!";
+                    logger.error(errorMessage, e);
+                    return false;
+                }
                 // skip /
                 if (query.hasMoreTokens()) {
                     query.nextToken();
@@ -434,26 +461,50 @@
          * sixth parameter quality.format
          */
         if (query.hasMoreTokens()) {
-            token = query.nextToken();
+            token = getNextDecodedToken(query);
             // quality.format -- color depth and output format
             try {
                 String[] parms = token.split("\\.");
-                // quality param (native is default anyway)
-                if (parms[0].equals("grey")) {
+                // quality param
+                if (parms[0].equals("native")||parms[0].equals("color")) {
+                    // native is default anyway
+                } else if (parms[0].equals("grey")) {
                     setValueFromString("colop", "grayscale");
+                } else {
+                    errorMessage = "Invalid quality parameter in IIIF path!";
+                    logger.error(errorMessage);
+                    return false;
                 }
                 // format param (we only support jpg and png)
-                if (parms[1].equals("jpg")) {
+                if (parms.length > 1 && parms[1].equals("jpg")) {
                     // force jpg
                     options.setOption("jpg");
-                } else if (parms[1].equals("png")) {
+                } else if (parms.length > 1 && parms[1].equals("png")) {
                     // force png
                     options.setOption("png");
+                } else if (parms.length > 1) {
+                    errorMessage = "Invalid format parameter in IIIF path!";
+                    logger.error(errorMessage);
+                    return false;
                 }
             } catch (Exception e) {
-                logger.error("Error parsing quality and format parameters in IIIF path!", e);
+                errorMessage = "Error parsing quality and format parameters in IIIF path!";
+                logger.error(errorMessage, e);
+                return false;
             }
         }
+        return true;
+    }
+
+    private String getNextDecodedToken(StringTokenizer tokens) {
+        String token = tokens.nextToken();
+        try {
+            token = URLDecoder.decode(token, "UTF-8");
+            return token;
+        } catch (UnsupportedEncodingException e) {
+            // this shouldn't happen
+        }
+        return null;
     }
 
     /**
--- a/servlet/src/main/java/digilib/conf/DigilibServletRequest.java	Mon Jul 22 13:27:43 2013 +0200
+++ b/servlet/src/main/java/digilib/conf/DigilibServletRequest.java	Mon Jul 22 19:26:43 2013 +0200
@@ -34,6 +34,7 @@
 import javax.servlet.http.HttpServletRequest;
 
 import digilib.image.DocuImage;
+import digilib.image.ImageOpException;
 import digilib.util.OptionsSet;
 import digilib.util.Parameter;
 
@@ -72,6 +73,7 @@
      * ServletRequest. All undefined parameters are set to default values.
      * 
      * @param request
+     * @throws ImageOpException 
      */
     public DigilibServletRequest(HttpServletRequest request) {
         setWithRequest(request);
@@ -83,6 +85,7 @@
      * ServletRequest. All undefined parameters are set to default values.
      * 
      * @param request
+     * @throws ImageOpException 
      */
     public DigilibServletRequest(HttpServletRequest request, DigilibConfiguration config) {
         this.config = config;
@@ -197,6 +200,7 @@
      * Recognizes digilib API (old and new) and IIIF API style requests. 
      * 
      * @param request
+     * @throws ImageOpException 
      */
     public void setWithRequest(HttpServletRequest request) {
         servletRequest = request;
@@ -214,7 +218,10 @@
                 // replace path with part of uri
                 path = uri.substring(mp + ms.length());
             }
-            setWithIiifPath(path.substring(1));
+            if (!setWithIiifPath(path.substring(1))) {
+                // there was an error -- set dw=-1 to indicate, Servlet should check .errorMessage anyway
+                setValue("dw", -1);
+            }
         } else {
             // decide if it's old-style or new-style digilib
             String qs = ((HttpServletRequest) request).getQueryString();
--- a/servlet/src/main/java/digilib/servlet/ServletOps.java	Mon Jul 22 13:27:43 2013 +0200
+++ b/servlet/src/main/java/digilib/servlet/ServletOps.java	Mon Jul 22 19:26:43 2013 +0200
@@ -371,13 +371,13 @@
             } else if (url.endsWith("/")) {
                 url = url.substring(0, url.lastIndexOf("/"));
             }
+            response.setContentType("application/json;charset=UTF-8");
             PrintWriter writer = response.getWriter();
-            response.setContentType("application/json");
             writer.println("{");
             writer.println("\"@context\" : \"http://library.stanford.edu/iiif/image-api/1.1/context.json\",");
             writer.println("\"@id\" : \""+url+"\",");
-            writer.println("\"width\" : \""+size.width+"\",");
-            writer.println("\"height\" : \""+size.height+"\",");
+            writer.println("\"width\" : "+size.width+",");
+            writer.println("\"height\" : "+size.height+",");
             writer.println("\"formats\" : [\"jpg\", \"png\"],");
             writer.println("\"qualities\" : [\"native\", \"color\", \"grey\"],");
             writer.println("\"profile\" : \"http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level2\"");
--- a/servlet3/src/main/java/digilib/servlet/Scaler.java	Mon Jul 22 13:27:43 2013 +0200
+++ b/servlet3/src/main/java/digilib/servlet/Scaler.java	Mon Jul 22 19:26:43 2013 +0200
@@ -254,6 +254,12 @@
             errMsgType = ErrMsg.CODE;
         }
 
+        // error out if request was bad
+        if (dlRequest.errorMessage != null) {
+            digilibError(errMsgType, Error.UNKNOWN, dlRequest.errorMessage, response);
+            return;
+        }
+        
         try {
             /*
              * check if we can fast-track without scaling