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