/// <summary> /// Closes this instance. /// </summary> public void Close() { if (!CanModify) { throw new InvalidOperationException(PSSR.CannotModify); } if (OutStream != null) { // Get security handler if document gets encrypted PDFStandardSecurityHandler securityHandler = null; if (SecuritySettings.DocumentSecurityLevel != PDFDocumentSecurityLevel.None) { securityHandler = SecuritySettings.SecurityHandler; } PDFWriter writer = new PDFWriter(OutStream, securityHandler); try { DoSave(writer); } finally { writer.Close(); } } }
public PDFWriter(Stream pdfStream, PDFStandardSecurityHandler securityHandler) { Stream = pdfStream; SecurityHandler = securityHandler; //System.Xml.XmlTextWriter #if DEBUG Layout = PDFWriterLayout.Verbose; #endif }
/// <summary> /// Converts a raw string into a raw hexadecimal string literal, possibly encrypted. /// </summary> public static string ToHexStringLiteral(byte[] bytes, bool unicode, PDFStandardSecurityHandler securityHandler) { if (bytes == null || bytes.Length == 0) { return("<>"); } byte[] agTemp = FormatStringLiteral(bytes, unicode, true, true, securityHandler); return(RawEncoding.GetString(agTemp, 0, agTemp.Length)); }
/// <summary> /// Saves the document to the specified stream. /// </summary> public void Save(Stream stream, bool closeStream) { if (!CanModify) { throw new InvalidOperationException(PSSR.CannotModify); } // TODO: more diagnostic checks string message = ""; if (!CanSave(ref message)) { throw new PDFSharpException(message); } // Get security handler if document gets encrypted. PDFStandardSecurityHandler securityHandler = null; if (SecuritySettings.DocumentSecurityLevel != PDFDocumentSecurityLevel.None) { securityHandler = SecuritySettings.SecurityHandler; } PDFWriter writer = null; try { writer = new PDFWriter(stream, securityHandler); DoSave(writer); } finally { if (stream is Stream) { if (closeStream) { stream.Close(); } else if (stream.CanRead == true && stream.CanSeek == true) { stream.Position = 0; // Reset the stream position if the stream is kept open. } } writer?.Close(closeStream); } }
/// <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]; if (iref.Value is PDFCrossReferenceStream xrefStream) { 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]; if (iref.Value is PDFCrossReferenceStream xrefStream) { 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> /// 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. byte[] 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("<"); int 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> /// Converts a raw string into a raw hexadecimal string literal, possibly encrypted. /// </summary> public static string ToHexStringLiteral(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 = UnicodeEncoding.GetBytes(text); bytes = RawUnicodeEncoding.GetBytes(text); break; default: throw new NotImplementedException(encoding.ToString()); } 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 (_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) { _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]; iref.Position = writer.Position; iref.Value.WriteObject(writer); } int startxref = writer.Position; IrefTable.WriteObject(writer); writer.WriteRaw("trailer\n"); Trailer.Elements.SetInteger("/Size", count + 1); Trailer.WriteObject(writer); writer.WriteEof(this, startxref); } finally { if (writer != null) { writer.Stream.Flush(); } } }