Skip to content

Commit

Permalink
PDFBOX-5469: make COSString immutable
Browse files Browse the repository at this point in the history
git-svn-id: https://svn.apache.org/repos/asf/pdfbox/trunk@1917610 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
lehmi committed May 9, 2024
1 parent 5c7937d commit 865d482
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 58 deletions.
59 changes: 31 additions & 28 deletions pdfbox/src/main/java/org/apache/pdfbox/cos/COSString.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ public final class COSString extends COSBase
{
private static final Logger LOG = LogManager.getLogger(COSString.class);

private byte[] bytes;
private boolean forceHexForm;
private final byte[] bytes;
private final boolean forceHexForm;

// legacy behaviour for old PDFParser
public static final boolean FORCE_PARSING =
Expand All @@ -62,7 +62,21 @@ public final class COSString extends COSBase
*/
public COSString(byte[] bytes)
{
setValue(bytes);
this(bytes, false);
}

/**
* Creates a new PDF string from a byte array. This method can be used to read a string from an existing PDF file,
* or to create a new byte string.
*
* @param bytes The raw bytes of the PDF text string or byte string.
* @param forceHex forces the hexadecimal presentation of the string if set to true
*
*/
public COSString(byte[] bytes, boolean forceHex)
{
forceHexForm = forceHex;
this.bytes = Arrays.copyOf(bytes, bytes.length);
}

/**
Expand All @@ -72,6 +86,19 @@ public COSString(byte[] bytes)
*/
public COSString(String text)
{
this(text, false);
}

/**
* Creates a new <i>text string</i> from a Java String.
*
* @param text The string value of the object.
* @param forceHex forces the hexadecimal presentation of the string if set to true
*
*/
public COSString(String text, boolean forceHex)
{
forceHexForm = forceHex;
// check whether the string uses only characters available in PDFDocEncoding
boolean isOnlyPDFDocEncoding = true;
for (char c : text.toCharArray())
Expand Down Expand Up @@ -141,30 +168,6 @@ public static COSString parseHex(String hex) throws IOException
return new COSString(bytes.toByteArray());
}

/**
* Sets the raw value of this string.
*
* @param value The raw bytes of the PDF text string or byte string.
*
* @deprecated to be removed in a future release.
*/
@Deprecated
public void setValue(byte[] value)
{
bytes = value.clone();
}

/**
* Sets whether to force the string is to be written in hex form.
* This is needed when signing PDF files.
*
* @param value True to force hex.
*/
public void setForceHexForm(boolean value)
{
this.forceHexForm = value;
}

/**
* Returns true if the string is to be written in hex form.
*
Expand Down Expand Up @@ -219,7 +222,7 @@ public String getASCII()
*/
public byte[] getBytes()
{
return bytes.clone();
return Arrays.copyOf(bytes, bytes.length);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,9 @@ private COSBase parseFileObject(Long objOffset, final COSObjectKey objKey)
}
else if (securityHandler != null)
{
securityHandler.decrypt(parsedObject, objKey.getNumber(), objKey.getGeneration());
parsedObject = securityHandler.decrypt(parsedObject, objKey.getNumber(),
objKey.getGeneration());
parsedObject.setKey(objKey);
}

if (!endObjectKey.startsWith(ENDOBJ_STRING))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1429,12 +1429,17 @@ public void visitFromString(COSString obj) throws IOException
{
if (willEncrypt)
{
pdDocument.getEncryption().getSecurityHandler().encryptString(
COSString encryptedCOSString = (COSString) pdDocument.getEncryption()
.getSecurityHandler().encryptString(
obj,
currentObjectKey.getNumber(),
currentObjectKey.getGeneration());
COSWriter.writeString(encryptedCOSString, getStandardOutput());
}
else
{
COSWriter.writeString(obj, getStandardOutput());
}
COSWriter.writeString(obj, getStandardOutput());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,29 +444,33 @@ private SecureRandom getSecureRandom()
/**
* This will dispatch to the correct method.
*
* @param obj The object to decrypt.
* @param obj The object to decrypt.
* @param objNum The object number.
* @param genNum The object generation Number.
*
* @return the encrypted/decrypted COS object
*
* @throws IOException If there is an error getting the stream data.
*/
public void decrypt(COSBase obj, long objNum, long genNum) throws IOException
public COSBase decrypt(COSBase obj, long objNum, long genNum) throws IOException
{
// PDFBOX-4477: only cache strings and streams, this improves speed and memory footprint
if (obj instanceof COSString)
{
if (objects.contains(obj))
{
return;
return obj;
}
objects.add(obj);
decryptString((COSString) obj, objNum, genNum);
// replace the given COSString object with the encrypted/decrypted version
COSBase decryptedString = decryptString((COSString) obj, objNum, genNum);
objects.add(decryptedString);
return decryptedString;
}
else if (obj instanceof COSStream)
if (obj instanceof COSStream)
{
if (objects.contains(obj))
{
return;
return obj;
}
objects.add(obj);
decryptStream((COSStream) obj, objNum, genNum);
Expand All @@ -479,6 +483,7 @@ else if (obj instanceof COSArray)
{
decryptArray((COSArray) obj, objNum, genNum);
}
return obj;
}

/**
Expand Down Expand Up @@ -610,7 +615,7 @@ private void decryptDictionary(COSDictionary dictionary, long objNum, long genNu
// within a dictionary only the following kind of COS objects have to be decrypted
if (value instanceof COSString || value instanceof COSArray || value instanceof COSDictionary)
{
decrypt(value, objNum, genNum);
entry.setValue(decrypt(value, objNum, genNum));
}
}
}
Expand All @@ -621,27 +626,29 @@ private void decryptDictionary(COSDictionary dictionary, long objNum, long genNu
* @param string the string to decrypt.
* @param objNum The object number.
* @param genNum The object generation number.
*
*
* @return the decrypted COSString
*/
private void decryptString(COSString string, long objNum, long genNum)
private COSBase decryptString(COSString string, long objNum, long genNum)
{
// String encrypted with identity filter
if (COSName.IDENTITY.equals(stringFilterName))
{
return;
return string;
}

ByteArrayInputStream data = new ByteArrayInputStream(string.getBytes());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try
{
encryptData(objNum, genNum, data, outputStream, true /* decrypt */);
string.setValue(outputStream.toByteArray());
return new COSString(outputStream.toByteArray());
}
catch (IOException ex)
{
LOG.error("Failed to decrypt COSString of length {} in object {}: {}",
string.getBytes().length, objNum, ex.getMessage(), ex);
return string;
}
}

Expand All @@ -652,14 +659,16 @@ private void decryptString(COSString string, long objNum, long genNum)
* @param objNum The object number.
* @param genNum The object generation number.
*
* @return the encrypted COSString
*
* @throws IOException If an error occurs writing the new string.
*/
public void encryptString(COSString string, long objNum, int genNum) throws IOException
public COSBase encryptString(COSString string, long objNum, int genNum) throws IOException
{
ByteArrayInputStream data = new ByteArrayInputStream(string.getBytes());
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
encryptData(objNum, genNum, data, buffer, false /* encrypt */);
string.setValue(buffer.toByteArray());
return new COSString(buffer.toByteArray());
}

/**
Expand All @@ -675,7 +684,7 @@ private void decryptArray(COSArray array, long objNum, long genNum) throws IOExc
{
for (int i = 0; i < array.size(); i++)
{
decrypt(array.get(i), objNum, genNum);
array.set(i, decrypt(array.get(i), objNum, genNum));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,7 @@ private byte[] getConvertedContents(InputStream is) throws IOException
*/
public void setContents(byte[] bytes)
{
COSString string = new COSString(bytes);
string.setForceHexForm(true);
COSString string = new COSString(bytes, true);
dictionary.setItem(COSName.CONTENTS, string);
}

Expand Down
18 changes: 8 additions & 10 deletions pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSString.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,14 @@ void testSetForceHexLiteralForm()
{
String inputString = "Test with a text and a few numbers 1, 2 and 3";
String pdfHex = "<" + createHex(inputString) + ">";
COSString cosStr = new COSString(inputString);
cosStr.setForceHexForm(true);
COSString cosStr = new COSString(inputString, true);
writePDFTests(pdfHex, cosStr);

COSString escStr = new COSString(ESC_CHAR_STRING);
writePDFTests("(" + ESC_CHAR_STRING_PDF_FORMAT + ")", escStr);
escStr.setForceHexForm(true);
COSString escStrHex = new COSString(ESC_CHAR_STRING, true);
// Escape characters not escaped in hex version
writePDFTests("<" + createHex(ESC_CHAR_STRING) + ">", escStr);
writePDFTests("<" + createHex(ESC_CHAR_STRING) + ">", escStrHex);
}

/**
Expand Down Expand Up @@ -265,8 +264,8 @@ void testAccept() throws IOException
testSubj.accept(visitor);
assertEquals("(" + ESC_CHAR_STRING_PDF_FORMAT + ")", outStream.toString());
outStream.reset();
testSubj.setForceHexForm(true);
testSubj.accept(visitor);
COSString testSubjHex = new COSString(ESC_CHAR_STRING, true);
testSubjHex.accept(visitor);
assertEquals("<" + createHex(ESC_CHAR_STRING) + ">", outStream.toString());
}

Expand All @@ -287,8 +286,7 @@ void testEquals()
COSString y1 = new COSString("Test");
assertEquals(x1, y1);
assertEquals(y1, x1);
COSString x2 = new COSString("Test");
x2.setForceHexForm(true);
COSString x2 = new COSString("Test", true);
// also if x != y then y != x
assertNotEquals(x1, x2);
assertNotEquals(x2, x1);
Expand Down Expand Up @@ -316,8 +314,8 @@ void testHashCode()
assertNotEquals(str1.hashCode(), str2.hashCode());
COSString str3 = new COSString("Test1");
assertEquals(str1.hashCode(), str3.hashCode());
str3.setForceHexForm(true);
assertNotEquals(str1.hashCode(), str3.hashCode());
COSString str3Hex = new COSString("Test1", true);
assertNotEquals(str1.hashCode(), str3Hex.hashCode());
}

/**
Expand Down

0 comments on commit 865d482

Please sign in to comment.