You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by le...@apache.org on 2022/03/18 06:42:47 UTC

svn commit: r1899026 - in /pdfbox/branches/2.0/pdfbox/src: main/java/org/apache/pdfbox/pdmodel/font/ToUnicodeWriter.java test/java/org/apache/pdfbox/pdmodel/font/TestToUnicodeWriter.java

Author: lehmi
Date: Fri Mar 18 06:42:47 2022
New Revision: 1899026

URL: http://svn.apache.org/viewvc?rev=1899026&view=rev
Log:
PDFBOX-5387: fix overflow detection when writing bfrange entries as proposed by Ryan Jackson

Added:
    pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/font/TestToUnicodeWriter.java   (with props)
Modified:
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/ToUnicodeWriter.java

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/ToUnicodeWriter.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/ToUnicodeWriter.java?rev=1899026&r1=1899025&r2=1899026&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/ToUnicodeWriter.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/ToUnicodeWriter.java Fri Mar 18 06:42:47 2022
@@ -120,34 +120,23 @@ final class ToUnicodeWriter
         List<Integer> srcTo = new ArrayList<Integer>();
         List<String> dstString = new ArrayList<String>();
 
-        int srcPrev = -1;
-        String dstPrev = null;
+        Map.Entry<Integer, String> prev = null;
 
-        int srcCode1 = -1;
-
-        for (Map.Entry<Integer, String> entry : cidToUnicode.entrySet())
+        for (Map.Entry<Integer, String> next : cidToUnicode.entrySet())
         {
-            int cid = entry.getKey();
-            String text = entry.getValue();
-
-            if (cid == srcPrev + 1 &&                                 // CID must be last CID + 1
-                dstPrev.codePointCount(0, dstPrev.length()) == 1 &&   // no UTF-16 surrogates
-                text.codePointAt(0) == dstPrev.codePointAt(0) + 1 &&  // dstString must be prev + 1
-                dstPrev.codePointAt(0) + 1 <= 255 - (cid - srcCode1)) // increment last byte only
+            if (allowCIDToUnicodeRange(prev, next))
             {
                 // extend range
-                srcTo.set(srcTo.size() - 1, cid);
+                srcTo.set(srcTo.size() - 1, next.getKey());
             }
             else
             {
                 // begin range
-                srcCode1 = cid;
-                srcFrom.add(cid);
-                srcTo.add(cid);
-                dstString.add(text);
+                srcFrom.add(next.getKey());
+                srcTo.add(next.getKey());
+                dstString.add(next.getValue());
             }
-            srcPrev = cid;
-            dstPrev = text;
+            prev = next;
         }
 
         // limit entries per operator
@@ -191,4 +180,49 @@ final class ToUnicodeWriter
         writer.write(text);
         writer.write('\n');
     }
+
+    // allowCIDToUnicodeRange returns true if the CID and Unicode destination string are allowed to follow one another
+    // according to the Adobe 1.7 specification as described in Section 5.9, Example 5.16.
+    static boolean allowCIDToUnicodeRange(Map.Entry<Integer, String> prev,
+            Map.Entry<Integer, String> next)
+    {
+        if (prev == null || next == null)
+        {
+            return false;
+        }
+        return allowCodeRange(prev.getKey(), next.getKey())
+                && allowDestinationRange(prev.getValue(), next.getValue());
+    }
+
+    // allowCodeRange returns true if the 16-bit values are sequential and differ only in the low-order byte.
+    static boolean allowCodeRange(int prev, int next)
+    {
+        if ((prev + 1) != next)
+        {
+            return false;
+        }
+        int prevH = (prev >> 8) & 0xFF;
+        int prevL = prev & 0xFF;
+        int nextH = (next >> 8) & 0xFF;
+        int nextL = next & 0xFF;
+
+        return prevH == nextH && prevL < nextL;
+    }
+
+    // allowDestinationRange returns true if the code points represented by the strings are sequential and differ
+    // only in the low-order byte.
+    static boolean allowDestinationRange(String prev, String next)
+    {
+        if (prev.isEmpty() || next.isEmpty())
+        {
+            return false;
+        }
+        int prevCode = prev.codePointAt(0);
+        int nextCode = next.codePointAt(0);
+
+        // Allow the new destination string if:
+        // 1. It is sequential with the previous one and differs only in the low-order byte
+        // 2. The previous string does not contain any UTF-16 surrogates
+        return allowCodeRange(prevCode, nextCode) && prev.codePointCount(0, prev.length()) == 1;
+    }
 }

Added: pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/font/TestToUnicodeWriter.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/font/TestToUnicodeWriter.java?rev=1899026&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/font/TestToUnicodeWriter.java (added)
+++ pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/font/TestToUnicodeWriter.java Fri Mar 18 06:42:47 2022
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.pdmodel.font;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.AbstractMap;
+import java.util.Map;
+
+/**
+ * @author ryanjackson-wf
+ */
+public class TestToUnicodeWriter extends TestCase
+{
+    @Test
+    public void testCMapCIDOverflow() throws IOException
+    {
+        ToUnicodeWriter toUnicodeWriter = new ToUnicodeWriter();
+
+        toUnicodeWriter.add(0x3ff, "6");
+        toUnicodeWriter.add(0x400, "7");
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        toUnicodeWriter.writeTo(baos);
+        String output = baos.toString("ISO-8859-1");
+
+        assertTrue(output.contains("2 beginbfrange"));
+        assertTrue(output.contains("<03FF> <03FF> <0036>"));
+        assertTrue(output.contains("<0400> <0400> <0037>"));
+    }
+
+    @Test
+    public void testCMapStringOverflow() throws IOException
+    {
+        ToUnicodeWriter toUnicodeWriter = new ToUnicodeWriter();
+
+        StringBuilder string1 = new StringBuilder();
+        string1.appendCodePoint(0x04FF);
+        StringBuilder string2 = new StringBuilder();
+        string2.appendCodePoint(0x0500);
+        toUnicodeWriter.add(0x3ff, string1.toString());
+        toUnicodeWriter.add(0x400, string2.toString());
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        toUnicodeWriter.writeTo(baos);
+        String output = baos.toString("ISO-8859-1");
+
+        assertTrue(output.contains("2 beginbfrange"));
+        assertTrue(output.contains("<03FF> <03FF> <04FF>"));
+        assertTrue(output.contains("<0400> <0400> <0500>"));
+    }
+
+    @Test
+    public void testCMapSurrogates() throws IOException
+    {
+        ToUnicodeWriter toUnicodeWriter = new ToUnicodeWriter();
+
+        toUnicodeWriter.add(0x300, new String(new int[] { 0x2F874 }, 0, 1));
+        toUnicodeWriter.add(0x301, new String(new int[] { 0x2F876 }, 0, 1));
+        toUnicodeWriter.add(0x304, new String(new int[] { 0x2F884 }, 0, 1));
+        toUnicodeWriter.add(0x305, new String(new int[] { 0x2F885 }, 0, 1));
+        toUnicodeWriter.add(0x306, new String(new int[] { 0x2F886 }, 0, 1));
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        toUnicodeWriter.writeTo(baos);
+        String output = baos.toString("ISO-8859-1");
+
+        assertTrue(output.contains("3 beginbfrange"));
+        assertTrue(output.contains("<0300> <0300> <D87EDC74>"));
+        assertTrue(output.contains("<0301> <0301> <D87EDC76>"));
+        assertTrue(output.contains("<0304> <0306> <D87EDC84>"));
+    }
+
+    @Test
+    public void testAllowCIDToUnicodeRange()
+    {
+        Map.Entry<Integer, String> six = new AbstractMap.SimpleEntry<Integer, String>(0x03FF, "6");
+        Map.Entry<Integer, String> seven = new AbstractMap.SimpleEntry<Integer, String>(0x0400,
+                "7");
+        Map.Entry<Integer, String> eight = new AbstractMap.SimpleEntry<Integer, String>(0x0401,
+                "8");
+
+        assertFalse(ToUnicodeWriter.allowCIDToUnicodeRange(null, seven));
+        assertFalse(ToUnicodeWriter.allowCIDToUnicodeRange(six, null));
+        assertFalse(ToUnicodeWriter.allowCIDToUnicodeRange(six, seven));
+        assertTrue(ToUnicodeWriter.allowCIDToUnicodeRange(seven, eight));
+    }
+
+    @Test
+    public void testAllowCodeRange()
+    {
+        // Denied progressions (negative)
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x000F, 0x0007));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x00FF, 0x0000));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x03FF, 0x0300));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x0401, 0x0400));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0xFFFF, 0x0000));
+
+        // Denied progressions (non sequential)
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x0000, 0x0000));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x0000, 0x000F));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x0000, 0x007F));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x0000, 0x00FF));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x0007, 0x000F));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x007F, 0x00FF));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x00FF, 0x00FF));
+
+        // Denied progressions (overflow)
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x00FF, 0x0100));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x01FF, 0x0200));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x03FF, 0x0400));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x07FF, 0x0800));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x0FFF, 0x1000));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x1FFF, 0x2000));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x3FFF, 0x4000));
+        assertFalse(ToUnicodeWriter.allowCodeRange(0x7FFF, 0x8000));
+
+        // Allowed progressions (positive, sequential, and w/o overflow)
+        assertTrue(ToUnicodeWriter.allowCodeRange(0x00, 0x01));
+        assertTrue(ToUnicodeWriter.allowCodeRange(0x01, 0x02));
+        assertTrue(ToUnicodeWriter.allowCodeRange(0x03, 0x04));
+        assertTrue(ToUnicodeWriter.allowCodeRange(0x07, 0x08));
+        assertTrue(ToUnicodeWriter.allowCodeRange(0x0E, 0x0F));
+        assertTrue(ToUnicodeWriter.allowCodeRange(0x1F, 0x20));
+        assertTrue(ToUnicodeWriter.allowCodeRange(0x3F, 0x40));
+        assertTrue(ToUnicodeWriter.allowCodeRange(0x7F, 0x80));
+        assertTrue(ToUnicodeWriter.allowCodeRange(0xFE, 0xFF));
+        assertTrue(ToUnicodeWriter.allowCodeRange(0x03FE, 0x03FF));
+        assertTrue(ToUnicodeWriter.allowCodeRange(0x0400, 0x0401));
+        assertTrue(ToUnicodeWriter.allowCodeRange(0xFFFE, 0xFFFF));
+    }
+
+    @Test
+    public void testAllowDestinationRange()
+    {
+        // Denied (bogus)
+        assertFalse(ToUnicodeWriter.allowDestinationRange("", ""));
+        assertFalse(ToUnicodeWriter.allowDestinationRange("0", ""));
+        assertFalse(ToUnicodeWriter.allowDestinationRange("", "0"));
+
+        // Denied (non sequential)
+        assertFalse(ToUnicodeWriter.allowDestinationRange("0", "A"));
+        assertFalse(ToUnicodeWriter.allowDestinationRange("A", "a"));
+
+        // Denied (overflow)
+        assertFalse(ToUnicodeWriter.allowDestinationRange("ÿ", "Ā"));
+
+        // Check surrogates
+        StringBuilder endOfBMP = new StringBuilder();
+        endOfBMP.appendCodePoint(0xFFFF);
+
+        StringBuilder beyondBMP = new StringBuilder();
+        beyondBMP.appendCodePoint(0x10000);
+
+        StringBuilder cjk1 = new StringBuilder();
+        cjk1.appendCodePoint(0x2F884);
+
+        StringBuilder cjk2 = new StringBuilder();
+        cjk2.appendCodePoint(0x2F885);
+
+        StringBuilder cjk3 = new StringBuilder();
+        cjk3.appendCodePoint(0x2F886);
+
+        // Denied overflow
+        assertFalse(
+                ToUnicodeWriter.allowDestinationRange(endOfBMP.toString(), beyondBMP.toString()));
+        // Allowed (sequential surrogates)
+        assertTrue(ToUnicodeWriter.allowDestinationRange(cjk1.toString(), cjk2.toString()));
+        assertTrue(ToUnicodeWriter.allowDestinationRange(cjk2.toString(), cjk3.toString()));
+        // Denied (non sequential surrogates)
+        assertFalse(ToUnicodeWriter.allowDestinationRange(cjk1.toString(), cjk3.toString()));
+
+        // Allowed (sequential w/o surrogate)
+        assertTrue(ToUnicodeWriter.allowDestinationRange(" ", "!"));
+        assertTrue(ToUnicodeWriter.allowDestinationRange("(", ")"));
+        assertTrue(ToUnicodeWriter.allowDestinationRange("0", "1"));
+        assertTrue(ToUnicodeWriter.allowDestinationRange("a", "b"));
+        assertTrue(ToUnicodeWriter.allowDestinationRange("A", "B"));
+        assertTrue(ToUnicodeWriter.allowDestinationRange("À", "Á"));
+        assertTrue(ToUnicodeWriter.allowDestinationRange("þ", "ÿ"));
+    }
+}
\ No newline at end of file

Propchange: pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/font/TestToUnicodeWriter.java
------------------------------------------------------------------------------
    svn:eol-style = native