/// <summary> /// Converts a raw string into a raw hexadecimal string literal, possibly encrypted. /// </summary> public static string ToHexStringLiteral(string text, PdfStringEncoding encoding, PdfStandardSecurityHandler securityHandler, int paddingLeft) { if (String.IsNullOrEmpty(text) && paddingLeft == 0) { return("<>"); } byte[] bytes; switch (encoding) { case PdfStringEncoding.RawEncoding: bytes = RawEncoding.GetBytes(text); break; case PdfStringEncoding.WinAnsiEncoding: bytes = WinAnsiEncoding.GetBytes(text); break; case PdfStringEncoding.PDFDocEncoding: bytes = DocEncoding.GetBytes(text); break; case PdfStringEncoding.Unicode: //bytes = UnicodeEncoding.GetBytes(text); bytes = RawUnicodeEncoding.GetBytes(text); break; default: throw new NotImplementedException(encoding.ToString()); } if (bytes.Length < paddingLeft) { byte[] tmp = new byte[paddingLeft]; Array.Copy(bytes, tmp, bytes.Length); bytes = tmp; } byte[] agTemp = FormatStringLiteral(bytes, encoding == PdfStringEncoding.Unicode, true, true, securityHandler); return(RawEncoding.GetString(agTemp, 0, agTemp.Length)); }
/// <summary> /// Implements saving a PDF file. /// </summary> void DoSave(PdfWriter writer) { if (this.pages == null || this.pages.Count == 0) { throw new InvalidOperationException("Cannot save a PDF document with no pages."); } try { bool encrypt = this.securitySettings.DocumentSecurityLevel != PdfDocumentSecurityLevel.None; if (encrypt) { PdfStandardSecurityHandler securityHandler = this.securitySettings.SecurityHandler; if (securityHandler.Reference == null) { this.irefTable.Add(securityHandler); } else { Debug.Assert(this.irefTable.Contains(securityHandler.ObjectID)); } this.trailer.Elements[PdfTrailer.Keys.Encrypt] = this.securitySettings.SecurityHandler.Reference; } else { this.trailer.Elements.Remove(PdfTrailer.Keys.Encrypt); } PrepareForSave(); if (encrypt) { this.securitySettings.SecurityHandler.PrepareEncryption(); } writer.WriteFileHeader(this); PdfReference[] irefs = this.irefTable.AllReferences; int count = irefs.Length; for (int idx = 0; idx < count; idx++) { PdfReference iref = irefs[idx]; #if DEBUG_ if (iref.ObjectNumber == 378) { GetType(); } #endif iref.Position = writer.Position; iref.Value.WriteObject(writer); } int startxref = writer.Position; this.irefTable.WriteObject(writer); writer.WriteRaw("trailer\n"); this.trailer.Elements.SetInteger("/Size", count + 1); this.trailer.WriteObject(writer); writer.WriteEof(this, startxref); //if (encrypt) //{ // this.state &= ~DocumentState.SavingEncrypted; // //this.securitySettings.SecurityHandler.EncryptDocument(); //} } finally { if (writer != null) { writer.Stream.Flush(); // DO NOT CLOSE WRITER HERE //writer.Close(); } } }
//public static string EncodeAsLiteral(string text) //{ // return EncodeAsLiteral(text, false); //} /// <summary> /// Converts a raw string into a raw string literal, possibly encrypted. /// </summary> public static string ToStringLiteral(string text, PdfStringEncoding encoding, PdfStandardSecurityHandler securityHandler) { if (String.IsNullOrEmpty(text)) { return("()"); } byte[] bytes; switch (encoding) { case PdfStringEncoding.RawEncoding: bytes = RawEncoding.GetBytes(text); break; case PdfStringEncoding.WinAnsiEncoding: bytes = WinAnsiEncoding.GetBytes(text); break; case PdfStringEncoding.PDFDocEncoding: bytes = DocEncoding.GetBytes(text); break; case PdfStringEncoding.Unicode: bytes = RawUnicodeEncoding.GetBytes(text); break; default: throw new NotImplementedException(encoding.ToString()); } byte[] temp = FormatStringLiteral(bytes, encoding == PdfStringEncoding.Unicode, true, false, securityHandler); return(RawEncoding.GetString(temp, 0, temp.Length)); }
/// <summary> /// Converts the specified byte array into a byte array representing a string literal. /// </summary> /// <param name="bytes">The bytes of the string.</param> /// <param name="unicode">Indicates whether one or two bytes are one character.</param> /// <param name="prefix">Indicates whether to use Unicode prefix.</param> /// <param name="hex">Indicates whether to create a hexadecimal string literal.</param> /// <param name="securityHandler">Encrypts the bytes if specified.</param> /// <returns>The PDF bytes.</returns> public static byte[] FormatStringLiteral(byte[] bytes, bool unicode, bool prefix, bool hex, PdfStandardSecurityHandler securityHandler) { if (bytes == null || bytes.Length == 0) { return hex ? new byte[] { (byte)'<', (byte)'>' } } : new byte[] { (byte)'(', (byte)')' }; Debug.Assert(!unicode || bytes.Length % 2 == 0, "Odd number of bytes in Unicode string."); bool encrypted = false; if (securityHandler != null) { bytes = (byte[])bytes.Clone(); bytes = securityHandler.EncryptBytes(bytes); encrypted = true; } int count = bytes.Length; StringBuilder pdf = new StringBuilder(); if (!unicode) { if (!hex) { pdf.Append("("); for (int idx = 0; idx < count; idx++) { char ch = (char)bytes[idx]; if (ch < 32) { switch (ch) { case '\n': pdf.Append("\\n"); break; case '\r': pdf.Append("\\r"); break; case '\t': pdf.Append("\\t"); break; case '\b': pdf.Append("\\b"); break; // Corrupts encrypted text. //case '\f': // pdf.Append("\\f"); // break; default: // Don't escape characters less than 32 if the string is encrypted, because it is // unreadable anyway. encrypted = true; if (!encrypted) { pdf.Append("\\0"); pdf.Append((char)(ch % 8 + '0')); pdf.Append((char)(ch / 8 + '0')); } else { pdf.Append(ch); } break; } } else { switch (ch) { case '(': pdf.Append("\\("); break; case ')': pdf.Append("\\)"); break; case '\\': pdf.Append("\\\\"); break; default: pdf.Append(ch); break; } } } pdf.Append(')'); } else { pdf.Append('<'); for (int idx = 0; idx < count; idx++) { pdf.AppendFormat("{0:X2}", bytes[idx]); } pdf.Append('>'); } } else { Hex: if (hex) { pdf.Append(prefix ? "<FEFF" : "<"); for (int idx = 0; idx < count; idx += 2) { pdf.AppendFormat("{0:X2}{1:X2}", bytes[idx], bytes[idx + 1]); if (idx != 0 && (idx % 48) == 0) { pdf.Append("\n"); } } pdf.Append(">"); } else { // TODO non hex literals... not sure how to treat linefeeds, '(', '\' etc. hex = true; goto Hex; } } return(RawEncoding.GetBytes(pdf.ToString())); }
/// <summary> /// Converts the specified byte array into a byte array representing a string literal. /// </summary> /// <param name="bytes">The bytes of the string.</param> /// <param name="unicode">Indicates whether one or two bytes are one character.</param> /// <param name="prefix">Indicates whether to use Unicode prefix.</param> /// <param name="hex">Indicates whether to create a hexadecimal string literal.</param> /// <param name="securityHandler">Encrypts the bytes if specified.</param> /// <returns>The PDF bytes.</returns> public static byte[] FormatStringLiteral(byte[] bytes, bool unicode, bool prefix, bool hex, PdfStandardSecurityHandler securityHandler) { if (bytes == null || bytes.Length == 0) { return hex ? new byte[] { (byte)'<', (byte)'>' } } : new byte[] { (byte)'(', (byte)')' }; Debug.Assert(!unicode || bytes.Length % 2 == 0, "Odd number of bytes in Unicode string."); byte[] originalBytes = null; bool encrypted = false; if (securityHandler != null && !hex) { originalBytes = bytes; bytes = (byte[])bytes.Clone(); bytes = securityHandler.EncryptBytes(bytes); encrypted = true; } int count = bytes.Length; StringBuilder pdf = new StringBuilder(); if (!unicode) { if (!hex) { pdf.Append("("); for (int idx = 0; idx < count; idx++) { char ch = (char)bytes[idx]; if (ch < 32) { switch (ch) { case '\n': pdf.Append("\\n"); break; case '\r': pdf.Append("\\r"); break; case '\t': pdf.Append("\\t"); break; case '\b': pdf.Append("\\b"); break; // Corrupts encrypted text. //case '\f': // pdf.Append("\\f"); // break; default: // Don't escape characters less than 32 if the string is encrypted, because it is // unreadable anyway. encrypted = true; if (!encrypted) { pdf.Append("\\0"); pdf.Append((char)(ch % 8 + '0')); pdf.Append((char)(ch / 8 + '0')); } else { pdf.Append(ch); } break; } } else { switch (ch) { case '(': pdf.Append("\\("); break; case ')': pdf.Append("\\)"); break; case '\\': pdf.Append("\\\\"); break; default: pdf.Append(ch); break; } } } pdf.Append(')'); } else { pdf.Append('<'); for (int idx = 0; idx < count; idx++) { pdf.AppendFormat("{0:X2}", bytes[idx]); } pdf.Append('>'); } } else { //Hex: if (hex) { if (securityHandler != null && prefix) { // TODO Reduce redundancy. // Encrypt data after padding BOM. var bytes2 = new byte[bytes.Length + 2]; // Add BOM. bytes2[0] = 0xfe; bytes2[1] = 0xff; // Copy bytes. Array.Copy(bytes, 0, bytes2, 2, bytes.Length); // Encyption. bytes2 = securityHandler.EncryptBytes(bytes2); encrypted = true; pdf.Append("<"); var count2 = bytes2.Length; for (int idx = 0; idx < count2; idx += 2) { pdf.AppendFormat("{0:X2}{1:X2}", bytes2[idx], bytes2[idx + 1]); if (idx != 0 && (idx % 48) == 0) { pdf.Append("\n"); } } pdf.Append(">"); } else { // No prefix or no encryption. pdf.Append(prefix ? "<FEFF" : "<"); for (int idx = 0; idx < count; idx += 2) { pdf.AppendFormat("{0:X2}{1:X2}", bytes[idx], bytes[idx + 1]); if (idx != 0 && (idx % 48) == 0) { pdf.Append("\n"); } } pdf.Append(">"); } } else { // TODO non hex literals... not sure how to treat linefeeds, '(', '\' etc. if (encrypted) { // Hack: Call self with hex := true. return(FormatStringLiteral(originalBytes, unicode, prefix, true, securityHandler)); } else { // Hack: Call self with hex := true. return(FormatStringLiteral(bytes, true, prefix, true, null)); } } } return(RawEncoding.GetBytes(pdf.ToString())); }
/// <summary> /// Opens an existing PDF document. /// </summary> public static PdfDocument Open(Stream stream, string password, PdfDocumentOpenMode openmode, PdfPasswordProvider passwordProvider) { PdfDocument document; try { Lexer lexer = new Lexer(stream); document = new PdfDocument(lexer); document._state |= DocumentState.Imported; document._openMode = openmode; document._fileSize = stream.Length; // Get file version. byte[] header = new byte[1024]; stream.Position = 0; stream.Read(header, 0, 1024); document._version = GetPdfFileVersion(header); if (document._version == 0) { throw new InvalidOperationException(PSSR.InvalidPdf); } document._irefTable.IsUnderConstruction = true; Parser parser = new Parser(document); // Read all trailers or cross-reference streams, but no objects. document._trailer = parser.ReadTrailer(); if (document._trailer == null) { ParserDiagnostics.ThrowParserException("Invalid PDF file: no trailer found."); // TODO L10N using PSSR. } Debug.Assert(document._irefTable.IsUnderConstruction); document._irefTable.IsUnderConstruction = false; // Is document encrypted? PdfReference xrefEncrypt = document._trailer.Elements[PdfTrailer.Keys.Encrypt] as PdfReference; if (xrefEncrypt != null) { //xrefEncrypt.Value = parser.ReadObject(null, xrefEncrypt.ObjectID, false); PdfObject encrypt = parser.ReadObject(null, xrefEncrypt.ObjectID, false, false); encrypt.Reference = xrefEncrypt; xrefEncrypt.Value = encrypt; PdfStandardSecurityHandler securityHandler = document.SecurityHandler; TryAgain: PasswordValidity validity = securityHandler.ValidatePassword(password); if (validity == PasswordValidity.Invalid) { if (passwordProvider != null) { PdfPasswordProviderArgs args = new PdfPasswordProviderArgs(); passwordProvider(args); if (args.Abort) { return(null); } password = args.Password; goto TryAgain; } else { if (password == null) { throw new PdfReaderException(PSSR.PasswordRequired); } else { throw new PdfReaderException(PSSR.InvalidPassword); } } } else if (validity == PasswordValidity.UserPassword && openmode == PdfDocumentOpenMode.Modify) { if (passwordProvider != null) { PdfPasswordProviderArgs args = new PdfPasswordProviderArgs(); passwordProvider(args); if (args.Abort) { return(null); } password = args.Password; goto TryAgain; } else { throw new PdfReaderException(PSSR.OwnerPasswordRequired); } } } else { if (password != null) { // Password specified but document is not encrypted. // ignore } } PdfReference[] irefs2 = document._irefTable.AllReferences; int count2 = irefs2.Length; // 3rd: Create iRefs for all compressed objects. Dictionary <int, object> objectStreams = new Dictionary <int, object>(); for (int idx = 0; idx < count2; idx++) { PdfReference iref = irefs2[idx]; PdfCrossReferenceStream xrefStream = iref.Value as PdfCrossReferenceStream; if (xrefStream != null) { for (int idx2 = 0; idx2 < xrefStream.Entries.Count; idx2++) { PdfCrossReferenceStream.CrossReferenceStreamEntry item = xrefStream.Entries[idx2]; // Is type xref to compressed object? if (item.Type == 2) { //PdfReference irefNew = parser.ReadCompressedObject(new PdfObjectID((int)item.Field2), (int)item.Field3); //document._irefTable.Add(irefNew); int objectNumber = (int)item.Field2; if (!objectStreams.ContainsKey(objectNumber)) { objectStreams.Add(objectNumber, null); PdfObjectID objectID = new PdfObjectID((int)item.Field2); parser.ReadIRefsFromCompressedObject(objectID); } } } } } // 4th: Read compressed objects. for (int idx = 0; idx < count2; idx++) { PdfReference iref = irefs2[idx]; PdfCrossReferenceStream xrefStream = iref.Value as PdfCrossReferenceStream; if (xrefStream != null) { for (int idx2 = 0; idx2 < xrefStream.Entries.Count; idx2++) { PdfCrossReferenceStream.CrossReferenceStreamEntry item = xrefStream.Entries[idx2]; // Is type xref to compressed object? if (item.Type == 2) { PdfReference irefNew = parser.ReadCompressedObject(new PdfObjectID((int)item.Field2), (int)item.Field3); Debug.Assert(document._irefTable.Contains(iref.ObjectID)); //document._irefTable.Add(irefNew); } } } } PdfReference[] irefs = document._irefTable.AllReferences; int count = irefs.Length; // Read all indirect objects. for (int idx = 0; idx < count; idx++) { PdfReference iref = irefs[idx]; if (iref.Value == null) { #if DEBUG_ if (iref.ObjectNumber == 1074) { iref.GetType(); } #endif try { Debug.Assert(document._irefTable.Contains(iref.ObjectID)); PdfObject pdfObject = parser.ReadObject(null, iref.ObjectID, false, false); Debug.Assert(pdfObject.Reference == iref); pdfObject.Reference = iref; Debug.Assert(pdfObject.Reference.Value != null, "Something went wrong."); } catch (Exception ex) { Debug.WriteLine(ex.Message); // 4STLA rethrow exception to notify caller. throw; } } else { Debug.Assert(document._irefTable.Contains(iref.ObjectID)); //iref.GetType(); } // Set maximum object number. document._irefTable._maxObjectNumber = Math.Max(document._irefTable._maxObjectNumber, iref.ObjectNumber); } // Encrypt all objects. if (xrefEncrypt != null) { document.SecurityHandler.EncryptDocument(); } // Fix references of trailer values and then objects and irefs are consistent. document._trailer.Finish(); #if DEBUG_ // Some tests... PdfReference[] reachables = document.xrefTable.TransitiveClosure(document.trailer); reachables.GetType(); reachables = document.xrefTable.AllXRefs; document.xrefTable.CheckConsistence(); #endif if (openmode == PdfDocumentOpenMode.Modify) { // Create new or change existing document IDs. if (document.Internals.SecondDocumentID == "") { document._trailer.CreateNewDocumentIDs(); } else { byte[] agTemp = Guid.NewGuid().ToByteArray(); document.Internals.SecondDocumentID = PdfEncoders.RawEncoding.GetString(agTemp, 0, agTemp.Length); } // Change modification date document.Info.ModificationDate = DateTime.Now; // Remove all unreachable objects int removed = document._irefTable.Compact(); if (removed != 0) { Debug.WriteLine("Number of deleted unreachable objects: " + removed); } // Force flattening of page tree PdfPages pages = document.Pages; Debug.Assert(pages != null); //bool b = document.irefTable.Contains(new PdfObjectID(1108)); //b.GetType(); document._irefTable.CheckConsistence(); document._irefTable.Renumber(); document._irefTable.CheckConsistence(); } } catch (Exception ex) { Debug.WriteLine(ex.Message); throw; } return(document); }
/// <summary> /// Opens an existing PDF document. /// </summary> public static PdfDocument Open(Stream stream, string password, PdfDocumentOpenMode openmode, PdfPasswordProvider passwordProvider) { PdfDocument document = null; try { Lexer lexer = new Lexer(stream); document = new PdfDocument(lexer); document.state |= DocumentState.Imported; document.openMode = openmode; document.fileSize = stream.Length; // Get file version byte[] header = new byte[1024]; stream.Position = 0; stream.Read(header, 0, 1024); document.version = GetPdfFileVersion(header); if (document.version == 0) { throw new InvalidOperationException(PSSR.InvalidPdf); } // Read all trailers document.irefTable.IsUnderConstruction = true; Parser parser = new Parser(document); document.trailer = parser.ReadTrailer(); document.irefTable.IsUnderConstruction = false; // Is document encrypted? PdfReference xrefEncrypt = document.trailer.Elements[PdfTrailer.Keys.Encrypt] as PdfReference; if (xrefEncrypt != null) { //xrefEncrypt.Value = parser.ReadObject(null, xrefEncrypt.ObjectID, false); PdfObject encrypt = parser.ReadObject(null, xrefEncrypt.ObjectID, false); encrypt.Reference = xrefEncrypt; xrefEncrypt.Value = encrypt; PdfStandardSecurityHandler securityHandler = document.SecurityHandler; TryAgain: PasswordValidity validity = securityHandler.ValidatePassword(password); if (validity == PasswordValidity.Invalid) { if (passwordProvider != null) { PdfPasswordProviderArgs args = new PdfPasswordProviderArgs(); passwordProvider(args); if (args.Abort) { return(null); } password = args.Password; goto TryAgain; } else { if (password == null) { throw new PdfReaderException(PSSR.PasswordRequired); } else { throw new PdfReaderException(PSSR.InvalidPassword); } } } else if (validity == PasswordValidity.UserPassword && openmode == PdfDocumentOpenMode.Modify) { if (passwordProvider != null) { PdfPasswordProviderArgs args = new PdfPasswordProviderArgs(); passwordProvider(args); if (args.Abort) { return(null); } password = args.Password; goto TryAgain; } else { throw new PdfReaderException(PSSR.OwnerPasswordRequired); } } } else { if (password != null) { // Password specified but document is not encrypted. // ignore } } PdfReference[] irefs = document.irefTable.AllReferences; int count = irefs.Length; // Read all indirect objects for (int idx = 0; idx < count; idx++) { PdfReference iref = irefs[idx]; if (iref.Value == null) { try { Debug.Assert(document.irefTable.Contains(iref.ObjectID)); PdfObject pdfObject = parser.ReadObject(null, iref.ObjectID, false); Debug.Assert(pdfObject.Reference == iref); pdfObject.Reference = iref; Debug.Assert(pdfObject.Reference.Value != null, "something got wrong"); } catch (Exception ex) { Debug.WriteLine(ex.Message); } } else { Debug.Assert(document.irefTable.Contains(iref.ObjectID)); iref.GetType(); } // Set maximum object number document.irefTable.maxObjectNumber = Math.Max(document.irefTable.maxObjectNumber, iref.ObjectNumber); } // Encrypt all objects if (xrefEncrypt != null) { document.SecurityHandler.EncryptDocument(); } // Fix references of trailer values and then objects and irefs are consistent. document.trailer.Finish(); #if DEBUG_ // Some tests... PdfReference[] reachables = document.xrefTable.TransitiveClosure(document.trailer); reachables.GetType(); reachables = document.xrefTable.AllXRefs; document.xrefTable.CheckConsistence(); #endif if (openmode == PdfDocumentOpenMode.Modify) { // Create new or change existing document IDs if (document.Internals.SecondDocumentID == "") { document.trailer.CreateNewDocumentIDs(); } else { byte[] agTemp = Guid.NewGuid().ToByteArray(); document.Internals.SecondDocumentID = PdfEncoders.RawEncoding.GetString(agTemp, 0, agTemp.Length); } // Change modification date document.Info.ModificationDate = DateTime.Now; // Remove all unreachable objects int removed = document.irefTable.Compact(); if (removed != 0) { Debug.WriteLine("Number of deleted unreachable objects: " + removed.ToString()); } // Force flattening of page tree PdfPages pages = document.Pages; //bool b = document.irefTable.Contains(new PdfObjectID(1108)); //b.GetType(); document.irefTable.CheckConsistence(); document.irefTable.Renumber(); document.irefTable.CheckConsistence(); } } finally { //if (filestream != null) // filestream.Close(); } return(document); }
/// <summary> /// Implements saving a PDF file. /// </summary> void DoSave(PdfWriter writer) { if (_pages == null || _pages.Count == 0) { if (_outStream != null) { // Give feedback if the wrong constructor was used. throw new InvalidOperationException("Cannot save a PDF document with no pages. Do not use \"public PdfDocument(string filename)\" or \"public PdfDocument(Stream outputStream)\" if you want to open an existing PDF document from a file or stream; use PdfReader.Open() for that purpose."); } throw new InvalidOperationException("Cannot save a PDF document with no pages."); } try { // HACK: Remove XRefTrailer if (_trailer is PdfCrossReferenceStream) { // HACK^2: Preserve the SecurityHandler. PdfStandardSecurityHandler securityHandler = _securitySettings.SecurityHandler; _trailer = new PdfTrailer((PdfCrossReferenceStream)_trailer); _trailer._securityHandler = securityHandler; } bool encrypt = _securitySettings.DocumentSecurityLevel != PdfDocumentSecurityLevel.None; if (encrypt) { PdfStandardSecurityHandler securityHandler = _securitySettings.SecurityHandler; if (securityHandler.Reference == null) { _irefTable.Add(securityHandler); } else { Debug.Assert(_irefTable.Contains(securityHandler.ObjectID)); } _trailer.Elements[PdfTrailer.Keys.Encrypt] = _securitySettings.SecurityHandler.Reference; } else { _trailer.Elements.Remove(PdfTrailer.Keys.Encrypt); } PrepareForSave(); if (encrypt) { _securitySettings.SecurityHandler.PrepareEncryption(); } writer.WriteFileHeader(this); PdfReference[] irefs = _irefTable.AllReferences; int count = irefs.Length; for (int idx = 0; idx < count; idx++) { PdfReference iref = irefs[idx]; #if DEBUG_ if (iref.ObjectNumber == 378) { GetType(); } #endif iref.Position = writer.Position; iref.Value.WriteObject(writer); } var startxref = writer.Position; _irefTable.WriteObject(writer); writer.WriteRaw("trailer\n"); _trailer.Elements.SetInteger("/Size", count + 1); _trailer.WriteObject(writer); writer.WriteEof(this, startxref); //if (encrypt) //{ // state &= ~DocumentState.SavingEncrypted; // //_securitySettings.SecurityHandler.EncryptDocument(); //} } finally { if (writer != null) { writer.Stream.Flush(); // DO NOT CLOSE WRITER HERE //writer.Close(); } } }