From 2d40d6e71d48eca0efae0da3474ffa35a182bf8a Mon Sep 17 00:00:00 2001 From: Steven Kirbach Date: Mon, 24 Jun 2019 18:11:27 -0700 Subject: [PATCH 1/3] fixing xps code --- .../ReachFramework/Packaging/XpsDocument.cs | 30 +- .../Packaging/XpsFixedDocumentReaderWriter.cs | 2560 +++++++++-------- .../XpsFixedDocumentSequenceReaderWriter.cs | 47 +- .../Packaging/XmlDigitalSignatureProcessor.cs | 5 +- 4 files changed, 1327 insertions(+), 1315 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsDocument.cs b/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsDocument.cs index 3eca3c41df0..8b19df2ba8f 100644 --- a/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsDocument.cs +++ b/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsDocument.cs @@ -325,26 +325,28 @@ public ReadOnlyCollection Signatures foreach( PackagePart part in xmlPartList ) { - Stream stream = part.GetStream(FileMode.Open, FileAccess.Read ); - // - // An empty stream contains not version extensibility thus is valid - // We do create empty parts for print tickets - // - if( stream.Length == 0 ) - continue; - try + using (Stream stream = part.GetStream(FileMode.Open, FileAccess.Read)) { - if( StreamContainsVersionExtensiblity(stream) ) + // + // An empty stream contains not version extensibility thus is valid + // We do create empty parts for print tickets + // + if (stream.Length == 0) + continue; + try + { + if (StreamContainsVersionExtensiblity(stream)) + { + isSignable = false; + break; + } + } + catch (XmlException) { isSignable = false; break; } } - catch( XmlException ) - { - isSignable = false; - break; - } } return isSignable; } diff --git a/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs b/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs index 4396796c292..d6f07267bd3 100644 --- a/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs +++ b/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs @@ -1,1278 +1,1282 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*++ - - - - Abstract: - This file contains the definition for IXpsFixedDocumentReader - and IXpsFixedDocumentWriter interfaces as well as definition - and implementation of XpsFixedDocumentReaderWriter. These - interfaces and class are used for writing fixed document - parts to a Xps package. - - - ---*/ -using System; -using System.Collections; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using System.IO; -using System.IO.Packaging; -using System.Xml; -using System.Security.Cryptography.X509Certificates; -using System.Printing; - -using MS.Internal.IO.Packaging.Extensions; -using PackageRelationship = System.IO.Packaging.PackageRelationship; -using PackUriHelper = System.IO.Packaging.PackUriHelper; - -namespace System.Windows.Xps.Packaging -{ - #region IXpsFixedDocumentReader interface definition - - /// - /// Interface for reading fixed document parts from the Xps package. - /// - /// Interface will be internal until reading/de-serialization is implemented. - public interface IXpsFixedDocumentReader: - IDocumentStructureProvider - { - #region Public methods - - /// - /// This method retrieves a fixed page part from a Xps - /// package using the given URI. - /// - /// - /// The URI of the fixed page to retrieve. - /// - /// - /// Returns an interface to an IXpsFixedPageReader to read - /// the requested fixed page. - /// - IXpsFixedPageReader - GetFixedPage( - Uri pageSource - ); - - #endregion Public methods - - #region Public properties - - /// - /// Gets the PrintTicket associated with this fixed document. - /// - /// Value can be a PrintTicket or null. - /// PrintTicket has already been committed. - /// Property is not a valid PrintTicket instance. - PrintTicket PrintTicket { get; } - - /// - /// Gets the URI assigned to this fixed document part. - /// - /// Value is a URI for the Metro part. - Uri Uri { get; } - - /// - /// Gets a collection of all fixed pages that are contained within - /// this fixed document. - /// - /// - /// Value is a Collection containing IXpsFixedPageReader interfaces. - /// - ReadOnlyCollection FixedPages { get; } - - /// - /// 0 based document number in the document sequence - /// - int DocumentNumber{ get; } - - /// - /// A list of signature definitions associated with the document - /// - ICollection - SignatureDefinitions{ get; } - - /// - /// thumbnail image associated with this doucment - /// - XpsThumbnail - Thumbnail{ get; } - - /// - /// Document Structure associated with this doucment - /// - XpsStructure - DocumentStructure{ get; } - - - #endregion Public properties - - /// - /// Adds the passed Signature Definiton to the cached - /// Signature Definition list - /// Will not be written until FlushSignatureDefinition - /// is called - /// - /// - /// the Signature Definition to be added - /// - void - AddSignatureDefinition( - XpsSignatureDefinition signatureDefinition - ); - - /// - /// This method removes a signature definitions associated with - /// the FixedDocument - /// - /// - /// Signature Definition to remove - /// - void - RemoveSignatureDefinition(XpsSignatureDefinition signatureDefinition); - - /// - /// Writes out all modifications to Signature - /// Definitions as well as new Signature Definitions - /// - void - CommitSignatureDefinition(); - } - - #endregion IXpsFixedDocumentReader interface definition - - #region IXpsFixedDocumentWriter interface definition - - /// - /// Interface for writing fixed document parts to the xps package. - /// - public interface IXpsFixedDocumentWriter: - IDocumentStructureProvider - { - #region Public methods - - /// - /// This method adds a fixed page part to the Xps package - /// and associates it with the current fixed document. - /// - /// - /// Returns an interface to the newly created fixed page. - /// - /// The FixedDocument has already been disposed - /// FixedPage is not completed. - IXpsFixedPageWriter - AddFixedPage( - ); - - /// - /// This method adds a thumbnail to the current DocumentSequence. - /// - /// There can only be one thumbnail attached to the DocumentSequence. - /// Calling this method when there - /// is already a starting part causes InvalidOperationException to be - /// thrown. - /// - /// Returns a XpsThumbnail instance. - XpsThumbnail - AddThumbnail( - XpsImageType imageType - ); - - /// - /// This method commits any changes not already committed for this - /// fixed document. - /// - /// - void - Commit( - ); - - #endregion Public methods - - #region Public properties - - /// - /// Sets the PrintTicket associated with this fixed document. - /// - /// - /// The value must be a valid PrintTicket instance. - /// - /// Note: The PrintTicket can only be assigned to prior to if being - /// committed to the package. The commit happens when a valid PrintTicket - /// is set and a subsequent flush on the document occurs. - /// - /// PrintTicket has already been committed. - /// Property is not a valid PrintTicket instance. - PrintTicket PrintTicket { set; } - - /// - /// Gets the URI assigned to this fixed document part. - /// - /// Value is a URI for the Metro part. - Uri Uri { get; } - - /// - /// 0 based document number in the document sequence - /// - int DocumentNumber{ get; } - - #endregion Public properties - } - - #endregion IXpsFixedDocumentWriter interface definition - - /// - /// This class implements the reading and writing functionality for - /// a fixed document within a Xps package. - /// - internal sealed class XpsFixedDocumentReaderWriter : XpsPartBase, - IXpsFixedDocumentReader, - IXpsFixedDocumentWriter, - INode, - IDisposable - { - #region Constructors - - /// - /// Internal constructor for the XpsFixedDocumentReaderWriter class. - /// This class is created internally so we can keep the Xps hierarchy - /// completely under control when using these APIs. - /// - /// - /// The XpsManager for the current Xps package. - /// - /// - /// The parent node of this fixed document. - /// - /// - /// The internal Metro part that represents this fixed document. - /// - /// - /// The 0 base document number in the document sequence - /// - /// part is null. - internal - XpsFixedDocumentReaderWriter( - XpsManager xpsManager, - INode parent, - PackagePart part, - int documentNumber - ) - : base(xpsManager) - { - if (null == part) - { - throw new ArgumentNullException("part"); - } - - this.Uri = part.Uri; - - _metroPart = part; - - _partEditor = new XmlPartEditor(_metroPart); - - _pageCache = new List(); - - _pagesWritten = 0; - - _parentNode = parent; - - _hasParsedPages = false; - - _documentNumber = documentNumber; - } - - #endregion Constructors - - #region Public properties - - /// - /// Gets or sets the PrintTicket to be stored with this fixed document. - /// - /// NOTE: The PrintTicket can only be assigned to prior to if being - /// committed to the package. The commit happens when a valid PrintTicket - /// is set and a subsequent flush on the document occurs. - /// - /// Value can be a PrintTicket or null. - /// PrintTicket has already been committed. - /// Property is not a valid PrintTicket instance. - public PrintTicket PrintTicket - { - get - { - if( _printTicket == null ) - { - _printTicket = CurrentXpsManager.EnsurePrintTicket( Uri ); - } - return _printTicket; - } - set - { - if(value != null) - { - if (_isPrintTicketCommitted) - { - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PrintTicketAlreadyCommitted)); - } - if (!value.GetType().Equals(typeof(PrintTicket))) - { - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_NotAPrintTicket)); - } - - _printTicket = value.Clone(); - } - else - { - _printTicket = null; - } - } - } - - /// - /// Gets a collection of all fixed pages that are contained within - /// this fixed document. - /// - /// - /// Value is a Collection containing IXpsFixedPageReader interfaces. - /// - /// - /// This is only internal until it is implemented. - /// - public ReadOnlyCollection FixedPages - { - get - { - UpdatePageCache(); - return new ReadOnlyCollection(_pageCache ); - } - } - - /// - /// A list of signature definitions associated with the document - /// - public - ICollection - SignatureDefinitions - { - get - { - EnsureSignatureDefinitions(); - return _signatureDefinitions; - } - } - - /// - /// 0 based document number in the document sequence - /// - public int DocumentNumber - { - get - { - return _documentNumber; - } - } - - public - XpsThumbnail - Thumbnail - { - get - { - EnsureThumbnail(); - return _thumbnail; - } - } - - public - XpsStructure - DocumentStructure - { - get - { - EnsureDoucmentStructure(); - return _documentStructure; - } - } - - #endregion Public properties - - #region Public methods - /// - /// This method adds a fixed page part to the Xps package - /// and associates it with the current fixed document. - /// - /// - /// Returns an interface to the newly created fixed page. - /// - /// The FixedDocument has already been disposed - /// FixedPage is not completed. - public - IXpsFixedPageWriter - AddFixedPage( - ) - { - if (null == _metroPart || null == CurrentXpsManager.MetroPackage) - { - throw new ObjectDisposedException("XpsFixedDocumentReaderWriter"); - } - - // - // Only one page can be created/written at a time. - // - if (null != _currentPage) - { - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PanelOrSequenceAlreadyOpen)); - } - - - _linkTargetStream = new List(); - - // - // Create the part and writer - // - PackagePart metroPart = this.CurrentXpsManager.GenerateUniquePart(XpsS0Markup.FixedPageContentType); - XpsFixedPageReaderWriter fixedPage = new XpsFixedPageReaderWriter(CurrentXpsManager, this, metroPart, _linkTargetStream, _pagesWritten + 1); - - // - // Make the new page the current page - // - _currentPage = fixedPage; - - - //Here we used to add the fixed page to _pageCache, but _pageCache is never accessed if this object was created as an IXpsFixedDocumentWriter. - //So instead keep a separate pagesWritten count and forget about the cache when using this method. - _pagesWritten++; - return fixedPage; - } - - /// - /// This method adds a thumbnail to the current DocumentSequence. - /// - /// There can only be one thumbnail attached to the DocumentSequence. - /// Calling this method when there - /// is already a starting part causes InvalidOperationException to be - /// thrown. - /// - /// Returns a XpsThumbnail instance. - public - XpsThumbnail - AddThumbnail( - XpsImageType imageType - ) - { - _thumbnail = CurrentXpsManager.AddThumbnail( imageType, this, Thumbnail ); - _metroPart.CreateRelationship( _thumbnail.Uri, - TargetMode.Internal, - XpsS0Markup.ThumbnailRelationshipName - ); - return _thumbnail; - } - - /// - /// - public - XpsStructure - AddDocumentStructure( - ) - { - if (this.DocumentStructure != null) - { - // Document structure already available for this FixedDocument - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_MoreThanOneDocStructure)); - } - - Uri pageUri = this.CurrentXpsManager.CreateStructureUri(); - // - // Create the part and writer - // - PackagePart metroPart = this.CurrentXpsManager.GeneratePart( - XpsS0Markup.DocumentStructureContentType, - pageUri); - - _documentStructure = new XpsStructure(CurrentXpsManager, this, metroPart); - - // - // Create the relationship between the document and the document-structure - // Not in INode.Flush because IXpsFixedDocumentReader has no commit. - // - - string structurePath = XpsManager.MakeRelativePath(this.Uri, _documentStructure.Uri); - - _metroPart.CreateRelationship(new Uri(structurePath, UriKind.Relative), - TargetMode.Internal, - XpsS0Markup.StructureRelationshipName - ); - - return _documentStructure; - } - - /// - /// This method adds a relationship for this part that - /// targets the specified Uri and is based on the specified - /// content type. - /// - /// - /// Uri target for relationship. - /// - /// - /// Relationship type to add. - /// - public - void - AddRelationship( - Uri targetUri, - string relationshipName - ) - { - // - // We can not read from the file to do validation - // when streaming - // - if( !CurrentXpsManager.Streaming ) - { - foreach (PackageRelationship rel in _metroPart.GetRelationships()) - { - if (rel.TargetUri.Equals(targetUri)) - { - // - // Relationship already exists - // - return; - } - } - } - - // - // Add the relationship using a relative path to this page. - // - string relativePath = XpsManager.MakeRelativePath(this.Uri, targetUri); - _metroPart.CreateRelationship(new Uri(relativePath, UriKind.Relative), - TargetMode.Internal, - relationshipName); - } - - /// - /// This method retrieves a fixed page part from a Xps - /// package using the given URI. - /// - /// - /// The URI of the fixed page to retrieve. - /// - /// - /// Returns an interface to an IXpsFixedPageReader to read - /// the requested fixed page. - /// - public - IXpsFixedPageReader - GetFixedPage( - Uri pageUri - ) - { - UpdatePageCache(); - IXpsFixedPageReader pageReader = null; - - foreach (IXpsFixedPageReader reader in _pageCache ) - { - if( reader.Uri == pageUri ) - { - pageReader = reader; - } - } - - return pageReader; - } - - public - void - AddSignatureDefinition( - XpsSignatureDefinition signatureDefinition - ) - { - EnsureSignatureDefinitions(); - _signatureDefinitions.Add( signatureDefinition ); - _sigCollectionDirty = true; -} - - public - void - CommitSignatureDefinition() - { - bool isDirty = false; - - // - // if the collection is dirty not point in testing - // each signature. - // - if( !_sigCollectionDirty ) - { - foreach( XpsSignatureDefinition sigDef in _signatureDefinitions ) - { - if( sigDef.HasBeenModified ) - { - isDirty = true; - break; - } - } - } - if( isDirty || _sigCollectionDirty ) - { - WriteSignatureDefinitions(); - } - } - - /// - /// This method removes a signature definitions associated with - /// the FixedDocument - /// - /// - /// Signature Definition to remove - /// - public - void - RemoveSignatureDefinition(XpsSignatureDefinition signatureDefinition) - { - EnsureSignatureDefinitions(); - _signatureDefinitions.Remove( signatureDefinition ); - _sigCollectionDirty = true; -} - - /// - /// This method commits any changes not already committed for this - /// fixed document. - /// - /// NOTE: This commits changes to all child object under this - /// branch of the tree. No further changes will be allowed. - /// - public - void - Commit( - ) - { - CommitInternal(); - } - - /// - /// This method closes streams and frees memory for this - /// fixed document. - /// - internal - override - void - CommitInternal( - ) - { - CommitPrintTicket(); - if (null != _partEditor) - { - if (null != _partEditor.XmlWriter) - { - if(_partEditor.DoesWriteStartEndTags) - { - if(_partEditor.IsStartElementWritten) - { - _partEditor.XmlWriter.WriteEndElement(); - _partEditor.XmlWriter.WriteEndDocument(); - } - } - } - - ((INode)this).Flush(); - _partEditor.Close(); - - - _partEditor = null; - _metroPart = null; - - _parentNode = null; - - _thumbnail = null; - - _pageCache = null; - - _pagesWritten = 0; - - _hasParsedPages = false; - } - - base.CommitInternal(); - } - - - - /// - /// Adds itself and and its reationship if it exists - /// Adds dependent part Uris to the passed list following the passed restrictions - /// dependents include pages, annotaions, properties, and signatures - /// - internal - void - CollectSelfAndDependents( - Dictionary dependentList, - List selectorList, - XpsDigSigPartAlteringRestrictions restrictions - ) - { - // - // Add this document - // - dependentList[Uri] = Uri; - - // - // Add Signature Definition if it exists - // - PackagePart signatureDefinitionPart = - CurrentXpsManager.GetSignatureDefinitionPart(Uri); - - // - // Add Signature Definitions - // - selectorList.Add( new PackageRelationshipSelector( - Uri, - PackageRelationshipSelectorType.Type, - XpsS0Markup.SignatureDefinitionRelationshipName - ) - ); - - - if( signatureDefinitionPart != null ) - { - dependentList[signatureDefinitionPart.Uri] = signatureDefinitionPart.Uri; - } - // - // Add Restricted Font relationship - // - selectorList.Add( new PackageRelationshipSelector( - Uri, - PackageRelationshipSelectorType.Type, - XpsS0Markup.RestrictedFontRelationshipType - ) - ); - // - // Add Document Structure relationship - // - selectorList.Add( new PackageRelationshipSelector( - Uri, - PackageRelationshipSelectorType.Type, - XpsS0Markup.StructureRelationshipName - ) - ); - // - // Add this documents dependants - // - CollectDependents( dependentList, selectorList, restrictions); -} - - internal - void - CollectXmlPartsAndDepenedents( - List xmlPartList - ) - { - // - // Add my self to be tested for V&E Markup - // - xmlPartList.Add(_metroPart); - UpdatePageCache(); - // - // Add all pages - // - foreach (IXpsFixedPageReader reader in _pageCache) - { - (reader as XpsFixedPageReaderWriter).CollectXmlPartsAndDepenedents(xmlPartList); - } - // - // Add DocumentStructure - // - EnsureDoucmentStructure(); - if (_documentStructure != null) - { - // - // Add my DocumentStructure to be tested for V&E Markup - // - xmlPartList.Add((_documentStructure as INode).GetPart()); - } - // - // Add Signature Definition if it exists - // - PackagePart signatureDefinitionPart = - CurrentXpsManager.GetSignatureDefinitionPart(Uri); - if (signatureDefinitionPart != null) - { - // - // Add my signatureDefinitionPart to be tested for V&E Markup - // - xmlPartList.Add(signatureDefinitionPart); - } - } - - /// - /// Adds dependent part Uris to the passed list - /// - internal - void - CollectDependents( - Dictionary dependents, - List selectorList, - XpsDigSigPartAlteringRestrictions restrictions - ) - { - UpdatePageCache(); - // - // Add all pages - // - foreach( IXpsFixedPageReader reader in _pageCache) - { - (reader as XpsFixedPageReaderWriter). - CollectSelfAndDependents( - dependents, - selectorList, - restrictions - ); - } - - // - // Add DocumentStructure - // - EnsureDoucmentStructure(); - if( _documentStructure != null ) - { - dependents[_documentStructure.Uri] = _documentStructure.Uri; - } - } - - #endregion Public methods - - #region Private methods - - private - void - AddPageToDocument( - Uri partUri, - IList linkTargetStream - ) - { - _partEditor.PrepareXmlWriter(XpsS0Markup.FixedDocument, XpsS0Markup.FixedDocumentNamespace); - XmlTextWriter xmlWriter = _partEditor.XmlWriter; - // - // Write - // - String relativePath = XpsManager.MakeRelativePath(Uri, partUri); - - xmlWriter.WriteStartElement(XpsS0Markup.PageContent); - xmlWriter.WriteAttributeString(XmlTags.Source, relativePath); - - // - // Write out link targets if necessary - // - if (linkTargetStream.Count != 0) - { - xmlWriter.WriteRaw (""); - foreach (String nameElement in linkTargetStream) - { - xmlWriter.WriteRaw (String.Format( - System.Globalization.CultureInfo.InvariantCulture, - "", - nameElement) - ); - } - xmlWriter.WriteRaw (""); - } - - xmlWriter.WriteEndElement(); - } - /// - /// This method writes the PrintTicket associated with - /// this fixed document to the Metro package. - /// - private - void - CommitPrintTicket( - ) - { - // - // Flush the PrintTicket if needed - // - if (!_isPrintTicketCommitted ) - { - if(null != _printTicket) - { - CurrentXpsManager.WritePrintTicket(this,_metroPart, _printTicket); - } - else - { - CurrentXpsManager.WriteEmptyPrintTicket(this, _metroPart); - } - _isPrintTicketCommitted = true; - } - } - - /// - /// CurrentPageCommitted signals that our current page - /// is complete and we can write out any associated link - /// targets and end the PageContent element. - /// - internal - void - CurrentPageCommitted() - { - if( _currentPage != null ) - { - //Write out the fixed page tag - AddPageToDocument(_currentPage.Uri, _linkTargetStream); - _currentPage = null; - } - } - - /// - /// Test if the page cache has been initialized and - /// updates it if necessary - /// - private - void - UpdatePageCache() - { - if( !_hasParsedPages ) - { - ParsePages(); - _hasParsedPages = true; - } - } - /// - /// This method parses the part pulling out the Page Referneces - /// and populates the _pageCache - /// - private - void - ParsePages() - { - Stream stream = _metroPart.GetStream(FileMode.Open); - // - // If the stream is empty there are no pages to parse - // - if( stream.Length > 0 ) - { - XmlTextReader reader = new XmlTextReader(stream); - - while( reader.Read() ) - { - if( reader.NodeType == XmlNodeType.Element && reader.Name == XpsS0Markup.PageContent) - { - string attribute = reader.GetAttribute(XmlTags.Source); - if( attribute != null ) - { - Uri relativeUri = new Uri(attribute, UriKind.Relative); - AddPageToCache(PackUriHelper.ResolvePartUri( Uri, relativeUri)); - } - } - } - } - } - - private - IXpsFixedPageReader - AddPageToCache( Uri pageUri ) - { - PackagePart pagePart = CurrentXpsManager.GetPart(pageUri); - - if (pagePart == null) - { - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PartNotFound)); - } - - if (!pagePart.ValidatedContentType().AreTypeAndSubTypeEqual(XpsS0Markup.FixedPageContentType)) - { - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_NotAFixedPage)); - } - - // - // Create the reader/writer for the part - // - IXpsFixedPageReader pageReader = new XpsFixedPageReaderWriter(CurrentXpsManager, this, pagePart, null, _pageCache.Count+1); - - // - // Cache the new reader/writer for later - // - _pageCache.Add(pageReader ); - - return pageReader; - } - - private - void - EnsureThumbnail() - { - if( _thumbnail == null ) - { - _thumbnail = CurrentXpsManager.EnsureThumbnail( this, _metroPart ); - } - } - - - private - void - EnsureSignatureDefinitions() - { - // if _xpsSignaturs is not null we have already initialized this - // - if( null != _signatureDefinitions) - { - return; - } - _signatureDefinitions = new Collection(); - PackagePart sigDefPart = - CurrentXpsManager.GetSignatureDefinitionPart(Uri); - if( sigDefPart != null ) - { - ParseSignaturePart( sigDefPart, _signatureDefinitions ); - } - } - - private - void - EnsureDoucmentStructure() - { - // if _xpsSignaturs is not null we have already initialized this - // - if( null != _documentStructure) - { - return; - } - PackageRelationship documentStructureRelationship = null; - PackagePart documentStructurePart = null; - - foreach (PackageRelationship rel in _metroPart.GetRelationshipsByType(XpsS0Markup.StructureRelationshipName)) - { - if (documentStructureRelationship != null) - { - throw new InvalidDataException(SR.Get(SRID.ReachPackaging_MoreThanOneDocStructure)); - } - - documentStructureRelationship = rel; - } - - if (documentStructureRelationship != null) - { - Uri documentStructureUri = PackUriHelper.ResolvePartUri(documentStructureRelationship.SourceUri, - documentStructureRelationship.TargetUri); - - if (CurrentXpsManager.MetroPackage.PartExists(documentStructureUri)) - { - documentStructurePart = CurrentXpsManager.MetroPackage.GetPart(documentStructureUri); - _documentStructure = new XpsStructure(CurrentXpsManager, this, documentStructurePart); - } - } - } - private - void - ParseSignaturePart( - PackagePart sigDefPart, - Collection sigDefCollection - ) - { - XmlTextReader reader = new XmlTextReader( sigDefPart.GetStream(FileMode.Open) ); - while( reader.Read() ) - { - if( reader.NodeType == XmlNodeType.Element && - reader.Name == XpsS0Markup.SignatureDefinitions - ) - { - ParseSignatureDefinitions( reader, sigDefCollection ); - } - } - } - - private - void - ParseSignatureDefinitions( - XmlReader reader, - Collection sigDefCollection - ) - { - bool endLoop = false; - while (!endLoop && reader.Read()) - { - if( reader.NodeType == XmlNodeType.Element && - reader.Name == XpsS0Markup.SignatureDefinition - ) - { - XpsSignatureDefinition sigDef = new XpsSignatureDefinition(); - sigDef.ReadXML(reader); - sigDefCollection.Add( sigDef ); - } - - if( reader.NodeType == XmlNodeType.EndElement && - reader.Name == XpsS0Markup.SignatureDefinitions - ) - { - endLoop = true; - } - } - } - - private - void - WriteSignatureDefinitions() - { - PackagePart sigDefPart = - CurrentXpsManager.GetSignatureDefinitionPart(Uri); - if( sigDefPart == null ) - { - sigDefPart = CurrentXpsManager.AddSignatureDefinitionPart( _metroPart ); - } - Stream stream = sigDefPart.GetStream(FileMode.Create); - XmlTextWriter writer = new XmlTextWriter( - stream, - System.Text.Encoding.UTF8 - ); - writer.WriteStartDocument(); - writer.WriteStartElement( XpsS0Markup.SignatureDefinitions, - XpsS0Markup.SignatureDefinitionNamespace); - foreach( XpsSignatureDefinition sigDef in _signatureDefinitions ) - { - sigDef.WriteXML( writer ); - } - writer.WriteEndElement(); - writer.Close(); - stream.Close(); - _sigCollectionDirty = false; - } - - - - #endregion Private methods - - #region Private data - - private PackagePart _metroPart; - private PrintTicket _printTicket; - - private XmlPartEditor _partEditor; - - private IList _linkTargetStream; - - private List _pageCache; - - // - // This variable is used to keep a count of pages written via AddFixedPage - // - private int _pagesWritten; - - // - // This variable flags whether the PrintTicket property is - // committed. A writer can only commit this property once. - // - private bool _isPrintTicketCommitted; - - // - // These variables are used to keep track of the parent - // and current child of this node for walking up and - // down the current tree. This is used be the flushing - // policy to do interleave flushing of parts correctly. - // - private INode _parentNode; - - // - // This variable flags wehter the pageCashe - // has been populated by parsing the part for dependent pages - // - private bool _hasParsedPages; - - // - // 0 based document number in the document sequence - // - private int _documentNumber; - - private XpsThumbnail _thumbnail; - - - // - // Since the current page may add link target information - // we must track our current page - // for this reason we can not handle adding new page - // until the the current page has been committed - // - private XpsFixedPageReaderWriter _currentPage; - // - // A cached list of Signature Definitions - // - private Collection _signatureDefinitions; - // - // Boolean indicating whetehr _signatureDefinitions collection - // has been changed - // - private bool _sigCollectionDirty; - - private XpsStructure _documentStructure; - #endregion Private data - - #region Internal properties - - /// - /// Gets a reference to the XmlWriter for the Metro part - /// that represents this fixed document. - /// - internal System.Xml.XmlWriter XmlWriter - { - get - { - _partEditor.DoesWriteStartEndTags = false; - return _partEditor.XmlWriter; - } - } - - #endregion Internal properties - - #region INode implementation - - void - INode.Flush( - ) - { - // - // Commit the PrintTicket (if necessary) - // - CommitPrintTicket(); - - // - // Create the relationship between the package and the tumbnail - // - if( _thumbnail != null ) - { - _thumbnail = null; - } - } - - void - INode.CommitInternal( - ) - { - CommitInternal(); - } - - PackagePart - INode.GetPart( - ) - { - return _metroPart; - } - - #endregion INode implementation - - #region IDisposable implementation - - void - IDisposable.Dispose( - ) - { - CommitInternal(); - } - - #endregion IDisposable implementation - } -} - +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*++ + + + + Abstract: + This file contains the definition for IXpsFixedDocumentReader + and IXpsFixedDocumentWriter interfaces as well as definition + and implementation of XpsFixedDocumentReaderWriter. These + interfaces and class are used for writing fixed document + parts to a Xps package. + + + +--*/ +using System; +using System.Collections; +using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.IO; +using System.IO.Packaging; +using System.Xml; +using System.Security.Cryptography.X509Certificates; +using System.Printing; + +using MS.Internal.IO.Packaging.Extensions; +using PackageRelationship = System.IO.Packaging.PackageRelationship; +using PackUriHelper = System.IO.Packaging.PackUriHelper; + +namespace System.Windows.Xps.Packaging +{ + #region IXpsFixedDocumentReader interface definition + + /// + /// Interface for reading fixed document parts from the Xps package. + /// + /// Interface will be internal until reading/de-serialization is implemented. + public interface IXpsFixedDocumentReader: + IDocumentStructureProvider + { + #region Public methods + + /// + /// This method retrieves a fixed page part from a Xps + /// package using the given URI. + /// + /// + /// The URI of the fixed page to retrieve. + /// + /// + /// Returns an interface to an IXpsFixedPageReader to read + /// the requested fixed page. + /// + IXpsFixedPageReader + GetFixedPage( + Uri pageSource + ); + + #endregion Public methods + + #region Public properties + + /// + /// Gets the PrintTicket associated with this fixed document. + /// + /// Value can be a PrintTicket or null. + /// PrintTicket has already been committed. + /// Property is not a valid PrintTicket instance. + PrintTicket PrintTicket { get; } + + /// + /// Gets the URI assigned to this fixed document part. + /// + /// Value is a URI for the Metro part. + Uri Uri { get; } + + /// + /// Gets a collection of all fixed pages that are contained within + /// this fixed document. + /// + /// + /// Value is a Collection containing IXpsFixedPageReader interfaces. + /// + ReadOnlyCollection FixedPages { get; } + + /// + /// 0 based document number in the document sequence + /// + int DocumentNumber{ get; } + + /// + /// A list of signature definitions associated with the document + /// + ICollection + SignatureDefinitions{ get; } + + /// + /// thumbnail image associated with this doucment + /// + XpsThumbnail + Thumbnail{ get; } + + /// + /// Document Structure associated with this doucment + /// + XpsStructure + DocumentStructure{ get; } + + + #endregion Public properties + + /// + /// Adds the passed Signature Definiton to the cached + /// Signature Definition list + /// Will not be written until FlushSignatureDefinition + /// is called + /// + /// + /// the Signature Definition to be added + /// + void + AddSignatureDefinition( + XpsSignatureDefinition signatureDefinition + ); + + /// + /// This method removes a signature definitions associated with + /// the FixedDocument + /// + /// + /// Signature Definition to remove + /// + void + RemoveSignatureDefinition(XpsSignatureDefinition signatureDefinition); + + /// + /// Writes out all modifications to Signature + /// Definitions as well as new Signature Definitions + /// + void + CommitSignatureDefinition(); + } + + #endregion IXpsFixedDocumentReader interface definition + + #region IXpsFixedDocumentWriter interface definition + + /// + /// Interface for writing fixed document parts to the xps package. + /// + public interface IXpsFixedDocumentWriter: + IDocumentStructureProvider + { + #region Public methods + + /// + /// This method adds a fixed page part to the Xps package + /// and associates it with the current fixed document. + /// + /// + /// Returns an interface to the newly created fixed page. + /// + /// The FixedDocument has already been disposed + /// FixedPage is not completed. + IXpsFixedPageWriter + AddFixedPage( + ); + + /// + /// This method adds a thumbnail to the current DocumentSequence. + /// + /// There can only be one thumbnail attached to the DocumentSequence. + /// Calling this method when there + /// is already a starting part causes InvalidOperationException to be + /// thrown. + /// + /// Returns a XpsThumbnail instance. + XpsThumbnail + AddThumbnail( + XpsImageType imageType + ); + + /// + /// This method commits any changes not already committed for this + /// fixed document. + /// + /// + void + Commit( + ); + + #endregion Public methods + + #region Public properties + + /// + /// Sets the PrintTicket associated with this fixed document. + /// + /// + /// The value must be a valid PrintTicket instance. + /// + /// Note: The PrintTicket can only be assigned to prior to if being + /// committed to the package. The commit happens when a valid PrintTicket + /// is set and a subsequent flush on the document occurs. + /// + /// PrintTicket has already been committed. + /// Property is not a valid PrintTicket instance. + PrintTicket PrintTicket { set; } + + /// + /// Gets the URI assigned to this fixed document part. + /// + /// Value is a URI for the Metro part. + Uri Uri { get; } + + /// + /// 0 based document number in the document sequence + /// + int DocumentNumber{ get; } + + #endregion Public properties + } + + #endregion IXpsFixedDocumentWriter interface definition + + /// + /// This class implements the reading and writing functionality for + /// a fixed document within a Xps package. + /// + internal sealed class XpsFixedDocumentReaderWriter : XpsPartBase, + IXpsFixedDocumentReader, + IXpsFixedDocumentWriter, + INode, + IDisposable + { + #region Constructors + + /// + /// Internal constructor for the XpsFixedDocumentReaderWriter class. + /// This class is created internally so we can keep the Xps hierarchy + /// completely under control when using these APIs. + /// + /// + /// The XpsManager for the current Xps package. + /// + /// + /// The parent node of this fixed document. + /// + /// + /// The internal Metro part that represents this fixed document. + /// + /// + /// The 0 base document number in the document sequence + /// + /// part is null. + internal + XpsFixedDocumentReaderWriter( + XpsManager xpsManager, + INode parent, + PackagePart part, + int documentNumber + ) + : base(xpsManager) + { + if (null == part) + { + throw new ArgumentNullException("part"); + } + + this.Uri = part.Uri; + + _metroPart = part; + + _partEditor = new XmlPartEditor(_metroPart); + + _pageCache = new List(); + + _pagesWritten = 0; + + _parentNode = parent; + + _hasParsedPages = false; + + _documentNumber = documentNumber; + } + + #endregion Constructors + + #region Public properties + + /// + /// Gets or sets the PrintTicket to be stored with this fixed document. + /// + /// NOTE: The PrintTicket can only be assigned to prior to if being + /// committed to the package. The commit happens when a valid PrintTicket + /// is set and a subsequent flush on the document occurs. + /// + /// Value can be a PrintTicket or null. + /// PrintTicket has already been committed. + /// Property is not a valid PrintTicket instance. + public PrintTicket PrintTicket + { + get + { + if( _printTicket == null ) + { + _printTicket = CurrentXpsManager.EnsurePrintTicket( Uri ); + } + return _printTicket; + } + set + { + if(value != null) + { + if (_isPrintTicketCommitted) + { + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PrintTicketAlreadyCommitted)); + } + if (!value.GetType().Equals(typeof(PrintTicket))) + { + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_NotAPrintTicket)); + } + + _printTicket = value.Clone(); + } + else + { + _printTicket = null; + } + } + } + + /// + /// Gets a collection of all fixed pages that are contained within + /// this fixed document. + /// + /// + /// Value is a Collection containing IXpsFixedPageReader interfaces. + /// + /// + /// This is only internal until it is implemented. + /// + public ReadOnlyCollection FixedPages + { + get + { + UpdatePageCache(); + return new ReadOnlyCollection(_pageCache ); + } + } + + /// + /// A list of signature definitions associated with the document + /// + public + ICollection + SignatureDefinitions + { + get + { + EnsureSignatureDefinitions(); + return _signatureDefinitions; + } + } + + /// + /// 0 based document number in the document sequence + /// + public int DocumentNumber + { + get + { + return _documentNumber; + } + } + + public + XpsThumbnail + Thumbnail + { + get + { + EnsureThumbnail(); + return _thumbnail; + } + } + + public + XpsStructure + DocumentStructure + { + get + { + EnsureDoucmentStructure(); + return _documentStructure; + } + } + + #endregion Public properties + + #region Public methods + /// + /// This method adds a fixed page part to the Xps package + /// and associates it with the current fixed document. + /// + /// + /// Returns an interface to the newly created fixed page. + /// + /// The FixedDocument has already been disposed + /// FixedPage is not completed. + public + IXpsFixedPageWriter + AddFixedPage( + ) + { + if (null == _metroPart || null == CurrentXpsManager.MetroPackage) + { + throw new ObjectDisposedException("XpsFixedDocumentReaderWriter"); + } + + // + // Only one page can be created/written at a time. + // + if (null != _currentPage) + { + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PanelOrSequenceAlreadyOpen)); + } + + + _linkTargetStream = new List(); + + // + // Create the part and writer + // + PackagePart metroPart = this.CurrentXpsManager.GenerateUniquePart(XpsS0Markup.FixedPageContentType); + XpsFixedPageReaderWriter fixedPage = new XpsFixedPageReaderWriter(CurrentXpsManager, this, metroPart, _linkTargetStream, _pagesWritten + 1); + + // + // Make the new page the current page + // + _currentPage = fixedPage; + + + //Here we used to add the fixed page to _pageCache, but _pageCache is never accessed if this object was created as an IXpsFixedDocumentWriter. + //So instead keep a separate pagesWritten count and forget about the cache when using this method. + _pagesWritten++; + return fixedPage; + } + + /// + /// This method adds a thumbnail to the current DocumentSequence. + /// + /// There can only be one thumbnail attached to the DocumentSequence. + /// Calling this method when there + /// is already a starting part causes InvalidOperationException to be + /// thrown. + /// + /// Returns a XpsThumbnail instance. + public + XpsThumbnail + AddThumbnail( + XpsImageType imageType + ) + { + _thumbnail = CurrentXpsManager.AddThumbnail( imageType, this, Thumbnail ); + _metroPart.CreateRelationship( _thumbnail.Uri, + TargetMode.Internal, + XpsS0Markup.ThumbnailRelationshipName + ); + return _thumbnail; + } + + /// + /// + public + XpsStructure + AddDocumentStructure( + ) + { + if (this.DocumentStructure != null) + { + // Document structure already available for this FixedDocument + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_MoreThanOneDocStructure)); + } + + Uri pageUri = this.CurrentXpsManager.CreateStructureUri(); + // + // Create the part and writer + // + PackagePart metroPart = this.CurrentXpsManager.GeneratePart( + XpsS0Markup.DocumentStructureContentType, + pageUri); + + _documentStructure = new XpsStructure(CurrentXpsManager, this, metroPart); + + // + // Create the relationship between the document and the document-structure + // Not in INode.Flush because IXpsFixedDocumentReader has no commit. + // + + string structurePath = XpsManager.MakeRelativePath(this.Uri, _documentStructure.Uri); + + _metroPart.CreateRelationship(new Uri(structurePath, UriKind.Relative), + TargetMode.Internal, + XpsS0Markup.StructureRelationshipName + ); + + return _documentStructure; + } + + /// + /// This method adds a relationship for this part that + /// targets the specified Uri and is based on the specified + /// content type. + /// + /// + /// Uri target for relationship. + /// + /// + /// Relationship type to add. + /// + public + void + AddRelationship( + Uri targetUri, + string relationshipName + ) + { + // + // We can not read from the file to do validation + // when streaming + // + if( !CurrentXpsManager.Streaming ) + { + foreach (PackageRelationship rel in _metroPart.GetRelationships()) + { + if (rel.TargetUri.Equals(targetUri)) + { + // + // Relationship already exists + // + return; + } + } + } + + // + // Add the relationship using a relative path to this page. + // + string relativePath = XpsManager.MakeRelativePath(this.Uri, targetUri); + _metroPart.CreateRelationship(new Uri(relativePath, UriKind.Relative), + TargetMode.Internal, + relationshipName); + } + + /// + /// This method retrieves a fixed page part from a Xps + /// package using the given URI. + /// + /// + /// The URI of the fixed page to retrieve. + /// + /// + /// Returns an interface to an IXpsFixedPageReader to read + /// the requested fixed page. + /// + public + IXpsFixedPageReader + GetFixedPage( + Uri pageUri + ) + { + UpdatePageCache(); + IXpsFixedPageReader pageReader = null; + + foreach (IXpsFixedPageReader reader in _pageCache ) + { + if( reader.Uri == pageUri ) + { + pageReader = reader; + } + } + + return pageReader; + } + + public + void + AddSignatureDefinition( + XpsSignatureDefinition signatureDefinition + ) + { + EnsureSignatureDefinitions(); + _signatureDefinitions.Add( signatureDefinition ); + _sigCollectionDirty = true; +} + + public + void + CommitSignatureDefinition() + { + bool isDirty = false; + + // + // if the collection is dirty not point in testing + // each signature. + // + if( !_sigCollectionDirty ) + { + foreach( XpsSignatureDefinition sigDef in _signatureDefinitions ) + { + if( sigDef.HasBeenModified ) + { + isDirty = true; + break; + } + } + } + if( isDirty || _sigCollectionDirty ) + { + WriteSignatureDefinitions(); + } + } + + /// + /// This method removes a signature definitions associated with + /// the FixedDocument + /// + /// + /// Signature Definition to remove + /// + public + void + RemoveSignatureDefinition(XpsSignatureDefinition signatureDefinition) + { + EnsureSignatureDefinitions(); + _signatureDefinitions.Remove( signatureDefinition ); + _sigCollectionDirty = true; +} + + /// + /// This method commits any changes not already committed for this + /// fixed document. + /// + /// NOTE: This commits changes to all child object under this + /// branch of the tree. No further changes will be allowed. + /// + public + void + Commit( + ) + { + CommitInternal(); + } + + /// + /// This method closes streams and frees memory for this + /// fixed document. + /// + internal + override + void + CommitInternal( + ) + { + CommitPrintTicket(); + if (null != _partEditor) + { + if (null != _partEditor.XmlWriter) + { + if(_partEditor.DoesWriteStartEndTags) + { + if(_partEditor.IsStartElementWritten) + { + _partEditor.XmlWriter.WriteEndElement(); + _partEditor.XmlWriter.WriteEndDocument(); + } + } + } + + ((INode)this).Flush(); + _partEditor.Close(); + + + _partEditor = null; + _metroPart = null; + + _parentNode = null; + + _thumbnail = null; + + _pageCache = null; + + _pagesWritten = 0; + + _hasParsedPages = false; + } + + base.CommitInternal(); + } + + + + /// + /// Adds itself and and its reationship if it exists + /// Adds dependent part Uris to the passed list following the passed restrictions + /// dependents include pages, annotaions, properties, and signatures + /// + internal + void + CollectSelfAndDependents( + Dictionary dependentList, + List selectorList, + XpsDigSigPartAlteringRestrictions restrictions + ) + { + // + // Add this document + // + dependentList[Uri] = Uri; + + // + // Add Signature Definition if it exists + // + PackagePart signatureDefinitionPart = + CurrentXpsManager.GetSignatureDefinitionPart(Uri); + + // + // Add Signature Definitions + // + selectorList.Add( new PackageRelationshipSelector( + Uri, + PackageRelationshipSelectorType.Type, + XpsS0Markup.SignatureDefinitionRelationshipName + ) + ); + + + if( signatureDefinitionPart != null ) + { + dependentList[signatureDefinitionPart.Uri] = signatureDefinitionPart.Uri; + } + // + // Add Restricted Font relationship + // + selectorList.Add( new PackageRelationshipSelector( + Uri, + PackageRelationshipSelectorType.Type, + XpsS0Markup.RestrictedFontRelationshipType + ) + ); + // + // Add Document Structure relationship + // + selectorList.Add( new PackageRelationshipSelector( + Uri, + PackageRelationshipSelectorType.Type, + XpsS0Markup.StructureRelationshipName + ) + ); + // + // Add this documents dependants + // + CollectDependents( dependentList, selectorList, restrictions); +} + + internal + void + CollectXmlPartsAndDepenedents( + List xmlPartList + ) + { + // + // Add my self to be tested for V&E Markup + // + xmlPartList.Add(_metroPart); + UpdatePageCache(); + // + // Add all pages + // + foreach (IXpsFixedPageReader reader in _pageCache) + { + (reader as XpsFixedPageReaderWriter).CollectXmlPartsAndDepenedents(xmlPartList); + } + // + // Add DocumentStructure + // + EnsureDoucmentStructure(); + if (_documentStructure != null) + { + // + // Add my DocumentStructure to be tested for V&E Markup + // + xmlPartList.Add((_documentStructure as INode).GetPart()); + } + // + // Add Signature Definition if it exists + // + PackagePart signatureDefinitionPart = + CurrentXpsManager.GetSignatureDefinitionPart(Uri); + if (signatureDefinitionPart != null) + { + // + // Add my signatureDefinitionPart to be tested for V&E Markup + // + xmlPartList.Add(signatureDefinitionPart); + } + } + + /// + /// Adds dependent part Uris to the passed list + /// + internal + void + CollectDependents( + Dictionary dependents, + List selectorList, + XpsDigSigPartAlteringRestrictions restrictions + ) + { + UpdatePageCache(); + // + // Add all pages + // + foreach( IXpsFixedPageReader reader in _pageCache) + { + (reader as XpsFixedPageReaderWriter). + CollectSelfAndDependents( + dependents, + selectorList, + restrictions + ); + } + + // + // Add DocumentStructure + // + EnsureDoucmentStructure(); + if( _documentStructure != null ) + { + dependents[_documentStructure.Uri] = _documentStructure.Uri; + } + } + + #endregion Public methods + + #region Private methods + + private + void + AddPageToDocument( + Uri partUri, + IList linkTargetStream + ) + { + _partEditor.PrepareXmlWriter(XpsS0Markup.FixedDocument, XpsS0Markup.FixedDocumentNamespace); + XmlTextWriter xmlWriter = _partEditor.XmlWriter; + // + // Write + // + String relativePath = XpsManager.MakeRelativePath(Uri, partUri); + + xmlWriter.WriteStartElement(XpsS0Markup.PageContent); + xmlWriter.WriteAttributeString(XmlTags.Source, relativePath); + + // + // Write out link targets if necessary + // + if (linkTargetStream.Count != 0) + { + xmlWriter.WriteRaw (""); + foreach (String nameElement in linkTargetStream) + { + xmlWriter.WriteRaw (String.Format( + System.Globalization.CultureInfo.InvariantCulture, + "", + nameElement) + ); + } + xmlWriter.WriteRaw (""); + } + + xmlWriter.WriteEndElement(); + } + /// + /// This method writes the PrintTicket associated with + /// this fixed document to the Metro package. + /// + private + void + CommitPrintTicket( + ) + { + // + // Flush the PrintTicket if needed + // + if (!_isPrintTicketCommitted ) + { + if(null != _printTicket) + { + CurrentXpsManager.WritePrintTicket(this,_metroPart, _printTicket); + } + else + { + CurrentXpsManager.WriteEmptyPrintTicket(this, _metroPart); + } + _isPrintTicketCommitted = true; + } + } + + /// + /// CurrentPageCommitted signals that our current page + /// is complete and we can write out any associated link + /// targets and end the PageContent element. + /// + internal + void + CurrentPageCommitted() + { + if( _currentPage != null ) + { + //Write out the fixed page tag + AddPageToDocument(_currentPage.Uri, _linkTargetStream); + _currentPage = null; + } + } + + /// + /// Test if the page cache has been initialized and + /// updates it if necessary + /// + private + void + UpdatePageCache() + { + if( !_hasParsedPages ) + { + ParsePages(); + _hasParsedPages = true; + } + } + /// + /// This method parses the part pulling out the Page Referneces + /// and populates the _pageCache + /// + private + void + ParsePages() + { + using (Stream stream = _metroPart.GetStream(FileMode.Open)) + { + // + // If the stream is empty there are no pages to parse + // + if (stream.Length > 0) + { + XmlTextReader reader = new XmlTextReader(stream); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element && reader.Name == XpsS0Markup.PageContent) + { + string attribute = reader.GetAttribute(XmlTags.Source); + if (attribute != null) + { + Uri relativeUri = new Uri(attribute, UriKind.Relative); + AddPageToCache(PackUriHelper.ResolvePartUri(Uri, relativeUri)); + } + } + } + } + } + } + + private + IXpsFixedPageReader + AddPageToCache( Uri pageUri ) + { + PackagePart pagePart = CurrentXpsManager.GetPart(pageUri); + + if (pagePart == null) + { + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PartNotFound)); + } + + if (!pagePart.ValidatedContentType().AreTypeAndSubTypeEqual(XpsS0Markup.FixedPageContentType)) + { + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_NotAFixedPage)); + } + + // + // Create the reader/writer for the part + // + IXpsFixedPageReader pageReader = new XpsFixedPageReaderWriter(CurrentXpsManager, this, pagePart, null, _pageCache.Count+1); + + // + // Cache the new reader/writer for later + // + _pageCache.Add(pageReader ); + + return pageReader; + } + + private + void + EnsureThumbnail() + { + if( _thumbnail == null ) + { + _thumbnail = CurrentXpsManager.EnsureThumbnail( this, _metroPart ); + } + } + + + private + void + EnsureSignatureDefinitions() + { + // if _xpsSignaturs is not null we have already initialized this + // + if( null != _signatureDefinitions) + { + return; + } + _signatureDefinitions = new Collection(); + PackagePart sigDefPart = + CurrentXpsManager.GetSignatureDefinitionPart(Uri); + if( sigDefPart != null ) + { + ParseSignaturePart( sigDefPart, _signatureDefinitions ); + } + } + + private + void + EnsureDoucmentStructure() + { + // if _xpsSignaturs is not null we have already initialized this + // + if( null != _documentStructure) + { + return; + } + PackageRelationship documentStructureRelationship = null; + PackagePart documentStructurePart = null; + + foreach (PackageRelationship rel in _metroPart.GetRelationshipsByType(XpsS0Markup.StructureRelationshipName)) + { + if (documentStructureRelationship != null) + { + throw new InvalidDataException(SR.Get(SRID.ReachPackaging_MoreThanOneDocStructure)); + } + + documentStructureRelationship = rel; + } + + if (documentStructureRelationship != null) + { + Uri documentStructureUri = PackUriHelper.ResolvePartUri(documentStructureRelationship.SourceUri, + documentStructureRelationship.TargetUri); + + if (CurrentXpsManager.MetroPackage.PartExists(documentStructureUri)) + { + documentStructurePart = CurrentXpsManager.MetroPackage.GetPart(documentStructureUri); + _documentStructure = new XpsStructure(CurrentXpsManager, this, documentStructurePart); + } + } + } + private + void + ParseSignaturePart( + PackagePart sigDefPart, + Collection sigDefCollection + ) + { + using (XmlTextReader reader = new XmlTextReader(sigDefPart.GetStream(FileMode.Open))) + { + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element && + reader.Name == XpsS0Markup.SignatureDefinitions + ) + { + ParseSignatureDefinitions(reader, sigDefCollection); + } + } + } + } + + private + void + ParseSignatureDefinitions( + XmlReader reader, + Collection sigDefCollection + ) + { + bool endLoop = false; + while (!endLoop && reader.Read()) + { + if( reader.NodeType == XmlNodeType.Element && + reader.Name == XpsS0Markup.SignatureDefinition + ) + { + XpsSignatureDefinition sigDef = new XpsSignatureDefinition(); + sigDef.ReadXML(reader); + sigDefCollection.Add( sigDef ); + } + + if( reader.NodeType == XmlNodeType.EndElement && + reader.Name == XpsS0Markup.SignatureDefinitions + ) + { + endLoop = true; + } + } + } + + private + void + WriteSignatureDefinitions() + { + PackagePart sigDefPart = + CurrentXpsManager.GetSignatureDefinitionPart(Uri); + if( sigDefPart == null ) + { + sigDefPart = CurrentXpsManager.AddSignatureDefinitionPart( _metroPart ); + } + Stream stream = sigDefPart.GetStream(FileMode.Create); + XmlTextWriter writer = new XmlTextWriter( + stream, + System.Text.Encoding.UTF8 + ); + writer.WriteStartDocument(); + writer.WriteStartElement( XpsS0Markup.SignatureDefinitions, + XpsS0Markup.SignatureDefinitionNamespace); + foreach( XpsSignatureDefinition sigDef in _signatureDefinitions ) + { + sigDef.WriteXML( writer ); + } + writer.WriteEndElement(); + writer.Close(); + stream.Close(); + _sigCollectionDirty = false; + } + + + + #endregion Private methods + + #region Private data + + private PackagePart _metroPart; + private PrintTicket _printTicket; + + private XmlPartEditor _partEditor; + + private IList _linkTargetStream; + + private List _pageCache; + + // + // This variable is used to keep a count of pages written via AddFixedPage + // + private int _pagesWritten; + + // + // This variable flags whether the PrintTicket property is + // committed. A writer can only commit this property once. + // + private bool _isPrintTicketCommitted; + + // + // These variables are used to keep track of the parent + // and current child of this node for walking up and + // down the current tree. This is used be the flushing + // policy to do interleave flushing of parts correctly. + // + private INode _parentNode; + + // + // This variable flags wehter the pageCashe + // has been populated by parsing the part for dependent pages + // + private bool _hasParsedPages; + + // + // 0 based document number in the document sequence + // + private int _documentNumber; + + private XpsThumbnail _thumbnail; + + + // + // Since the current page may add link target information + // we must track our current page + // for this reason we can not handle adding new page + // until the the current page has been committed + // + private XpsFixedPageReaderWriter _currentPage; + // + // A cached list of Signature Definitions + // + private Collection _signatureDefinitions; + // + // Boolean indicating whetehr _signatureDefinitions collection + // has been changed + // + private bool _sigCollectionDirty; + + private XpsStructure _documentStructure; + #endregion Private data + + #region Internal properties + + /// + /// Gets a reference to the XmlWriter for the Metro part + /// that represents this fixed document. + /// + internal System.Xml.XmlWriter XmlWriter + { + get + { + _partEditor.DoesWriteStartEndTags = false; + return _partEditor.XmlWriter; + } + } + + #endregion Internal properties + + #region INode implementation + + void + INode.Flush( + ) + { + // + // Commit the PrintTicket (if necessary) + // + CommitPrintTicket(); + + // + // Create the relationship between the package and the tumbnail + // + if( _thumbnail != null ) + { + _thumbnail = null; + } + } + + void + INode.CommitInternal( + ) + { + CommitInternal(); + } + + PackagePart + INode.GetPart( + ) + { + return _metroPart; + } + + #endregion INode implementation + + #region IDisposable implementation + + void + IDisposable.Dispose( + ) + { + CommitInternal(); + } + + #endregion IDisposable implementation + } +} + diff --git a/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentSequenceReaderWriter.cs b/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentSequenceReaderWriter.cs index 272c2738042..0786fe03150 100644 --- a/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentSequenceReaderWriter.cs +++ b/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentSequenceReaderWriter.cs @@ -546,28 +546,31 @@ Uri partUri void ParseDocuments() { - Stream stream = _metroPart.GetStream(FileMode.Open); - // - // If the stream is empty there are no documents to parse - // - if( stream.Length > 0 ) - { - XmlTextReader reader = new XmlTextReader(stream); - - while( reader.Read() ) - { - if( reader.NodeType == XmlNodeType.Element && reader.Name == XpsS0Markup.DocumentReference) - { - string attribute = reader.GetAttribute(XmlTags.Source); - if( attribute != null ) - { - Uri relativeUri = new Uri(attribute, UriKind.Relative); - //This routine properly adds DocumentReaderWriter to the _documentCache - AddDocumentToCache(PackUriHelper.ResolvePartUri( Uri, relativeUri)); - } - } - } - } + using (Stream stream = _metroPart.GetStream(FileMode.Open)) + { + // + // If the stream is empty there are no documents to parse + // + if (stream.Length > 0) + { + XmlTextReader reader = new XmlTextReader(stream); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element && reader.Name == XpsS0Markup.DocumentReference) + { + string attribute = reader.GetAttribute(XmlTags.Source); + if (attribute != null) + { + Uri relativeUri = new Uri(attribute, UriKind.Relative); + //This routine properly adds DocumentReaderWriter to the _documentCache + AddDocumentToCache(PackUriHelper.ResolvePartUri(Uri, relativeUri)); + } + } + } + } + } + } /// diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/IO/Packaging/XmlDigitalSignatureProcessor.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/IO/Packaging/XmlDigitalSignatureProcessor.cs index 55d616dcca9..1870fbb4cef 100644 --- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/IO/Packaging/XmlDigitalSignatureProcessor.cs +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/IO/Packaging/XmlDigitalSignatureProcessor.cs @@ -725,7 +725,10 @@ private PackageDigitalSignature Sign( IEnumerable objectReferences) { // don't overwrite - Debug.Assert(SignaturePart.GetStream().Length == 0, "Logic Error: Can't sign when signature already exists"); + using (Stream stream = SignaturePart.GetStream()) + { + Debug.Assert(stream.Length == 0, "Logic Error: Can't sign when signature already exists"); + } // grab hash algorithm as this may change in the future _hashAlgorithmName = _manager.HashAlgorithm; From a583fe9a20b4beb7ddb872d03b43be3aef5bc873 Mon Sep 17 00:00:00 2001 From: Steven Kirbach Date: Thu, 18 Jul 2019 10:36:37 -0700 Subject: [PATCH 2/3] responding to PR feedback --- .../Packaging/XpsFixedDocumentReaderWriter.cs | 25 +- .../Packaging/XmlDigitalSignatureProcessor.cs | 2486 +++++++++-------- 2 files changed, 1256 insertions(+), 1255 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs b/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs index d6f07267bd3..8b275f79d7e 100644 --- a/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs +++ b/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs @@ -1128,21 +1128,20 @@ Collection sigDefCollection { sigDefPart = CurrentXpsManager.AddSignatureDefinitionPart( _metroPart ); } - Stream stream = sigDefPart.GetStream(FileMode.Create); - XmlTextWriter writer = new XmlTextWriter( - stream, - System.Text.Encoding.UTF8 - ); - writer.WriteStartDocument(); - writer.WriteStartElement( XpsS0Markup.SignatureDefinitions, - XpsS0Markup.SignatureDefinitionNamespace); - foreach( XpsSignatureDefinition sigDef in _signatureDefinitions ) + + using (Stream stream = sigDefPart.GetStream(FileMode.Create)) + using (XmlTextWriter writer = new XmlTextWriter(stream, System.Text.Encoding.UTF8)) { - sigDef.WriteXML( writer ); + writer.WriteStartDocument(); + writer.WriteStartElement(XpsS0Markup.SignatureDefinitions, + XpsS0Markup.SignatureDefinitionNamespace); + foreach (XpsSignatureDefinition sigDef in _signatureDefinitions) + { + sigDef.WriteXML(writer); + } + writer.WriteEndElement(); } - writer.WriteEndElement(); - writer.Close(); - stream.Close(); + _sigCollectionDirty = false; } diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/IO/Packaging/XmlDigitalSignatureProcessor.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/IO/Packaging/XmlDigitalSignatureProcessor.cs index 1870fbb4cef..5706e3438f6 100644 --- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/IO/Packaging/XmlDigitalSignatureProcessor.cs +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/IO/Packaging/XmlDigitalSignatureProcessor.cs @@ -1,1244 +1,1246 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// -// -// Description: -// Implementation of the W3C Digital Signature Handler. -// Generates and consumes XmlDSig-compliant digital signatures based on the subset -// specified by the Opc file format. -// - - -using System; -using System.Diagnostics; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Runtime.InteropServices; -using System.Runtime.Serialization; -using System.Security; // for SecurityCritical and SecurityTreatAsSafe -using System.Security.Cryptography; -using System.Security.Cryptography.Xml; -using System.Security.Cryptography.X509Certificates; -using System.Xml; -using System.IO; -using System.Windows; -using System.IO.Packaging; -using Microsoft.Win32; -using MS.Internal; -using MS.Internal.WindowsBase; - -using MS.Internal.IO.Packaging.Extensions; - -namespace MS.Internal.IO.Packaging -{ - /// - /// Signature Handler implementation that follows the Feb 12, 2002 W3C DigSig Recommendation - /// - /// See: http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/ for details - internal class XmlDigitalSignatureProcessor - { - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - /// - /// Constructor - called from PackageDigitalSignatureManager when opening an existing signature - /// - /// current DigitalSignatureManager - /// public signature object - /// the part that will/does house the associated XML signature - internal XmlDigitalSignatureProcessor(PackageDigitalSignatureManager manager, - PackagePart signaturePart, PackageDigitalSignature packageSignature) : this(manager, signaturePart) - { - _signature = packageSignature; - } - - /// - /// Factory method that creates a new PackageDigitalSignature - /// - internal static PackageDigitalSignature Sign( - PackageDigitalSignatureManager manager, - PackagePart signaturePart, - IEnumerable parts, - IEnumerable relationshipSelectors, - X509Certificate2 signer, - String signatureId, - bool embedCertificate, - IEnumerable signatureObjects, - IEnumerable objectReferences) - { - // create - XmlDigitalSignatureProcessor p = new XmlDigitalSignatureProcessor(manager, signaturePart); - - // and sign - return p.Sign(parts, relationshipSelectors, signer, signatureId, - embedCertificate, signatureObjects, objectReferences); - } - - /// - /// Verify the given signature - /// - /// throws if no certificate found in the signature - /// true if the data stream has not been altered since it was signed - internal bool Verify() - { - return Verify(Signer); - } - - /// - /// Verify the given signature - /// - /// certificate to use (ignores any embedded cert) - /// true if the data stream has not been altered since it was signed - internal bool Verify(X509Certificate2 signer) - { - Invariant.Assert(signer != null); - - // Create a SignedXml to do the dirty work - SignedXml xmlSig = EnsureXmlSignatureParsed(); - - bool result = false; - - // Validate the Reference tags in the SignedInfo as per the - // restrictions imposed by the OPC spec - ValidateReferences(xmlSig.SignedInfo.References, true /*allowPackageSpecificReference*/); - - // verify "standard" XmlSignature portions - result = xmlSig.CheckSignature(signer, true); - - if (result) - { - HashAlgorithm hashAlgorithm = null; // optimize - generally only need to create and re-use one of these - String currentHashAlgorithmName = String.Empty; // guaranteed not to match - - try - { - try - { - // if that succeeds, verify the Manifest including Part/Relationship content and ContentTypes - ParsePackageDataObject(); - } - catch (XmlException) - { - // parsing exception - means this is a bad signature - return false; - } - - foreach (PartManifestEntry partEntry in _partEntryManifest) - { - // compare the content - Stream s = null; - - // Relationship requires special handling - if (partEntry.IsRelationshipEntry) - { - // This behaves correctely even if the Relationship Part is missing entirely - s = GetRelationshipStream(partEntry); - } - else // Part entry - inspect raw stream - { - // part is guaranteed to exist at this point because we fail early in PackageDigitalSignature.Verify() - Debug.Assert(_manager.Package.PartExists(partEntry.Uri)); - - // Compare the content type first so we can fail early if it doesn't match - // (faster than hashing the content itself). - // Compare ordinal case-sensitive which is more strict than normal ContentType - // comparision because this is manadated by the OPC specification. - PackagePart part = _manager.Package.GetPart(partEntry.Uri); - if (String.CompareOrdinal( - partEntry.ContentType.OriginalString, - part.ValidatedContentType().OriginalString) != 0) - { - result = false; // content type mismatch - break; - } - s = part.GetStream(FileMode.Open, FileAccess.Read); - } - - using (s) - { - // ensure hash algorithm object is available - re-use if possible - if (((hashAlgorithm != null) && (!hashAlgorithm.CanReuseTransform)) || - String.CompareOrdinal(partEntry.HashAlgorithm, currentHashAlgorithmName) != 0) - { - if (hashAlgorithm != null) - ((IDisposable)hashAlgorithm).Dispose(); - - currentHashAlgorithmName = partEntry.HashAlgorithm; - hashAlgorithm = GetHashAlgorithm(currentHashAlgorithmName); - - // not a supported or recognized algorithm? - if (hashAlgorithm == null) - { - // return invalid result instead of throwing exception - result = false; - break; - } - } - - // calculate hash - String base64EncodedHashValue = GenerateDigestValue(s, partEntry.Transforms, hashAlgorithm); - - // now compare the hash - must be identical - if (String.CompareOrdinal(base64EncodedHashValue, partEntry.HashValue) != 0) - { - result = false; // hash mismatch - break; - } - } - } - } - finally - { - if (hashAlgorithm != null) - ((IDisposable)hashAlgorithm).Dispose(); - } - } - - return result; - } - - /// - /// Get the list of transforms applied to the given part (works for Relationship parts too) - /// - /// part to get transforms for - /// possibly empty, ordered list of transforms applied to the given part (never null) - /// This method only returns transform names. Transform-specific properties can be obtained by parsing the actual - /// signature contents which conform to the W3C XmlDSig spec. - internal List GetPartTransformList(Uri partName) - { - // query the parsed manifest - ParsePackageDataObject(); - - List transformList = null; - - // look through the manifest for the requested part - foreach (PartManifestEntry entry in _partEntryManifest) - { - if (System.IO.Packaging.PackUriHelper.ComparePartUri(entry.Uri, partName) == 0) - { - transformList = entry.Transforms; - break; - } - } - - // never return null - an empty list is better form - if (transformList == null) - transformList = new List(); - - return transformList; - } - - //------------------------------------------------------ - // - // Internal Properties - // - //------------------------------------------------------ - - /// - /// Content type of signature parts created by this processor - /// - internal static ContentType ContentType - { - get - { - return _xmlSignaturePartType; - } - } - - - /// - /// Associated signature part - /// - internal PackagePart SignaturePart - { - get - { - return _signaturePart; - } - } - - - /// - /// Obtain the list of signed parts - /// - /// Authors identity in handler-proprietary format - /// if signature xml is malformed - internal List PartManifest - { - get - { - ParsePackageDataObject(); - return _partManifest; - } - } - - /// - /// Obtain the author's identity as a byte stream - /// - /// Authors identity in handler-proprietary format - /// if signature xml is malformed - internal List RelationshipManifest - { - get - { - ParsePackageDataObject(); - return _relationshipManifest; - } - } - - /// - /// Obtain the author's identity in X509 Certificate form - /// - /// Authors identity as a certificate in X509 form, or null if none found - internal X509Certificate2 Signer - { - get - { - // lazy init when loading existing cert - Sign may have assigned this for us - if (_certificate == null) - { - // first look for cert part - if (PackageSignature.GetCertificatePart() != null) - _certificate = PackageSignature.GetCertificatePart().GetCertificate(); - else - { - // look in signature - if (_lookForEmbeddedCert) - { - IEnumerator keyInfoClauseEnum = EnsureXmlSignatureParsed().KeyInfo.GetEnumerator(typeof(KeyInfoX509Data)); - while (keyInfoClauseEnum.MoveNext()) - { - KeyInfoX509Data x509Data = (KeyInfoX509Data)keyInfoClauseEnum.Current; - foreach (X509Certificate2 cert in x509Data.Certificates) - { - // just take the first one for now - _certificate = cert; - break; - } - - // just need one for now - if (_certificate != null) - break; - } - - // only need to do this once - _lookForEmbeddedCert = false; - } - } - } - - return _certificate; // may be null - } - } - - /// - /// encrypted hash value - /// - /// - internal byte[] SignatureValue - { - get - { - return EnsureXmlSignatureParsed().SignatureValue; - } - } - - /// - /// The object that actually creates the signature - /// Note: This API is exposed to the public API surface through the - /// PackageDigitalSignature.Signature property. Through this API it is - /// possible to create Signatures that are not OPC compliant. However, - /// at verify time, we will throw exceptions for non-conforming signatures. - /// - /// object of type System.Security.Cryptography.Xml.Signature - internal Signature Signature - { - get - { - return EnsureXmlSignatureParsed().Signature; - } - set - { - UpdatePartFromSignature(value); - } - } - - /// - /// Get the given signature - /// - internal PackageDigitalSignature PackageSignature - { - get - { - return _signature; - } - } - - /// - /// Time that the signature was created - /// - /// Time of signing if available, or DateTime.MinValue if signature was not time-stamped - internal DateTime SigningTime - { - get - { - // lazy init - ParsePackageDataObject(); - return _signingTime; - } - } - - internal String TimeFormat - { - get - { - // lazy init - ParsePackageDataObject(); - return _signingTimeFormat; - } - } - - //------------------------------------------------------ - // - // Digest Helper Functions - // - //------------------------------------------------------ - /// - /// Generate digest value tag - /// - /// - /// name of the single transform to use - may be null - /// hash algorithm to use - /// - internal static String GenerateDigestValue( - Stream s, - String transformName, - HashAlgorithm hashAlgorithm) - { - List transforms = null; - if (transformName != null) - { - transforms = new List(1); - transforms.Add(transformName); - } - return GenerateDigestValue(s, transforms, hashAlgorithm); - } - - /// - /// Generate digest value tag - /// - /// transforms to apply - may be null and list may contain empty strings - /// stream to hash - /// algorithm to use - internal static String GenerateDigestValue( - Stream s, - List transforms, - HashAlgorithm hashAlgorithm) - { - s.Seek(0, SeekOrigin.Begin); - - // We need to be able to dispose streams generated by the - // Transform object but we don't want to dispose the stream - // passed to us so we insert this to block any propagated Dispose() calls. - Stream transformStream = new IgnoreFlushAndCloseStream(s); - - List transformStreams = null; - - // canonicalize the part content if asked - if (transforms != null) - { - transformStreams = new List(transforms.Count); - transformStreams.Add(transformStream); - foreach (String transformName in transforms) - { - // ignore empty strings at this point (as well as Relationship Transforms) - these are legal - if ((transformName.Length == 0) - || (String.CompareOrdinal(transformName, XTable.Get(XTable.ID.RelationshipsTransformName)) == 0)) - { - continue; - } - - // convert the transform names into objects (if defined) - Transform transform = StringToTransform(transformName); - - if (transform == null) - { - // throw XmlException so the outer loop knows the signature is invalid - throw new XmlException(SR.Get(SRID.UnsupportedTransformAlgorithm)); - } - - transformStream = TransformXml(transform, transformStream); - transformStreams.Add(transformStream); - } - } - - // hash it and encode to Base64 - String hashValueString = System.Convert.ToBase64String(HashStream(hashAlgorithm, transformStream)); - - // dispose of any generated streams - if (transformStreams != null) - { - foreach (Stream stream in transformStreams) - stream.Close(); - } - - return hashValueString; - } - - /// - /// Synthesizes a NodeList of Reference tags to hash - /// - /// - /// - internal static Stream GenerateRelationshipNodeStream(IEnumerable relationships) - { - // create a NodeList containing valid Relationship XML and serialize it to the stream - Stream s = new MemoryStream(); - - // Wrap in a stream that ignores Flush and Close so the XmlTextWriter - // will not close it. - // use UTF-8 encoding by default - using (XmlTextWriter writer = new XmlTextWriter(new IgnoreFlushAndCloseStream(s), - System.Text.Encoding.UTF8)) - { - // start outer Relationships tag - writer.WriteStartElement(XTable.Get(XTable.ID.RelationshipsTagName), PackagingUtilities.RelationshipNamespaceUri); - - // generate a valid Relationship tag according to the Opc schema - InternalRelationshipCollection.WriteRelationshipsAsXml(writer, relationships, - true, /* systematically write target mode */ - false /* not in streaming production */ - ); - - // end of Relationships tag - writer.WriteEndElement(); - } - - return s; - } - - /// - /// Convert algorithm name to object - /// - /// fully specified name - /// HashAlgorithm object or null if it does not map to a supported hash type - /// Caller is responsible for calling Dispose() on returned object - internal static HashAlgorithm GetHashAlgorithm(String hashAlgorithmName) - { - Object o = CryptoConfig.CreateFromName(hashAlgorithmName); - HashAlgorithm algorithm = o as HashAlgorithm; - - // In the case that we get a valid crypto object that is not a hash algorithm - // we should attempt to dispose it if it offers IDisposable. - if (algorithm == null && o != null) - { - IDisposable disposable = o as IDisposable; - if (disposable != null) - disposable.Dispose(); - } - - return algorithm; - } - - /// - /// IMPORTANT NOTE: - /// 1. In the XmlDigitalSignatureProcessor.IsValidXmlCanonicalizationTransform method, - /// we have similar logic regarding these two transforms.So both these methods must be updated - /// in sync. - /// - private static Transform StringToTransform(String transformName) - { - Invariant.Assert(transformName != null); - - if (String.CompareOrdinal(transformName, SignedXml.XmlDsigC14NTransformUrl) == 0) - { - return new XmlDsigC14NTransform(); - } - else if (String.CompareOrdinal(transformName, SignedXml.XmlDsigC14NWithCommentsTransformUrl) == 0) - { - return new XmlDsigC14NWithCommentsTransform(); - } - else - return null; -} - - // As per the OPC spec, only two tranforms are valid. Also, both of these happen to be - // XML canonicalization transforms. - // In the XmlSignatureManifest.ParseTransformsTag method we make use this method to - // validate the transforms to make sure that they are supported by the OPC spec and - // we also take advantage of the fact that both of them are XML canonicalization transforms - // IMPORTANT NOTE: - // 1. In the XmlDigitalSignatureProcessor.StringToTransform method, we have similar logic - // regarding these two transforms.So both these methods must be updated in sync. - // 2. If ever this method is updated to add other transforms, careful review must be done to - // make sure that methods calling this method are updated as required. - internal static bool IsValidXmlCanonicalizationTransform(String transformName) - { - Invariant.Assert(transformName != null); - - if (String.CompareOrdinal(transformName, SignedXml.XmlDsigC14NTransformUrl) == 0 || - String.CompareOrdinal(transformName, SignedXml.XmlDsigC14NWithCommentsTransformUrl) == 0) - { - return true; - } - else - return false; - } - - //------------------------------------------------------ - // - // Private Properties - // - //------------------------------------------------------ - /// - /// Helper class - /// - private SignedXml EnsureXmlSignatureParsed() - { - // lazy init - if (_signedXml == null) - { - _signedXml = new CustomSignedXml(); - - // Load the XML - XmlDocument xmlDocument = new XmlDocument(); - xmlDocument.PreserveWhitespace = true; - using (Stream s = SignaturePart.GetStream()) - { - using (XmlTextReader xmlReader = new XmlTextReader(s)) - { - //Prohibit DTD from the markup as per the OPC spec - xmlReader.ProhibitDtd = true; - - //This method expects the reader to be in ReadState.Initial. - //It will make the first read call. - PackagingUtilities.PerformInitailReadAndVerifyEncoding(xmlReader); - - //If the reader.ReadState is ReadState.Initial, then XmlDocument with perform the - //first xmlReader.Read() and start loading from that node/tag. - //If the reader.ReadState is ReadState.Intermediate, then XmlDocument, will start - //loading from that location itself. - //Note: Since in the above method we perform only the first read and will have not - //moved the reader state further down in the markup, we should be okay, and - //xmlDocument.Load will load from the very begining as intended. - xmlDocument.Load(xmlReader); - - // W3C spec allows for Signature tag to appear as an island and inherently allows - // for multiple Signature tags within the same XML document. - // OPC restricts this to a single, root-level Signature tag. However, Signature - // tags are allowed to exist within the non-OPC Object tags within an OPC signature. - // This is common for XAdES signatures and must be explicitly allowed. - XmlNodeList nodeList = xmlDocument.ChildNodes; - if (nodeList == null || nodeList.Count == 0 || nodeList.Count > 2) - throw new XmlException(SR.Get(SRID.PackageSignatureCorruption)); - - XmlNode node = nodeList[0]; - if (nodeList.Count == 2) - { - // First node must be the XmlDeclaration - if (nodeList[0].NodeType != XmlNodeType.XmlDeclaration) - throw new XmlException(SR.Get(SRID.PackageSignatureCorruption)); - - // Second node must be in the w3c namespace, and must be the tag - node = nodeList[1]; - } - - if ((node.NodeType != XmlNodeType.Element) || - (String.CompareOrdinal(node.NamespaceURI, SignedXml.XmlDsigNamespaceUrl) != 0) || - (String.CompareOrdinal(node.LocalName, XTable.Get(XTable.ID.SignatureTagName)) != 0)) - { - throw new XmlException(SR.Get(SRID.PackageSignatureCorruption)); - } - - // instantiate the SignedXml from the xmlDoc - _signedXml.LoadXml((XmlElement)node); - } - } - } - - // As per the OPC spec, only two Canonicalization methods can be specified - if (!IsValidXmlCanonicalizationTransform(_signedXml.SignedInfo.CanonicalizationMethod)) - throw new XmlException(SR.Get(SRID.UnsupportedCanonicalizationMethod)); - - // As per OPC spec, signature ID must be NCName - if (_signedXml.Signature.Id != null) - { - try - { - System.Xml.XmlConvert.VerifyNCName(_signedXml.Signature.Id); - } - catch (System.Xml.XmlException) - { - throw new XmlException(SR.Get(SRID.PackageSignatureCorruption)); - } - } - - return _signedXml; - } - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - /// - /// Constructor - called from public constructor as well as static Sign() method - /// - /// current DigitalSignatureManager - /// the part that will/does house the associated XML signature - private XmlDigitalSignatureProcessor(PackageDigitalSignatureManager manager, - PackagePart signaturePart) - { - Invariant.Assert(manager != null); - Invariant.Assert(signaturePart != null); - - _signaturePart = signaturePart; - _manager = manager; - _lookForEmbeddedCert = true; - } - - /// - /// Create a new PackageDigitalSignature - /// - /// the data being protected by this signature - /// possibly null collection of relationshipSelectors that represent the - /// relationships that are to be signed - /// Identity of the author - /// Id attribute of the new Xml Signature - /// true if caller wants certificate embedded in the signature itself - /// references - /// objects to sign - private PackageDigitalSignature Sign( - IEnumerable parts, - IEnumerable relationshipSelectors, - X509Certificate2 signer, - String signatureId, - bool embedCertificate, - IEnumerable signatureObjects, - IEnumerable objectReferences) - { - // don't overwrite +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// +// Description: +// Implementation of the W3C Digital Signature Handler. +// Generates and consumes XmlDSig-compliant digital signatures based on the subset +// specified by the Opc file format. +// + + +using System; +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Security; // for SecurityCritical and SecurityTreatAsSafe +using System.Security.Cryptography; +using System.Security.Cryptography.Xml; +using System.Security.Cryptography.X509Certificates; +using System.Xml; +using System.IO; +using System.Windows; +using System.IO.Packaging; +using Microsoft.Win32; +using MS.Internal; +using MS.Internal.WindowsBase; + +using MS.Internal.IO.Packaging.Extensions; + +namespace MS.Internal.IO.Packaging +{ + /// + /// Signature Handler implementation that follows the Feb 12, 2002 W3C DigSig Recommendation + /// + /// See: http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/ for details + internal class XmlDigitalSignatureProcessor + { + //------------------------------------------------------ + // + // Internal Methods + // + //------------------------------------------------------ + + /// + /// Constructor - called from PackageDigitalSignatureManager when opening an existing signature + /// + /// current DigitalSignatureManager + /// public signature object + /// the part that will/does house the associated XML signature + internal XmlDigitalSignatureProcessor(PackageDigitalSignatureManager manager, + PackagePart signaturePart, PackageDigitalSignature packageSignature) : this(manager, signaturePart) + { + _signature = packageSignature; + } + + /// + /// Factory method that creates a new PackageDigitalSignature + /// + internal static PackageDigitalSignature Sign( + PackageDigitalSignatureManager manager, + PackagePart signaturePart, + IEnumerable parts, + IEnumerable relationshipSelectors, + X509Certificate2 signer, + String signatureId, + bool embedCertificate, + IEnumerable signatureObjects, + IEnumerable objectReferences) + { + // create + XmlDigitalSignatureProcessor p = new XmlDigitalSignatureProcessor(manager, signaturePart); + + // and sign + return p.Sign(parts, relationshipSelectors, signer, signatureId, + embedCertificate, signatureObjects, objectReferences); + } + + /// + /// Verify the given signature + /// + /// throws if no certificate found in the signature + /// true if the data stream has not been altered since it was signed + internal bool Verify() + { + return Verify(Signer); + } + + /// + /// Verify the given signature + /// + /// certificate to use (ignores any embedded cert) + /// true if the data stream has not been altered since it was signed + internal bool Verify(X509Certificate2 signer) + { + Invariant.Assert(signer != null); + + // Create a SignedXml to do the dirty work + SignedXml xmlSig = EnsureXmlSignatureParsed(); + + bool result = false; + + // Validate the Reference tags in the SignedInfo as per the + // restrictions imposed by the OPC spec + ValidateReferences(xmlSig.SignedInfo.References, true /*allowPackageSpecificReference*/); + + // verify "standard" XmlSignature portions + result = xmlSig.CheckSignature(signer, true); + + if (result) + { + HashAlgorithm hashAlgorithm = null; // optimize - generally only need to create and re-use one of these + String currentHashAlgorithmName = String.Empty; // guaranteed not to match + + try + { + try + { + // if that succeeds, verify the Manifest including Part/Relationship content and ContentTypes + ParsePackageDataObject(); + } + catch (XmlException) + { + // parsing exception - means this is a bad signature + return false; + } + + foreach (PartManifestEntry partEntry in _partEntryManifest) + { + // compare the content + Stream s = null; + + // Relationship requires special handling + if (partEntry.IsRelationshipEntry) + { + // This behaves correctely even if the Relationship Part is missing entirely + s = GetRelationshipStream(partEntry); + } + else // Part entry - inspect raw stream + { + // part is guaranteed to exist at this point because we fail early in PackageDigitalSignature.Verify() + Debug.Assert(_manager.Package.PartExists(partEntry.Uri)); + + // Compare the content type first so we can fail early if it doesn't match + // (faster than hashing the content itself). + // Compare ordinal case-sensitive which is more strict than normal ContentType + // comparision because this is manadated by the OPC specification. + PackagePart part = _manager.Package.GetPart(partEntry.Uri); + if (String.CompareOrdinal( + partEntry.ContentType.OriginalString, + part.ValidatedContentType().OriginalString) != 0) + { + result = false; // content type mismatch + break; + } + s = part.GetStream(FileMode.Open, FileAccess.Read); + } + + using (s) + { + // ensure hash algorithm object is available - re-use if possible + if (((hashAlgorithm != null) && (!hashAlgorithm.CanReuseTransform)) || + String.CompareOrdinal(partEntry.HashAlgorithm, currentHashAlgorithmName) != 0) + { + if (hashAlgorithm != null) + ((IDisposable)hashAlgorithm).Dispose(); + + currentHashAlgorithmName = partEntry.HashAlgorithm; + hashAlgorithm = GetHashAlgorithm(currentHashAlgorithmName); + + // not a supported or recognized algorithm? + if (hashAlgorithm == null) + { + // return invalid result instead of throwing exception + result = false; + break; + } + } + + // calculate hash + String base64EncodedHashValue = GenerateDigestValue(s, partEntry.Transforms, hashAlgorithm); + + // now compare the hash - must be identical + if (String.CompareOrdinal(base64EncodedHashValue, partEntry.HashValue) != 0) + { + result = false; // hash mismatch + break; + } + } + } + } + finally + { + if (hashAlgorithm != null) + ((IDisposable)hashAlgorithm).Dispose(); + } + } + + return result; + } + + /// + /// Get the list of transforms applied to the given part (works for Relationship parts too) + /// + /// part to get transforms for + /// possibly empty, ordered list of transforms applied to the given part (never null) + /// This method only returns transform names. Transform-specific properties can be obtained by parsing the actual + /// signature contents which conform to the W3C XmlDSig spec. + internal List GetPartTransformList(Uri partName) + { + // query the parsed manifest + ParsePackageDataObject(); + + List transformList = null; + + // look through the manifest for the requested part + foreach (PartManifestEntry entry in _partEntryManifest) + { + if (System.IO.Packaging.PackUriHelper.ComparePartUri(entry.Uri, partName) == 0) + { + transformList = entry.Transforms; + break; + } + } + + // never return null - an empty list is better form + if (transformList == null) + transformList = new List(); + + return transformList; + } + + //------------------------------------------------------ + // + // Internal Properties + // + //------------------------------------------------------ + + /// + /// Content type of signature parts created by this processor + /// + internal static ContentType ContentType + { + get + { + return _xmlSignaturePartType; + } + } + + + /// + /// Associated signature part + /// + internal PackagePart SignaturePart + { + get + { + return _signaturePart; + } + } + + + /// + /// Obtain the list of signed parts + /// + /// Authors identity in handler-proprietary format + /// if signature xml is malformed + internal List PartManifest + { + get + { + ParsePackageDataObject(); + return _partManifest; + } + } + + /// + /// Obtain the author's identity as a byte stream + /// + /// Authors identity in handler-proprietary format + /// if signature xml is malformed + internal List RelationshipManifest + { + get + { + ParsePackageDataObject(); + return _relationshipManifest; + } + } + + /// + /// Obtain the author's identity in X509 Certificate form + /// + /// Authors identity as a certificate in X509 form, or null if none found + internal X509Certificate2 Signer + { + get + { + // lazy init when loading existing cert - Sign may have assigned this for us + if (_certificate == null) + { + // first look for cert part + if (PackageSignature.GetCertificatePart() != null) + _certificate = PackageSignature.GetCertificatePart().GetCertificate(); + else + { + // look in signature + if (_lookForEmbeddedCert) + { + IEnumerator keyInfoClauseEnum = EnsureXmlSignatureParsed().KeyInfo.GetEnumerator(typeof(KeyInfoX509Data)); + while (keyInfoClauseEnum.MoveNext()) + { + KeyInfoX509Data x509Data = (KeyInfoX509Data)keyInfoClauseEnum.Current; + foreach (X509Certificate2 cert in x509Data.Certificates) + { + // just take the first one for now + _certificate = cert; + break; + } + + // just need one for now + if (_certificate != null) + break; + } + + // only need to do this once + _lookForEmbeddedCert = false; + } + } + } + + return _certificate; // may be null + } + } + + /// + /// encrypted hash value + /// + /// + internal byte[] SignatureValue + { + get + { + return EnsureXmlSignatureParsed().SignatureValue; + } + } + + /// + /// The object that actually creates the signature + /// Note: This API is exposed to the public API surface through the + /// PackageDigitalSignature.Signature property. Through this API it is + /// possible to create Signatures that are not OPC compliant. However, + /// at verify time, we will throw exceptions for non-conforming signatures. + /// + /// object of type System.Security.Cryptography.Xml.Signature + internal Signature Signature + { + get + { + return EnsureXmlSignatureParsed().Signature; + } + set + { + UpdatePartFromSignature(value); + } + } + + /// + /// Get the given signature + /// + internal PackageDigitalSignature PackageSignature + { + get + { + return _signature; + } + } + + /// + /// Time that the signature was created + /// + /// Time of signing if available, or DateTime.MinValue if signature was not time-stamped + internal DateTime SigningTime + { + get + { + // lazy init + ParsePackageDataObject(); + return _signingTime; + } + } + + internal String TimeFormat + { + get + { + // lazy init + ParsePackageDataObject(); + return _signingTimeFormat; + } + } + + //------------------------------------------------------ + // + // Digest Helper Functions + // + //------------------------------------------------------ + /// + /// Generate digest value tag + /// + /// + /// name of the single transform to use - may be null + /// hash algorithm to use + /// + internal static String GenerateDigestValue( + Stream s, + String transformName, + HashAlgorithm hashAlgorithm) + { + List transforms = null; + if (transformName != null) + { + transforms = new List(1); + transforms.Add(transformName); + } + return GenerateDigestValue(s, transforms, hashAlgorithm); + } + + /// + /// Generate digest value tag + /// + /// transforms to apply - may be null and list may contain empty strings + /// stream to hash + /// algorithm to use + internal static String GenerateDigestValue( + Stream s, + List transforms, + HashAlgorithm hashAlgorithm) + { + s.Seek(0, SeekOrigin.Begin); + + // We need to be able to dispose streams generated by the + // Transform object but we don't want to dispose the stream + // passed to us so we insert this to block any propagated Dispose() calls. + Stream transformStream = new IgnoreFlushAndCloseStream(s); + + List transformStreams = null; + + // canonicalize the part content if asked + if (transforms != null) + { + transformStreams = new List(transforms.Count); + transformStreams.Add(transformStream); + foreach (String transformName in transforms) + { + // ignore empty strings at this point (as well as Relationship Transforms) - these are legal + if ((transformName.Length == 0) + || (String.CompareOrdinal(transformName, XTable.Get(XTable.ID.RelationshipsTransformName)) == 0)) + { + continue; + } + + // convert the transform names into objects (if defined) + Transform transform = StringToTransform(transformName); + + if (transform == null) + { + // throw XmlException so the outer loop knows the signature is invalid + throw new XmlException(SR.Get(SRID.UnsupportedTransformAlgorithm)); + } + + transformStream = TransformXml(transform, transformStream); + transformStreams.Add(transformStream); + } + } + + // hash it and encode to Base64 + String hashValueString = System.Convert.ToBase64String(HashStream(hashAlgorithm, transformStream)); + + // dispose of any generated streams + if (transformStreams != null) + { + foreach (Stream stream in transformStreams) + stream.Close(); + } + + return hashValueString; + } + + /// + /// Synthesizes a NodeList of Reference tags to hash + /// + /// + /// + internal static Stream GenerateRelationshipNodeStream(IEnumerable relationships) + { + // create a NodeList containing valid Relationship XML and serialize it to the stream + Stream s = new MemoryStream(); + + // Wrap in a stream that ignores Flush and Close so the XmlTextWriter + // will not close it. + // use UTF-8 encoding by default + using (XmlTextWriter writer = new XmlTextWriter(new IgnoreFlushAndCloseStream(s), + System.Text.Encoding.UTF8)) + { + // start outer Relationships tag + writer.WriteStartElement(XTable.Get(XTable.ID.RelationshipsTagName), PackagingUtilities.RelationshipNamespaceUri); + + // generate a valid Relationship tag according to the Opc schema + InternalRelationshipCollection.WriteRelationshipsAsXml(writer, relationships, + true, /* systematically write target mode */ + false /* not in streaming production */ + ); + + // end of Relationships tag + writer.WriteEndElement(); + } + + return s; + } + + /// + /// Convert algorithm name to object + /// + /// fully specified name + /// HashAlgorithm object or null if it does not map to a supported hash type + /// Caller is responsible for calling Dispose() on returned object + internal static HashAlgorithm GetHashAlgorithm(String hashAlgorithmName) + { + Object o = CryptoConfig.CreateFromName(hashAlgorithmName); + HashAlgorithm algorithm = o as HashAlgorithm; + + // In the case that we get a valid crypto object that is not a hash algorithm + // we should attempt to dispose it if it offers IDisposable. + if (algorithm == null && o != null) + { + IDisposable disposable = o as IDisposable; + if (disposable != null) + disposable.Dispose(); + } + + return algorithm; + } + + /// + /// IMPORTANT NOTE: + /// 1. In the XmlDigitalSignatureProcessor.IsValidXmlCanonicalizationTransform method, + /// we have similar logic regarding these two transforms.So both these methods must be updated + /// in sync. + /// + private static Transform StringToTransform(String transformName) + { + Invariant.Assert(transformName != null); + + if (String.CompareOrdinal(transformName, SignedXml.XmlDsigC14NTransformUrl) == 0) + { + return new XmlDsigC14NTransform(); + } + else if (String.CompareOrdinal(transformName, SignedXml.XmlDsigC14NWithCommentsTransformUrl) == 0) + { + return new XmlDsigC14NWithCommentsTransform(); + } + else + return null; +} + + // As per the OPC spec, only two tranforms are valid. Also, both of these happen to be + // XML canonicalization transforms. + // In the XmlSignatureManifest.ParseTransformsTag method we make use this method to + // validate the transforms to make sure that they are supported by the OPC spec and + // we also take advantage of the fact that both of them are XML canonicalization transforms + // IMPORTANT NOTE: + // 1. In the XmlDigitalSignatureProcessor.StringToTransform method, we have similar logic + // regarding these two transforms.So both these methods must be updated in sync. + // 2. If ever this method is updated to add other transforms, careful review must be done to + // make sure that methods calling this method are updated as required. + internal static bool IsValidXmlCanonicalizationTransform(String transformName) + { + Invariant.Assert(transformName != null); + + if (String.CompareOrdinal(transformName, SignedXml.XmlDsigC14NTransformUrl) == 0 || + String.CompareOrdinal(transformName, SignedXml.XmlDsigC14NWithCommentsTransformUrl) == 0) + { + return true; + } + else + return false; + } + + //------------------------------------------------------ + // + // Private Properties + // + //------------------------------------------------------ + /// + /// Helper class + /// + private SignedXml EnsureXmlSignatureParsed() + { + // lazy init + if (_signedXml == null) + { + _signedXml = new CustomSignedXml(); + + // Load the XML + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.PreserveWhitespace = true; + using (Stream s = SignaturePart.GetStream()) + { + using (XmlTextReader xmlReader = new XmlTextReader(s)) + { + //Prohibit DTD from the markup as per the OPC spec + xmlReader.ProhibitDtd = true; + + //This method expects the reader to be in ReadState.Initial. + //It will make the first read call. + PackagingUtilities.PerformInitailReadAndVerifyEncoding(xmlReader); + + //If the reader.ReadState is ReadState.Initial, then XmlDocument with perform the + //first xmlReader.Read() and start loading from that node/tag. + //If the reader.ReadState is ReadState.Intermediate, then XmlDocument, will start + //loading from that location itself. + //Note: Since in the above method we perform only the first read and will have not + //moved the reader state further down in the markup, we should be okay, and + //xmlDocument.Load will load from the very begining as intended. + xmlDocument.Load(xmlReader); + + // W3C spec allows for Signature tag to appear as an island and inherently allows + // for multiple Signature tags within the same XML document. + // OPC restricts this to a single, root-level Signature tag. However, Signature + // tags are allowed to exist within the non-OPC Object tags within an OPC signature. + // This is common for XAdES signatures and must be explicitly allowed. + XmlNodeList nodeList = xmlDocument.ChildNodes; + if (nodeList == null || nodeList.Count == 0 || nodeList.Count > 2) + throw new XmlException(SR.Get(SRID.PackageSignatureCorruption)); + + XmlNode node = nodeList[0]; + if (nodeList.Count == 2) + { + // First node must be the XmlDeclaration + if (nodeList[0].NodeType != XmlNodeType.XmlDeclaration) + throw new XmlException(SR.Get(SRID.PackageSignatureCorruption)); + + // Second node must be in the w3c namespace, and must be the tag + node = nodeList[1]; + } + + if ((node.NodeType != XmlNodeType.Element) || + (String.CompareOrdinal(node.NamespaceURI, SignedXml.XmlDsigNamespaceUrl) != 0) || + (String.CompareOrdinal(node.LocalName, XTable.Get(XTable.ID.SignatureTagName)) != 0)) + { + throw new XmlException(SR.Get(SRID.PackageSignatureCorruption)); + } + + // instantiate the SignedXml from the xmlDoc + _signedXml.LoadXml((XmlElement)node); + } + } + } + + // As per the OPC spec, only two Canonicalization methods can be specified + if (!IsValidXmlCanonicalizationTransform(_signedXml.SignedInfo.CanonicalizationMethod)) + throw new XmlException(SR.Get(SRID.UnsupportedCanonicalizationMethod)); + + // As per OPC spec, signature ID must be NCName + if (_signedXml.Signature.Id != null) + { + try + { + System.Xml.XmlConvert.VerifyNCName(_signedXml.Signature.Id); + } + catch (System.Xml.XmlException) + { + throw new XmlException(SR.Get(SRID.PackageSignatureCorruption)); + } + } + + return _signedXml; + } + + //------------------------------------------------------ + // + // Private Methods + // + //------------------------------------------------------ + + /// + /// Constructor - called from public constructor as well as static Sign() method + /// + /// current DigitalSignatureManager + /// the part that will/does house the associated XML signature + private XmlDigitalSignatureProcessor(PackageDigitalSignatureManager manager, + PackagePart signaturePart) + { + Invariant.Assert(manager != null); + Invariant.Assert(signaturePart != null); + + _signaturePart = signaturePart; + _manager = manager; + _lookForEmbeddedCert = true; + } + + /// + /// Create a new PackageDigitalSignature + /// + /// the data being protected by this signature + /// possibly null collection of relationshipSelectors that represent the + /// relationships that are to be signed + /// Identity of the author + /// Id attribute of the new Xml Signature + /// true if caller wants certificate embedded in the signature itself + /// references + /// objects to sign + private PackageDigitalSignature Sign( + IEnumerable parts, + IEnumerable relationshipSelectors, + X509Certificate2 signer, + String signatureId, + bool embedCertificate, + IEnumerable signatureObjects, + IEnumerable objectReferences) + { + // don't overwrite +#if DEBUG using (Stream stream = SignaturePart.GetStream()) { - Debug.Assert(stream.Length == 0, "Logic Error: Can't sign when signature already exists"); - } - - // grab hash algorithm as this may change in the future - _hashAlgorithmName = _manager.HashAlgorithm; - - // keep the signer if indicated - if (_manager.CertificateOption == CertificateEmbeddingOption.NotEmbedded) - _lookForEmbeddedCert = false; // don't bother parsing - else - _certificate = signer; // save some parsing - - // we only release this key if we obtain it - AsymmetricAlgorithm key = null; - if (signer.HasPrivateKey) - { - key = GetPrivateKey(signer); - } - else - { - key = GetPrivateKeyForSigning(signer); - } - - try - { - _signedXml = new CustomSignedXml(); - _signedXml.SigningKey = key; - _signedXml.Signature.Id = signatureId; - - if (BaseCompatibilityPreferences.MatchPackageSignatureMethodToPackagePartDigestMethod) - { - // Defaulting to SHA1 for key signing is counter-productive if the hash algorithm - // for the rest of the package is more secure. So if we have a selected hash - // algorithm in the PackageDigitalSignatureManager that is more secure, then - // select an equivalent level algorithm for key signing. - _signedXml.SignedInfo.SignatureMethod = SelectSignatureMethod(key); - } - - // Track if we are matching the signature method in order to retry on failure - bool usingMatchingSignatureMethod = _signedXml.SignedInfo.SignatureMethod != null; - - // put it in the XML - if (embedCertificate) - { - _signedXml.KeyInfo = GenerateKeyInfo(key, signer); - } - - // Package object tag - // convert from string to class and ensure we dispose - using (HashAlgorithm hashAlgorithm = GetHashAlgorithm(_hashAlgorithmName)) - { - // inform caller if hash algorithm is unknown - if (hashAlgorithm == null) - throw new InvalidOperationException(SR.Get(SRID.UnsupportedHashAlgorithm)); - - _signedXml.AddObject(GenerateObjectTag(hashAlgorithm, parts, relationshipSelectors, signatureId)); - } - - // add reference from SignedInfo to Package object tag - Reference objectReference = new Reference(XTable.Get(XTable.ID.OpcLinkAttrValue)); - objectReference.Type = XTable.Get(XTable.ID.W3CSignatureNamespaceRoot) + "Object"; - objectReference.DigestMethod = _hashAlgorithmName; - _signedXml.AddReference(objectReference); - - // add any custom object tags - AddCustomObjectTags(signatureObjects, objectReferences); - - // compute the signature - SignedXml xmlSig = _signedXml; - - try - { - xmlSig.ComputeSignature(); - } - catch (CryptographicException) when (usingMatchingSignatureMethod) - { - // We've hit a state where System.Security is possibly missing the required updates to process the matched signature. - // Disable our matching and attempt to sign again with the default SignatureMethod - BaseCompatibilityPreferences.MatchPackageSignatureMethodToPackagePartDigestMethod = false; - xmlSig.SignedInfo.SignatureMethod = null; - xmlSig.ComputeSignature(); - } - - // persist - UpdatePartFromSignature(_signedXml.Signature); - } - finally - { - if (key != null) - ((IDisposable)key).Dispose(); - } - - // create the PackageDigitalSignature object - _signature = new PackageDigitalSignature(_manager, this); - return _signature; - } - - /// - /// Attempt to select a key signing method that matches the strength of the selected - /// hash algorithm in the PackageDigitalSignatureManager. - /// - /// If no algorithm was selected, defaults to SHA1. - /// - /// - /// DSA keys have no supported stronger signing solution and are always SHA1 signed. - /// - /// The key being used - /// The URI of the appropriate signing method - private string SelectSignatureMethod(AsymmetricAlgorithm key) - { - string signatureMethod = null; - - if (key is RSA) - { - _rsaSigMethodLookup.TryGetValue(_manager.HashAlgorithm, out signatureMethod); - } - - return signatureMethod; - } - - /// - /// Extracts the private key from the X509Certificate2 certificate - /// - /// certificate for which we are looking for the private key - /// returns the private key - private static AsymmetricAlgorithm GetPrivateKey(X509Certificate2 cert) - { - // Adding support for CNG certificates. The default certificate template in Windows Server 2008+ is CNG - // Get[Algorithm]PrivateKey methods returns unique object while PrivateKey property returns shared one - // Make sure to dispose the key if it is from Get[Algorithm]PrivateKey methods - AsymmetricAlgorithm key = cert.GetRSAPrivateKey(); - if (key != null) - return key; - - key = cert.GetDSAPrivateKey(); - if (key != null) - return key; - - key = cert.GetECDsaPrivateKey(); - if (key != null) - return key; - - // Get[Algorithm]PrivateKey methods would always have returned the private key if the PrivateKey property would - // But Get[Algorithm]PrivateKey methods never throw but returns null in case of error during cryptographic operations - // But we want exception to be thrown when an error occurs during a cryptographic operation so that we can revert the changes - return cert.PrivateKey; - } - - /// - /// Assembles the sorted list of relationships for this part entry and - /// generates a stream with the Xml-equivalent as defined in the Opc spec - /// - /// relationship-type part entry - /// - private Stream GetRelationshipStream(PartManifestEntry partEntry) - { - Debug.Assert(partEntry.IsRelationshipEntry); - - //Get the list of relationships from the RelationshipSelectors for this part - SortedDictionary partRelationships = - new SortedDictionary(StringComparer.Ordinal); - foreach (System.IO.Packaging.PackageRelationshipSelector relationshipSelector in partEntry.RelationshipSelectors) - { - foreach (System.IO.Packaging.PackageRelationship r in relationshipSelector.Select(_manager.Package)) - { - if(!partRelationships.ContainsKey(r.Id)) - partRelationships.Add(r.Id, r); - } - } - - return GenerateRelationshipNodeStream(partRelationships.Values); - } - - private void AddCustomObjectTags(IEnumerable signatureObjects, - IEnumerable objectReferences) - { - Invariant.Assert(_signedXml != null); - - - // add any references - if (objectReferences != null) - { - // Validate the Reference tags in the SignedInfo as per the - // restrictions imposed by the OPC spec - ValidateReferences(objectReferences, false /*allowPackageSpecificReference*/); - - foreach (Reference reference in objectReferences) - { - // consistent hash algorithm for entire signature - reference.DigestMethod = _hashAlgorithmName; - _signedXml.AddReference(reference); - } - } - - // any object tags - if (signatureObjects != null) - { - // thes have been pre-screened for matches against reserved OpcAttrValue and duplicates - foreach (DataObject obj in signatureObjects) - { - _signedXml.AddObject(obj); - } - } - } - - private void UpdatePartFromSignature(Signature sig) - { - // write to stream - using (Stream s = SignaturePart.GetStream(FileMode.Create, FileAccess.Write)) - { - using (XmlTextWriter xWriter = new XmlTextWriter(s, System.Text.Encoding.UTF8)) - { - xWriter.WriteStartDocument(true); - sig.GetXml().WriteTo(xWriter); - xWriter.WriteEndDocument(); - } - } - _signedXml = null; // force a re-parse - } - - private static byte[] HashStream(HashAlgorithm hashAlgorithm, Stream s) - { - s.Seek(0, SeekOrigin.Begin); - - // reset algorithm - hashAlgorithm.Initialize(); - return hashAlgorithm.ComputeHash(s); - } - - private static Stream TransformXml(Transform xForm, Object source) - { - // transform - xForm.LoadInput(source); - - return (Stream)xForm.GetOutput(); - } - - /// - /// Full parse of the Package-specific Object tag - /// - /// Side effect of updating _signingTime, _signingTimeFormat, - /// _partManifest, _partEntryManifest and _relationshipManifest - /// throws if markup does not match OPC spec - private void ParsePackageDataObject() - { - if (!_dataObjectParsed) - { - EnsureXmlSignatureParsed(); - - // find the package-specific Object tag - XmlNodeList nodeList = GetPackageDataObject().Data; - - // The legal parent is a "Package" Object tag with 2 children - // and - if (nodeList.Count != 2) - throw new XmlException(SR.Get(SRID.XmlSignatureParseError)); - - // get a NodeReader that allows us to easily and correctly skip comments - XmlReader reader = new XmlNodeReader(nodeList[0].ParentNode); - - // parse the tag - ensure that it is in the correct namespace - reader.Read(); // enter the Object tag - if (String.CompareOrdinal(reader.NamespaceURI, SignedXml.XmlDsigNamespaceUrl) != 0) - throw new XmlException(SR.Get(SRID.XmlSignatureParseError)); - - string signaturePropertiesTagName = XTable.Get(XTable.ID.SignaturePropertiesTagName); - string manifestTagName = XTable.Get(XTable.ID.ManifestTagName); - bool signaturePropertiesTagFound = false; - bool manifestTagFound = false; - while (reader.Read() && (reader.NodeType == XmlNodeType.Element)) - { - if (reader.MoveToContent() == XmlNodeType.Element - && (String.CompareOrdinal(reader.NamespaceURI, SignedXml.XmlDsigNamespaceUrl) == 0) - && reader.Depth == 1) - { - if (!signaturePropertiesTagFound && String.CompareOrdinal(reader.LocalName, signaturePropertiesTagName) == 0) - { - signaturePropertiesTagFound = true; - - // parse the tag - _signingTime = XmlSignatureProperties.ParseSigningTime( - reader, _signedXml.Signature.Id, out _signingTimeFormat); - - continue; - } - else if (!manifestTagFound && String.CompareOrdinal(reader.LocalName, manifestTagName) == 0) - { - manifestTagFound = true; - - // parse the tag - XmlSignatureManifest.ParseManifest(_manager, reader, - out _partManifest, out _partEntryManifest, out _relationshipManifest); - - continue; - } - } - - throw new XmlException(SR.Get(SRID.XmlSignatureParseError)); - } - - // these must both exist on exit - if (!(signaturePropertiesTagFound && manifestTagFound)) - throw new XmlException(SR.Get(SRID.XmlSignatureParseError)); - - _dataObjectParsed = true; - } - } - - /// - /// Finds and return the package-specific Object tag - /// - /// - private DataObject GetPackageDataObject() - { - EnsureXmlSignatureParsed(); - - // look for the Package-specific object tag - String opcId = XTable.Get(XTable.ID.OpcAttrValue); - DataObject returnValue = null; - foreach (DataObject dataObject in _signedXml.Signature.ObjectList) - { - if (String.CompareOrdinal(dataObject.Id, opcId) == 0) - { - // duplicates not allowed - if (returnValue != null) - throw new XmlException(SR.Get(SRID.SignatureObjectIdMustBeUnique)); - - returnValue = dataObject; - } - } - - // Package object tag required - if (returnValue != null) - return returnValue; - else - throw new XmlException(SR.Get(SRID.PackageSignatureObjectTagRequired)); - } - - private KeyInfo GenerateKeyInfo(AsymmetricAlgorithm key, X509Certificate2 signer) - { - // KeyInfo section - KeyInfo keyInfo = new KeyInfo(); - KeyInfoName keyInfoName = new KeyInfoName(); - keyInfoName.Value = signer.Subject; - keyInfo.AddClause(keyInfoName); // human readable Principal name - - // Include the public key information (if we are familiar with the algorithm type) - if (key is RSA) - keyInfo.AddClause(new RSAKeyValue((RSA)key)); // RSA key parameters - else - { - if (key is DSA) - keyInfo.AddClause(new DSAKeyValue((DSA)key)); // DSA - else - throw new ArgumentException(SR.Get(SRID.CertificateKeyTypeNotSupported), "signer"); - } - - // the actual X509 cert - keyInfo.AddClause(new KeyInfoX509Data(signer)); - - return keyInfo; - } - - private DataObject GenerateObjectTag( - HashAlgorithm hashAlgorithm, - IEnumerable parts, IEnumerable relationshipSelectors, - String signatureId) - { - XmlDocument xDoc = new XmlDocument(); - xDoc.AppendChild(xDoc.CreateNode(XmlNodeType.Element, "root", "namespace")); // dummy root - xDoc.DocumentElement.AppendChild(XmlSignatureManifest.GenerateManifest(_manager, xDoc, hashAlgorithm, parts, relationshipSelectors)); - xDoc.DocumentElement.AppendChild(XmlSignatureProperties.AssembleSignatureProperties(xDoc, DateTime.Now, _manager.TimeFormat, signatureId)); - - DataObject dataObject = new DataObject(); - dataObject.Data = xDoc.DocumentElement.ChildNodes; - dataObject.Id = XTable.Get(XTable.ID.OpcAttrValue); - - return dataObject; - } - - /// - /// lookup the private key using the given identity - /// - /// X509Cert - /// IDisposable asymmetric algorithm that serves as a proxy to the private key. Caller must dispose - /// of properly. - private static AsymmetricAlgorithm GetPrivateKeyForSigning(X509Certificate2 signer) - { - // if the certificate does not actually contain the key, we need to look it up via ThumbPrint - Invariant.Assert(!signer.HasPrivateKey); - - // look for appropriate certificates - X509Store store = new X509Store(StoreLocation.CurrentUser); - - try - { - store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); - - X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates; - - collection = collection.Find(X509FindType.FindByThumbprint, signer.Thumbprint, true); - if (collection.Count > 0) - { - if (collection.Count > 1) - throw new CryptographicException(SR.Get(SRID.DigSigDuplicateCertificate)); - - signer = collection[0]; - } - else - throw new CryptographicException(SR.Get(SRID.DigSigCannotLocateCertificate)); - } - finally - { - store.Close(); - } - - // get the corresponding AsymmetricAlgorithm - return GetPrivateKey(signer); - } - - - /// - /// This method validated the Reference tags as per the restrictions imposed - /// by the OPC spec. - /// NOTE: The same method is called from Verify and Sign methods. At verify time we need to make sure - /// that there is exactly one Package-specific reference. At Sign time we need to make sure that - /// there are no package-specific references in the list of references passed to Sign APIs as a - /// input parameter, since we will be generating Package-specific object. - /// - /// list of references to be validated - /// When "true", we check to make sure that there is - /// exactly one package-specific reference and when "false", we do not allow any package-specific - /// references - private void ValidateReferences(IEnumerable references, bool allowPackageSpecificReferences) - { - Debug.Assert(references != null); - - bool packageReferenceFound = false; - TransformChain currentTransformChain; - - foreach (Reference currentReference in references) - { - //As per the OPC spec, Uri attribute in Reference elements MUST refer using fragment identifiers - //This implies that Uri cannot be absolute. - if (currentReference.Uri.StartsWith("#", StringComparison.Ordinal)) - { - //As per the OPC spec, there MUST be exactly one package specific reference to the - //package specific element - if (String.CompareOrdinal(currentReference.Uri, XTable.Get(XTable.ID.OpcLinkAttrValue)) == 0) - { - if (!allowPackageSpecificReferences) - throw new ArgumentException(SR.Get(SRID.PackageSpecificReferenceTagMustBeUnique)); - - //If there are more than one package specific tags - if (packageReferenceFound == true) - throw new XmlException(SR.Get(SRID.MoreThanOnePackageSpecificReference)); - else - packageReferenceFound = true; - } - - currentTransformChain = currentReference.TransformChain; - - for(int j=0; j _partManifest; // signed parts (suitable for return to public API) - private List _partEntryManifest; // signed parts (with extra info) - private List _relationshipManifest; // signed relationship selectors - - private static readonly ContentType _xmlSignaturePartType - = new ContentType("application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"); - - /// - /// Mapping from hash algorithms in the PackageDigitalSignatureManager to RSA key signing methods. - /// - private static readonly Dictionary _rsaSigMethodLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "http://www.w3.org/2001/04/xmlenc#sha256", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" }, - { "http://www.w3.org/2001/04/xmldsig-more#sha384", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" }, - { "http://www.w3.org/2001/04/xmlenc#sha512", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" }, - }; - } -} - + Debug.Assert(stream.Length == 0, "Logic Error: Can't sign when signature already exists"); + } +#endif + + // grab hash algorithm as this may change in the future + _hashAlgorithmName = _manager.HashAlgorithm; + + // keep the signer if indicated + if (_manager.CertificateOption == CertificateEmbeddingOption.NotEmbedded) + _lookForEmbeddedCert = false; // don't bother parsing + else + _certificate = signer; // save some parsing + + // we only release this key if we obtain it + AsymmetricAlgorithm key = null; + if (signer.HasPrivateKey) + { + key = GetPrivateKey(signer); + } + else + { + key = GetPrivateKeyForSigning(signer); + } + + try + { + _signedXml = new CustomSignedXml(); + _signedXml.SigningKey = key; + _signedXml.Signature.Id = signatureId; + + if (BaseCompatibilityPreferences.MatchPackageSignatureMethodToPackagePartDigestMethod) + { + // Defaulting to SHA1 for key signing is counter-productive if the hash algorithm + // for the rest of the package is more secure. So if we have a selected hash + // algorithm in the PackageDigitalSignatureManager that is more secure, then + // select an equivalent level algorithm for key signing. + _signedXml.SignedInfo.SignatureMethod = SelectSignatureMethod(key); + } + + // Track if we are matching the signature method in order to retry on failure + bool usingMatchingSignatureMethod = _signedXml.SignedInfo.SignatureMethod != null; + + // put it in the XML + if (embedCertificate) + { + _signedXml.KeyInfo = GenerateKeyInfo(key, signer); + } + + // Package object tag + // convert from string to class and ensure we dispose + using (HashAlgorithm hashAlgorithm = GetHashAlgorithm(_hashAlgorithmName)) + { + // inform caller if hash algorithm is unknown + if (hashAlgorithm == null) + throw new InvalidOperationException(SR.Get(SRID.UnsupportedHashAlgorithm)); + + _signedXml.AddObject(GenerateObjectTag(hashAlgorithm, parts, relationshipSelectors, signatureId)); + } + + // add reference from SignedInfo to Package object tag + Reference objectReference = new Reference(XTable.Get(XTable.ID.OpcLinkAttrValue)); + objectReference.Type = XTable.Get(XTable.ID.W3CSignatureNamespaceRoot) + "Object"; + objectReference.DigestMethod = _hashAlgorithmName; + _signedXml.AddReference(objectReference); + + // add any custom object tags + AddCustomObjectTags(signatureObjects, objectReferences); + + // compute the signature + SignedXml xmlSig = _signedXml; + + try + { + xmlSig.ComputeSignature(); + } + catch (CryptographicException) when (usingMatchingSignatureMethod) + { + // We've hit a state where System.Security is possibly missing the required updates to process the matched signature. + // Disable our matching and attempt to sign again with the default SignatureMethod + BaseCompatibilityPreferences.MatchPackageSignatureMethodToPackagePartDigestMethod = false; + xmlSig.SignedInfo.SignatureMethod = null; + xmlSig.ComputeSignature(); + } + + // persist + UpdatePartFromSignature(_signedXml.Signature); + } + finally + { + if (key != null) + ((IDisposable)key).Dispose(); + } + + // create the PackageDigitalSignature object + _signature = new PackageDigitalSignature(_manager, this); + return _signature; + } + + /// + /// Attempt to select a key signing method that matches the strength of the selected + /// hash algorithm in the PackageDigitalSignatureManager. + /// + /// If no algorithm was selected, defaults to SHA1. + /// + /// + /// DSA keys have no supported stronger signing solution and are always SHA1 signed. + /// + /// The key being used + /// The URI of the appropriate signing method + private string SelectSignatureMethod(AsymmetricAlgorithm key) + { + string signatureMethod = null; + + if (key is RSA) + { + _rsaSigMethodLookup.TryGetValue(_manager.HashAlgorithm, out signatureMethod); + } + + return signatureMethod; + } + + /// + /// Extracts the private key from the X509Certificate2 certificate + /// + /// certificate for which we are looking for the private key + /// returns the private key + private static AsymmetricAlgorithm GetPrivateKey(X509Certificate2 cert) + { + // Adding support for CNG certificates. The default certificate template in Windows Server 2008+ is CNG + // Get[Algorithm]PrivateKey methods returns unique object while PrivateKey property returns shared one + // Make sure to dispose the key if it is from Get[Algorithm]PrivateKey methods + AsymmetricAlgorithm key = cert.GetRSAPrivateKey(); + if (key != null) + return key; + + key = cert.GetDSAPrivateKey(); + if (key != null) + return key; + + key = cert.GetECDsaPrivateKey(); + if (key != null) + return key; + + // Get[Algorithm]PrivateKey methods would always have returned the private key if the PrivateKey property would + // But Get[Algorithm]PrivateKey methods never throw but returns null in case of error during cryptographic operations + // But we want exception to be thrown when an error occurs during a cryptographic operation so that we can revert the changes + return cert.PrivateKey; + } + + /// + /// Assembles the sorted list of relationships for this part entry and + /// generates a stream with the Xml-equivalent as defined in the Opc spec + /// + /// relationship-type part entry + /// + private Stream GetRelationshipStream(PartManifestEntry partEntry) + { + Debug.Assert(partEntry.IsRelationshipEntry); + + //Get the list of relationships from the RelationshipSelectors for this part + SortedDictionary partRelationships = + new SortedDictionary(StringComparer.Ordinal); + foreach (System.IO.Packaging.PackageRelationshipSelector relationshipSelector in partEntry.RelationshipSelectors) + { + foreach (System.IO.Packaging.PackageRelationship r in relationshipSelector.Select(_manager.Package)) + { + if(!partRelationships.ContainsKey(r.Id)) + partRelationships.Add(r.Id, r); + } + } + + return GenerateRelationshipNodeStream(partRelationships.Values); + } + + private void AddCustomObjectTags(IEnumerable signatureObjects, + IEnumerable objectReferences) + { + Invariant.Assert(_signedXml != null); + + + // add any references + if (objectReferences != null) + { + // Validate the Reference tags in the SignedInfo as per the + // restrictions imposed by the OPC spec + ValidateReferences(objectReferences, false /*allowPackageSpecificReference*/); + + foreach (Reference reference in objectReferences) + { + // consistent hash algorithm for entire signature + reference.DigestMethod = _hashAlgorithmName; + _signedXml.AddReference(reference); + } + } + + // any object tags + if (signatureObjects != null) + { + // thes have been pre-screened for matches against reserved OpcAttrValue and duplicates + foreach (DataObject obj in signatureObjects) + { + _signedXml.AddObject(obj); + } + } + } + + private void UpdatePartFromSignature(Signature sig) + { + // write to stream + using (Stream s = SignaturePart.GetStream(FileMode.Create, FileAccess.Write)) + { + using (XmlTextWriter xWriter = new XmlTextWriter(s, System.Text.Encoding.UTF8)) + { + xWriter.WriteStartDocument(true); + sig.GetXml().WriteTo(xWriter); + xWriter.WriteEndDocument(); + } + } + _signedXml = null; // force a re-parse + } + + private static byte[] HashStream(HashAlgorithm hashAlgorithm, Stream s) + { + s.Seek(0, SeekOrigin.Begin); + + // reset algorithm + hashAlgorithm.Initialize(); + return hashAlgorithm.ComputeHash(s); + } + + private static Stream TransformXml(Transform xForm, Object source) + { + // transform + xForm.LoadInput(source); + + return (Stream)xForm.GetOutput(); + } + + /// + /// Full parse of the Package-specific Object tag + /// + /// Side effect of updating _signingTime, _signingTimeFormat, + /// _partManifest, _partEntryManifest and _relationshipManifest + /// throws if markup does not match OPC spec + private void ParsePackageDataObject() + { + if (!_dataObjectParsed) + { + EnsureXmlSignatureParsed(); + + // find the package-specific Object tag + XmlNodeList nodeList = GetPackageDataObject().Data; + + // The legal parent is a "Package" Object tag with 2 children + // and + if (nodeList.Count != 2) + throw new XmlException(SR.Get(SRID.XmlSignatureParseError)); + + // get a NodeReader that allows us to easily and correctly skip comments + XmlReader reader = new XmlNodeReader(nodeList[0].ParentNode); + + // parse the tag - ensure that it is in the correct namespace + reader.Read(); // enter the Object tag + if (String.CompareOrdinal(reader.NamespaceURI, SignedXml.XmlDsigNamespaceUrl) != 0) + throw new XmlException(SR.Get(SRID.XmlSignatureParseError)); + + string signaturePropertiesTagName = XTable.Get(XTable.ID.SignaturePropertiesTagName); + string manifestTagName = XTable.Get(XTable.ID.ManifestTagName); + bool signaturePropertiesTagFound = false; + bool manifestTagFound = false; + while (reader.Read() && (reader.NodeType == XmlNodeType.Element)) + { + if (reader.MoveToContent() == XmlNodeType.Element + && (String.CompareOrdinal(reader.NamespaceURI, SignedXml.XmlDsigNamespaceUrl) == 0) + && reader.Depth == 1) + { + if (!signaturePropertiesTagFound && String.CompareOrdinal(reader.LocalName, signaturePropertiesTagName) == 0) + { + signaturePropertiesTagFound = true; + + // parse the tag + _signingTime = XmlSignatureProperties.ParseSigningTime( + reader, _signedXml.Signature.Id, out _signingTimeFormat); + + continue; + } + else if (!manifestTagFound && String.CompareOrdinal(reader.LocalName, manifestTagName) == 0) + { + manifestTagFound = true; + + // parse the tag + XmlSignatureManifest.ParseManifest(_manager, reader, + out _partManifest, out _partEntryManifest, out _relationshipManifest); + + continue; + } + } + + throw new XmlException(SR.Get(SRID.XmlSignatureParseError)); + } + + // these must both exist on exit + if (!(signaturePropertiesTagFound && manifestTagFound)) + throw new XmlException(SR.Get(SRID.XmlSignatureParseError)); + + _dataObjectParsed = true; + } + } + + /// + /// Finds and return the package-specific Object tag + /// + /// + private DataObject GetPackageDataObject() + { + EnsureXmlSignatureParsed(); + + // look for the Package-specific object tag + String opcId = XTable.Get(XTable.ID.OpcAttrValue); + DataObject returnValue = null; + foreach (DataObject dataObject in _signedXml.Signature.ObjectList) + { + if (String.CompareOrdinal(dataObject.Id, opcId) == 0) + { + // duplicates not allowed + if (returnValue != null) + throw new XmlException(SR.Get(SRID.SignatureObjectIdMustBeUnique)); + + returnValue = dataObject; + } + } + + // Package object tag required + if (returnValue != null) + return returnValue; + else + throw new XmlException(SR.Get(SRID.PackageSignatureObjectTagRequired)); + } + + private KeyInfo GenerateKeyInfo(AsymmetricAlgorithm key, X509Certificate2 signer) + { + // KeyInfo section + KeyInfo keyInfo = new KeyInfo(); + KeyInfoName keyInfoName = new KeyInfoName(); + keyInfoName.Value = signer.Subject; + keyInfo.AddClause(keyInfoName); // human readable Principal name + + // Include the public key information (if we are familiar with the algorithm type) + if (key is RSA) + keyInfo.AddClause(new RSAKeyValue((RSA)key)); // RSA key parameters + else + { + if (key is DSA) + keyInfo.AddClause(new DSAKeyValue((DSA)key)); // DSA + else + throw new ArgumentException(SR.Get(SRID.CertificateKeyTypeNotSupported), "signer"); + } + + // the actual X509 cert + keyInfo.AddClause(new KeyInfoX509Data(signer)); + + return keyInfo; + } + + private DataObject GenerateObjectTag( + HashAlgorithm hashAlgorithm, + IEnumerable parts, IEnumerable relationshipSelectors, + String signatureId) + { + XmlDocument xDoc = new XmlDocument(); + xDoc.AppendChild(xDoc.CreateNode(XmlNodeType.Element, "root", "namespace")); // dummy root + xDoc.DocumentElement.AppendChild(XmlSignatureManifest.GenerateManifest(_manager, xDoc, hashAlgorithm, parts, relationshipSelectors)); + xDoc.DocumentElement.AppendChild(XmlSignatureProperties.AssembleSignatureProperties(xDoc, DateTime.Now, _manager.TimeFormat, signatureId)); + + DataObject dataObject = new DataObject(); + dataObject.Data = xDoc.DocumentElement.ChildNodes; + dataObject.Id = XTable.Get(XTable.ID.OpcAttrValue); + + return dataObject; + } + + /// + /// lookup the private key using the given identity + /// + /// X509Cert + /// IDisposable asymmetric algorithm that serves as a proxy to the private key. Caller must dispose + /// of properly. + private static AsymmetricAlgorithm GetPrivateKeyForSigning(X509Certificate2 signer) + { + // if the certificate does not actually contain the key, we need to look it up via ThumbPrint + Invariant.Assert(!signer.HasPrivateKey); + + // look for appropriate certificates + X509Store store = new X509Store(StoreLocation.CurrentUser); + + try + { + store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); + + X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates; + + collection = collection.Find(X509FindType.FindByThumbprint, signer.Thumbprint, true); + if (collection.Count > 0) + { + if (collection.Count > 1) + throw new CryptographicException(SR.Get(SRID.DigSigDuplicateCertificate)); + + signer = collection[0]; + } + else + throw new CryptographicException(SR.Get(SRID.DigSigCannotLocateCertificate)); + } + finally + { + store.Close(); + } + + // get the corresponding AsymmetricAlgorithm + return GetPrivateKey(signer); + } + + + /// + /// This method validated the Reference tags as per the restrictions imposed + /// by the OPC spec. + /// NOTE: The same method is called from Verify and Sign methods. At verify time we need to make sure + /// that there is exactly one Package-specific reference. At Sign time we need to make sure that + /// there are no package-specific references in the list of references passed to Sign APIs as a + /// input parameter, since we will be generating Package-specific object. + /// + /// list of references to be validated + /// When "true", we check to make sure that there is + /// exactly one package-specific reference and when "false", we do not allow any package-specific + /// references + private void ValidateReferences(IEnumerable references, bool allowPackageSpecificReferences) + { + Debug.Assert(references != null); + + bool packageReferenceFound = false; + TransformChain currentTransformChain; + + foreach (Reference currentReference in references) + { + //As per the OPC spec, Uri attribute in Reference elements MUST refer using fragment identifiers + //This implies that Uri cannot be absolute. + if (currentReference.Uri.StartsWith("#", StringComparison.Ordinal)) + { + //As per the OPC spec, there MUST be exactly one package specific reference to the + //package specific element + if (String.CompareOrdinal(currentReference.Uri, XTable.Get(XTable.ID.OpcLinkAttrValue)) == 0) + { + if (!allowPackageSpecificReferences) + throw new ArgumentException(SR.Get(SRID.PackageSpecificReferenceTagMustBeUnique)); + + //If there are more than one package specific tags + if (packageReferenceFound == true) + throw new XmlException(SR.Get(SRID.MoreThanOnePackageSpecificReference)); + else + packageReferenceFound = true; + } + + currentTransformChain = currentReference.TransformChain; + + for(int j=0; j _partManifest; // signed parts (suitable for return to public API) + private List _partEntryManifest; // signed parts (with extra info) + private List _relationshipManifest; // signed relationship selectors + + private static readonly ContentType _xmlSignaturePartType + = new ContentType("application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"); + + /// + /// Mapping from hash algorithms in the PackageDigitalSignatureManager to RSA key signing methods. + /// + private static readonly Dictionary _rsaSigMethodLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "http://www.w3.org/2001/04/xmlenc#sha256", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" }, + { "http://www.w3.org/2001/04/xmldsig-more#sha384", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" }, + { "http://www.w3.org/2001/04/xmlenc#sha512", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" }, + }; + } +} + From 8f3c45bbc3709c79284dc492d896d794cb96a418 Mon Sep 17 00:00:00 2001 From: Steven Kirbach Date: Thu, 18 Jul 2019 11:34:03 -0700 Subject: [PATCH 3/3] undo line ending change --- .../Packaging/XpsFixedDocumentReaderWriter.cs | 2562 ++++++++--------- 1 file changed, 1281 insertions(+), 1281 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs b/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs index 8b275f79d7e..33983f1afb8 100644 --- a/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs +++ b/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsFixedDocumentReaderWriter.cs @@ -1,1281 +1,1281 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*++ - - - - Abstract: - This file contains the definition for IXpsFixedDocumentReader - and IXpsFixedDocumentWriter interfaces as well as definition - and implementation of XpsFixedDocumentReaderWriter. These - interfaces and class are used for writing fixed document - parts to a Xps package. - - - ---*/ -using System; -using System.Collections; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using System.IO; -using System.IO.Packaging; -using System.Xml; -using System.Security.Cryptography.X509Certificates; -using System.Printing; - -using MS.Internal.IO.Packaging.Extensions; -using PackageRelationship = System.IO.Packaging.PackageRelationship; -using PackUriHelper = System.IO.Packaging.PackUriHelper; - -namespace System.Windows.Xps.Packaging -{ - #region IXpsFixedDocumentReader interface definition - - /// - /// Interface for reading fixed document parts from the Xps package. - /// - /// Interface will be internal until reading/de-serialization is implemented. - public interface IXpsFixedDocumentReader: - IDocumentStructureProvider - { - #region Public methods - - /// - /// This method retrieves a fixed page part from a Xps - /// package using the given URI. - /// - /// - /// The URI of the fixed page to retrieve. - /// - /// - /// Returns an interface to an IXpsFixedPageReader to read - /// the requested fixed page. - /// - IXpsFixedPageReader - GetFixedPage( - Uri pageSource - ); - - #endregion Public methods - - #region Public properties - - /// - /// Gets the PrintTicket associated with this fixed document. - /// - /// Value can be a PrintTicket or null. - /// PrintTicket has already been committed. - /// Property is not a valid PrintTicket instance. - PrintTicket PrintTicket { get; } - - /// - /// Gets the URI assigned to this fixed document part. - /// - /// Value is a URI for the Metro part. - Uri Uri { get; } - - /// - /// Gets a collection of all fixed pages that are contained within - /// this fixed document. - /// - /// - /// Value is a Collection containing IXpsFixedPageReader interfaces. - /// - ReadOnlyCollection FixedPages { get; } - - /// - /// 0 based document number in the document sequence - /// - int DocumentNumber{ get; } - - /// - /// A list of signature definitions associated with the document - /// - ICollection - SignatureDefinitions{ get; } - - /// - /// thumbnail image associated with this doucment - /// - XpsThumbnail - Thumbnail{ get; } - - /// - /// Document Structure associated with this doucment - /// - XpsStructure - DocumentStructure{ get; } - - - #endregion Public properties - - /// - /// Adds the passed Signature Definiton to the cached - /// Signature Definition list - /// Will not be written until FlushSignatureDefinition - /// is called - /// - /// - /// the Signature Definition to be added - /// - void - AddSignatureDefinition( - XpsSignatureDefinition signatureDefinition - ); - - /// - /// This method removes a signature definitions associated with - /// the FixedDocument - /// - /// - /// Signature Definition to remove - /// - void - RemoveSignatureDefinition(XpsSignatureDefinition signatureDefinition); - - /// - /// Writes out all modifications to Signature - /// Definitions as well as new Signature Definitions - /// - void - CommitSignatureDefinition(); - } - - #endregion IXpsFixedDocumentReader interface definition - - #region IXpsFixedDocumentWriter interface definition - - /// - /// Interface for writing fixed document parts to the xps package. - /// - public interface IXpsFixedDocumentWriter: - IDocumentStructureProvider - { - #region Public methods - - /// - /// This method adds a fixed page part to the Xps package - /// and associates it with the current fixed document. - /// - /// - /// Returns an interface to the newly created fixed page. - /// - /// The FixedDocument has already been disposed - /// FixedPage is not completed. - IXpsFixedPageWriter - AddFixedPage( - ); - - /// - /// This method adds a thumbnail to the current DocumentSequence. - /// - /// There can only be one thumbnail attached to the DocumentSequence. - /// Calling this method when there - /// is already a starting part causes InvalidOperationException to be - /// thrown. - /// - /// Returns a XpsThumbnail instance. - XpsThumbnail - AddThumbnail( - XpsImageType imageType - ); - - /// - /// This method commits any changes not already committed for this - /// fixed document. - /// - /// - void - Commit( - ); - - #endregion Public methods - - #region Public properties - - /// - /// Sets the PrintTicket associated with this fixed document. - /// - /// - /// The value must be a valid PrintTicket instance. - /// - /// Note: The PrintTicket can only be assigned to prior to if being - /// committed to the package. The commit happens when a valid PrintTicket - /// is set and a subsequent flush on the document occurs. - /// - /// PrintTicket has already been committed. - /// Property is not a valid PrintTicket instance. - PrintTicket PrintTicket { set; } - - /// - /// Gets the URI assigned to this fixed document part. - /// - /// Value is a URI for the Metro part. - Uri Uri { get; } - - /// - /// 0 based document number in the document sequence - /// - int DocumentNumber{ get; } - - #endregion Public properties - } - - #endregion IXpsFixedDocumentWriter interface definition - - /// - /// This class implements the reading and writing functionality for - /// a fixed document within a Xps package. - /// - internal sealed class XpsFixedDocumentReaderWriter : XpsPartBase, - IXpsFixedDocumentReader, - IXpsFixedDocumentWriter, - INode, - IDisposable - { - #region Constructors - - /// - /// Internal constructor for the XpsFixedDocumentReaderWriter class. - /// This class is created internally so we can keep the Xps hierarchy - /// completely under control when using these APIs. - /// - /// - /// The XpsManager for the current Xps package. - /// - /// - /// The parent node of this fixed document. - /// - /// - /// The internal Metro part that represents this fixed document. - /// - /// - /// The 0 base document number in the document sequence - /// - /// part is null. - internal - XpsFixedDocumentReaderWriter( - XpsManager xpsManager, - INode parent, - PackagePart part, - int documentNumber - ) - : base(xpsManager) - { - if (null == part) - { - throw new ArgumentNullException("part"); - } - - this.Uri = part.Uri; - - _metroPart = part; - - _partEditor = new XmlPartEditor(_metroPart); - - _pageCache = new List(); - - _pagesWritten = 0; - - _parentNode = parent; - - _hasParsedPages = false; - - _documentNumber = documentNumber; - } - - #endregion Constructors - - #region Public properties - - /// - /// Gets or sets the PrintTicket to be stored with this fixed document. - /// - /// NOTE: The PrintTicket can only be assigned to prior to if being - /// committed to the package. The commit happens when a valid PrintTicket - /// is set and a subsequent flush on the document occurs. - /// - /// Value can be a PrintTicket or null. - /// PrintTicket has already been committed. - /// Property is not a valid PrintTicket instance. - public PrintTicket PrintTicket - { - get - { - if( _printTicket == null ) - { - _printTicket = CurrentXpsManager.EnsurePrintTicket( Uri ); - } - return _printTicket; - } - set - { - if(value != null) - { - if (_isPrintTicketCommitted) - { - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PrintTicketAlreadyCommitted)); - } - if (!value.GetType().Equals(typeof(PrintTicket))) - { - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_NotAPrintTicket)); - } - - _printTicket = value.Clone(); - } - else - { - _printTicket = null; - } - } - } - - /// - /// Gets a collection of all fixed pages that are contained within - /// this fixed document. - /// - /// - /// Value is a Collection containing IXpsFixedPageReader interfaces. - /// - /// - /// This is only internal until it is implemented. - /// - public ReadOnlyCollection FixedPages - { - get - { - UpdatePageCache(); - return new ReadOnlyCollection(_pageCache ); - } - } - - /// - /// A list of signature definitions associated with the document - /// - public - ICollection - SignatureDefinitions - { - get - { - EnsureSignatureDefinitions(); - return _signatureDefinitions; - } - } - - /// - /// 0 based document number in the document sequence - /// - public int DocumentNumber - { - get - { - return _documentNumber; - } - } - - public - XpsThumbnail - Thumbnail - { - get - { - EnsureThumbnail(); - return _thumbnail; - } - } - - public - XpsStructure - DocumentStructure - { - get - { - EnsureDoucmentStructure(); - return _documentStructure; - } - } - - #endregion Public properties - - #region Public methods - /// - /// This method adds a fixed page part to the Xps package - /// and associates it with the current fixed document. - /// - /// - /// Returns an interface to the newly created fixed page. - /// - /// The FixedDocument has already been disposed - /// FixedPage is not completed. - public - IXpsFixedPageWriter - AddFixedPage( - ) - { - if (null == _metroPart || null == CurrentXpsManager.MetroPackage) - { - throw new ObjectDisposedException("XpsFixedDocumentReaderWriter"); - } - - // - // Only one page can be created/written at a time. - // - if (null != _currentPage) - { - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PanelOrSequenceAlreadyOpen)); - } - - - _linkTargetStream = new List(); - - // - // Create the part and writer - // - PackagePart metroPart = this.CurrentXpsManager.GenerateUniquePart(XpsS0Markup.FixedPageContentType); - XpsFixedPageReaderWriter fixedPage = new XpsFixedPageReaderWriter(CurrentXpsManager, this, metroPart, _linkTargetStream, _pagesWritten + 1); - - // - // Make the new page the current page - // - _currentPage = fixedPage; - - - //Here we used to add the fixed page to _pageCache, but _pageCache is never accessed if this object was created as an IXpsFixedDocumentWriter. - //So instead keep a separate pagesWritten count and forget about the cache when using this method. - _pagesWritten++; - return fixedPage; - } - - /// - /// This method adds a thumbnail to the current DocumentSequence. - /// - /// There can only be one thumbnail attached to the DocumentSequence. - /// Calling this method when there - /// is already a starting part causes InvalidOperationException to be - /// thrown. - /// - /// Returns a XpsThumbnail instance. - public - XpsThumbnail - AddThumbnail( - XpsImageType imageType - ) - { - _thumbnail = CurrentXpsManager.AddThumbnail( imageType, this, Thumbnail ); - _metroPart.CreateRelationship( _thumbnail.Uri, - TargetMode.Internal, - XpsS0Markup.ThumbnailRelationshipName - ); - return _thumbnail; - } - - /// - /// - public - XpsStructure - AddDocumentStructure( - ) - { - if (this.DocumentStructure != null) - { - // Document structure already available for this FixedDocument - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_MoreThanOneDocStructure)); - } - - Uri pageUri = this.CurrentXpsManager.CreateStructureUri(); - // - // Create the part and writer - // - PackagePart metroPart = this.CurrentXpsManager.GeneratePart( - XpsS0Markup.DocumentStructureContentType, - pageUri); - - _documentStructure = new XpsStructure(CurrentXpsManager, this, metroPart); - - // - // Create the relationship between the document and the document-structure - // Not in INode.Flush because IXpsFixedDocumentReader has no commit. - // - - string structurePath = XpsManager.MakeRelativePath(this.Uri, _documentStructure.Uri); - - _metroPart.CreateRelationship(new Uri(structurePath, UriKind.Relative), - TargetMode.Internal, - XpsS0Markup.StructureRelationshipName - ); - - return _documentStructure; - } - - /// - /// This method adds a relationship for this part that - /// targets the specified Uri and is based on the specified - /// content type. - /// - /// - /// Uri target for relationship. - /// - /// - /// Relationship type to add. - /// - public - void - AddRelationship( - Uri targetUri, - string relationshipName - ) - { - // - // We can not read from the file to do validation - // when streaming - // - if( !CurrentXpsManager.Streaming ) - { - foreach (PackageRelationship rel in _metroPart.GetRelationships()) - { - if (rel.TargetUri.Equals(targetUri)) - { - // - // Relationship already exists - // - return; - } - } - } - - // - // Add the relationship using a relative path to this page. - // - string relativePath = XpsManager.MakeRelativePath(this.Uri, targetUri); - _metroPart.CreateRelationship(new Uri(relativePath, UriKind.Relative), - TargetMode.Internal, - relationshipName); - } - - /// - /// This method retrieves a fixed page part from a Xps - /// package using the given URI. - /// - /// - /// The URI of the fixed page to retrieve. - /// - /// - /// Returns an interface to an IXpsFixedPageReader to read - /// the requested fixed page. - /// - public - IXpsFixedPageReader - GetFixedPage( - Uri pageUri - ) - { - UpdatePageCache(); - IXpsFixedPageReader pageReader = null; - - foreach (IXpsFixedPageReader reader in _pageCache ) - { - if( reader.Uri == pageUri ) - { - pageReader = reader; - } - } - - return pageReader; - } - - public - void - AddSignatureDefinition( - XpsSignatureDefinition signatureDefinition - ) - { - EnsureSignatureDefinitions(); - _signatureDefinitions.Add( signatureDefinition ); - _sigCollectionDirty = true; -} - - public - void - CommitSignatureDefinition() - { - bool isDirty = false; - - // - // if the collection is dirty not point in testing - // each signature. - // - if( !_sigCollectionDirty ) - { - foreach( XpsSignatureDefinition sigDef in _signatureDefinitions ) - { - if( sigDef.HasBeenModified ) - { - isDirty = true; - break; - } - } - } - if( isDirty || _sigCollectionDirty ) - { - WriteSignatureDefinitions(); - } - } - - /// - /// This method removes a signature definitions associated with - /// the FixedDocument - /// - /// - /// Signature Definition to remove - /// - public - void - RemoveSignatureDefinition(XpsSignatureDefinition signatureDefinition) - { - EnsureSignatureDefinitions(); - _signatureDefinitions.Remove( signatureDefinition ); - _sigCollectionDirty = true; -} - - /// - /// This method commits any changes not already committed for this - /// fixed document. - /// - /// NOTE: This commits changes to all child object under this - /// branch of the tree. No further changes will be allowed. - /// - public - void - Commit( - ) - { - CommitInternal(); - } - - /// - /// This method closes streams and frees memory for this - /// fixed document. - /// - internal - override - void - CommitInternal( - ) - { - CommitPrintTicket(); - if (null != _partEditor) - { - if (null != _partEditor.XmlWriter) - { - if(_partEditor.DoesWriteStartEndTags) - { - if(_partEditor.IsStartElementWritten) - { - _partEditor.XmlWriter.WriteEndElement(); - _partEditor.XmlWriter.WriteEndDocument(); - } - } - } - - ((INode)this).Flush(); - _partEditor.Close(); - - - _partEditor = null; - _metroPart = null; - - _parentNode = null; - - _thumbnail = null; - - _pageCache = null; - - _pagesWritten = 0; - - _hasParsedPages = false; - } - - base.CommitInternal(); - } - - - - /// - /// Adds itself and and its reationship if it exists - /// Adds dependent part Uris to the passed list following the passed restrictions - /// dependents include pages, annotaions, properties, and signatures - /// - internal - void - CollectSelfAndDependents( - Dictionary dependentList, - List selectorList, - XpsDigSigPartAlteringRestrictions restrictions - ) - { - // - // Add this document - // - dependentList[Uri] = Uri; - - // - // Add Signature Definition if it exists - // - PackagePart signatureDefinitionPart = - CurrentXpsManager.GetSignatureDefinitionPart(Uri); - - // - // Add Signature Definitions - // - selectorList.Add( new PackageRelationshipSelector( - Uri, - PackageRelationshipSelectorType.Type, - XpsS0Markup.SignatureDefinitionRelationshipName - ) - ); - - - if( signatureDefinitionPart != null ) - { - dependentList[signatureDefinitionPart.Uri] = signatureDefinitionPart.Uri; - } - // - // Add Restricted Font relationship - // - selectorList.Add( new PackageRelationshipSelector( - Uri, - PackageRelationshipSelectorType.Type, - XpsS0Markup.RestrictedFontRelationshipType - ) - ); - // - // Add Document Structure relationship - // - selectorList.Add( new PackageRelationshipSelector( - Uri, - PackageRelationshipSelectorType.Type, - XpsS0Markup.StructureRelationshipName - ) - ); - // - // Add this documents dependants - // - CollectDependents( dependentList, selectorList, restrictions); -} - - internal - void - CollectXmlPartsAndDepenedents( - List xmlPartList - ) - { - // - // Add my self to be tested for V&E Markup - // - xmlPartList.Add(_metroPart); - UpdatePageCache(); - // - // Add all pages - // - foreach (IXpsFixedPageReader reader in _pageCache) - { - (reader as XpsFixedPageReaderWriter).CollectXmlPartsAndDepenedents(xmlPartList); - } - // - // Add DocumentStructure - // - EnsureDoucmentStructure(); - if (_documentStructure != null) - { - // - // Add my DocumentStructure to be tested for V&E Markup - // - xmlPartList.Add((_documentStructure as INode).GetPart()); - } - // - // Add Signature Definition if it exists - // - PackagePart signatureDefinitionPart = - CurrentXpsManager.GetSignatureDefinitionPart(Uri); - if (signatureDefinitionPart != null) - { - // - // Add my signatureDefinitionPart to be tested for V&E Markup - // - xmlPartList.Add(signatureDefinitionPart); - } - } - - /// - /// Adds dependent part Uris to the passed list - /// - internal - void - CollectDependents( - Dictionary dependents, - List selectorList, - XpsDigSigPartAlteringRestrictions restrictions - ) - { - UpdatePageCache(); - // - // Add all pages - // - foreach( IXpsFixedPageReader reader in _pageCache) - { - (reader as XpsFixedPageReaderWriter). - CollectSelfAndDependents( - dependents, - selectorList, - restrictions - ); - } - - // - // Add DocumentStructure - // - EnsureDoucmentStructure(); - if( _documentStructure != null ) - { - dependents[_documentStructure.Uri] = _documentStructure.Uri; - } - } - - #endregion Public methods - - #region Private methods - - private - void - AddPageToDocument( - Uri partUri, - IList linkTargetStream - ) - { - _partEditor.PrepareXmlWriter(XpsS0Markup.FixedDocument, XpsS0Markup.FixedDocumentNamespace); - XmlTextWriter xmlWriter = _partEditor.XmlWriter; - // - // Write - // - String relativePath = XpsManager.MakeRelativePath(Uri, partUri); - - xmlWriter.WriteStartElement(XpsS0Markup.PageContent); - xmlWriter.WriteAttributeString(XmlTags.Source, relativePath); - - // - // Write out link targets if necessary - // - if (linkTargetStream.Count != 0) - { - xmlWriter.WriteRaw (""); - foreach (String nameElement in linkTargetStream) - { - xmlWriter.WriteRaw (String.Format( - System.Globalization.CultureInfo.InvariantCulture, - "", - nameElement) - ); - } - xmlWriter.WriteRaw (""); - } - - xmlWriter.WriteEndElement(); - } - /// - /// This method writes the PrintTicket associated with - /// this fixed document to the Metro package. - /// - private - void - CommitPrintTicket( - ) - { - // - // Flush the PrintTicket if needed - // - if (!_isPrintTicketCommitted ) - { - if(null != _printTicket) - { - CurrentXpsManager.WritePrintTicket(this,_metroPart, _printTicket); - } - else - { - CurrentXpsManager.WriteEmptyPrintTicket(this, _metroPart); - } - _isPrintTicketCommitted = true; - } - } - - /// - /// CurrentPageCommitted signals that our current page - /// is complete and we can write out any associated link - /// targets and end the PageContent element. - /// - internal - void - CurrentPageCommitted() - { - if( _currentPage != null ) - { - //Write out the fixed page tag - AddPageToDocument(_currentPage.Uri, _linkTargetStream); - _currentPage = null; - } - } - - /// - /// Test if the page cache has been initialized and - /// updates it if necessary - /// - private - void - UpdatePageCache() - { - if( !_hasParsedPages ) - { - ParsePages(); - _hasParsedPages = true; - } - } - /// - /// This method parses the part pulling out the Page Referneces - /// and populates the _pageCache - /// - private - void - ParsePages() - { - using (Stream stream = _metroPart.GetStream(FileMode.Open)) - { - // - // If the stream is empty there are no pages to parse - // - if (stream.Length > 0) - { - XmlTextReader reader = new XmlTextReader(stream); - - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element && reader.Name == XpsS0Markup.PageContent) - { - string attribute = reader.GetAttribute(XmlTags.Source); - if (attribute != null) - { - Uri relativeUri = new Uri(attribute, UriKind.Relative); - AddPageToCache(PackUriHelper.ResolvePartUri(Uri, relativeUri)); - } - } - } - } - } - } - - private - IXpsFixedPageReader - AddPageToCache( Uri pageUri ) - { - PackagePart pagePart = CurrentXpsManager.GetPart(pageUri); - - if (pagePart == null) - { - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PartNotFound)); - } - - if (!pagePart.ValidatedContentType().AreTypeAndSubTypeEqual(XpsS0Markup.FixedPageContentType)) - { - throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_NotAFixedPage)); - } - - // - // Create the reader/writer for the part - // - IXpsFixedPageReader pageReader = new XpsFixedPageReaderWriter(CurrentXpsManager, this, pagePart, null, _pageCache.Count+1); - - // - // Cache the new reader/writer for later - // - _pageCache.Add(pageReader ); - - return pageReader; - } - - private - void - EnsureThumbnail() - { - if( _thumbnail == null ) - { - _thumbnail = CurrentXpsManager.EnsureThumbnail( this, _metroPart ); - } - } - - - private - void - EnsureSignatureDefinitions() - { - // if _xpsSignaturs is not null we have already initialized this - // - if( null != _signatureDefinitions) - { - return; - } - _signatureDefinitions = new Collection(); - PackagePart sigDefPart = - CurrentXpsManager.GetSignatureDefinitionPart(Uri); - if( sigDefPart != null ) - { - ParseSignaturePart( sigDefPart, _signatureDefinitions ); - } - } - - private - void - EnsureDoucmentStructure() - { - // if _xpsSignaturs is not null we have already initialized this - // - if( null != _documentStructure) - { - return; - } - PackageRelationship documentStructureRelationship = null; - PackagePart documentStructurePart = null; - - foreach (PackageRelationship rel in _metroPart.GetRelationshipsByType(XpsS0Markup.StructureRelationshipName)) - { - if (documentStructureRelationship != null) - { - throw new InvalidDataException(SR.Get(SRID.ReachPackaging_MoreThanOneDocStructure)); - } - - documentStructureRelationship = rel; - } - - if (documentStructureRelationship != null) - { - Uri documentStructureUri = PackUriHelper.ResolvePartUri(documentStructureRelationship.SourceUri, - documentStructureRelationship.TargetUri); - - if (CurrentXpsManager.MetroPackage.PartExists(documentStructureUri)) - { - documentStructurePart = CurrentXpsManager.MetroPackage.GetPart(documentStructureUri); - _documentStructure = new XpsStructure(CurrentXpsManager, this, documentStructurePart); - } - } - } - private - void - ParseSignaturePart( - PackagePart sigDefPart, - Collection sigDefCollection - ) - { - using (XmlTextReader reader = new XmlTextReader(sigDefPart.GetStream(FileMode.Open))) - { - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element && - reader.Name == XpsS0Markup.SignatureDefinitions - ) - { - ParseSignatureDefinitions(reader, sigDefCollection); - } - } - } - } - - private - void - ParseSignatureDefinitions( - XmlReader reader, - Collection sigDefCollection - ) - { - bool endLoop = false; - while (!endLoop && reader.Read()) - { - if( reader.NodeType == XmlNodeType.Element && - reader.Name == XpsS0Markup.SignatureDefinition - ) - { - XpsSignatureDefinition sigDef = new XpsSignatureDefinition(); - sigDef.ReadXML(reader); - sigDefCollection.Add( sigDef ); - } - - if( reader.NodeType == XmlNodeType.EndElement && - reader.Name == XpsS0Markup.SignatureDefinitions - ) - { - endLoop = true; - } - } - } - - private - void - WriteSignatureDefinitions() - { - PackagePart sigDefPart = - CurrentXpsManager.GetSignatureDefinitionPart(Uri); - if( sigDefPart == null ) - { - sigDefPart = CurrentXpsManager.AddSignatureDefinitionPart( _metroPart ); - } - - using (Stream stream = sigDefPart.GetStream(FileMode.Create)) - using (XmlTextWriter writer = new XmlTextWriter(stream, System.Text.Encoding.UTF8)) - { - writer.WriteStartDocument(); - writer.WriteStartElement(XpsS0Markup.SignatureDefinitions, - XpsS0Markup.SignatureDefinitionNamespace); - foreach (XpsSignatureDefinition sigDef in _signatureDefinitions) - { - sigDef.WriteXML(writer); - } - writer.WriteEndElement(); - } - - _sigCollectionDirty = false; - } - - - - #endregion Private methods - - #region Private data - - private PackagePart _metroPart; - private PrintTicket _printTicket; - - private XmlPartEditor _partEditor; - - private IList _linkTargetStream; - - private List _pageCache; - - // - // This variable is used to keep a count of pages written via AddFixedPage - // - private int _pagesWritten; - - // - // This variable flags whether the PrintTicket property is - // committed. A writer can only commit this property once. - // - private bool _isPrintTicketCommitted; - - // - // These variables are used to keep track of the parent - // and current child of this node for walking up and - // down the current tree. This is used be the flushing - // policy to do interleave flushing of parts correctly. - // - private INode _parentNode; - - // - // This variable flags wehter the pageCashe - // has been populated by parsing the part for dependent pages - // - private bool _hasParsedPages; - - // - // 0 based document number in the document sequence - // - private int _documentNumber; - - private XpsThumbnail _thumbnail; - - - // - // Since the current page may add link target information - // we must track our current page - // for this reason we can not handle adding new page - // until the the current page has been committed - // - private XpsFixedPageReaderWriter _currentPage; - // - // A cached list of Signature Definitions - // - private Collection _signatureDefinitions; - // - // Boolean indicating whetehr _signatureDefinitions collection - // has been changed - // - private bool _sigCollectionDirty; - - private XpsStructure _documentStructure; - #endregion Private data - - #region Internal properties - - /// - /// Gets a reference to the XmlWriter for the Metro part - /// that represents this fixed document. - /// - internal System.Xml.XmlWriter XmlWriter - { - get - { - _partEditor.DoesWriteStartEndTags = false; - return _partEditor.XmlWriter; - } - } - - #endregion Internal properties - - #region INode implementation - - void - INode.Flush( - ) - { - // - // Commit the PrintTicket (if necessary) - // - CommitPrintTicket(); - - // - // Create the relationship between the package and the tumbnail - // - if( _thumbnail != null ) - { - _thumbnail = null; - } - } - - void - INode.CommitInternal( - ) - { - CommitInternal(); - } - - PackagePart - INode.GetPart( - ) - { - return _metroPart; - } - - #endregion INode implementation - - #region IDisposable implementation - - void - IDisposable.Dispose( - ) - { - CommitInternal(); - } - - #endregion IDisposable implementation - } -} - +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*++ + + + + Abstract: + This file contains the definition for IXpsFixedDocumentReader + and IXpsFixedDocumentWriter interfaces as well as definition + and implementation of XpsFixedDocumentReaderWriter. These + interfaces and class are used for writing fixed document + parts to a Xps package. + + + +--*/ +using System; +using System.Collections; +using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.IO; +using System.IO.Packaging; +using System.Xml; +using System.Security.Cryptography.X509Certificates; +using System.Printing; + +using MS.Internal.IO.Packaging.Extensions; +using PackageRelationship = System.IO.Packaging.PackageRelationship; +using PackUriHelper = System.IO.Packaging.PackUriHelper; + +namespace System.Windows.Xps.Packaging +{ + #region IXpsFixedDocumentReader interface definition + + /// + /// Interface for reading fixed document parts from the Xps package. + /// + /// Interface will be internal until reading/de-serialization is implemented. + public interface IXpsFixedDocumentReader: + IDocumentStructureProvider + { + #region Public methods + + /// + /// This method retrieves a fixed page part from a Xps + /// package using the given URI. + /// + /// + /// The URI of the fixed page to retrieve. + /// + /// + /// Returns an interface to an IXpsFixedPageReader to read + /// the requested fixed page. + /// + IXpsFixedPageReader + GetFixedPage( + Uri pageSource + ); + + #endregion Public methods + + #region Public properties + + /// + /// Gets the PrintTicket associated with this fixed document. + /// + /// Value can be a PrintTicket or null. + /// PrintTicket has already been committed. + /// Property is not a valid PrintTicket instance. + PrintTicket PrintTicket { get; } + + /// + /// Gets the URI assigned to this fixed document part. + /// + /// Value is a URI for the Metro part. + Uri Uri { get; } + + /// + /// Gets a collection of all fixed pages that are contained within + /// this fixed document. + /// + /// + /// Value is a Collection containing IXpsFixedPageReader interfaces. + /// + ReadOnlyCollection FixedPages { get; } + + /// + /// 0 based document number in the document sequence + /// + int DocumentNumber{ get; } + + /// + /// A list of signature definitions associated with the document + /// + ICollection + SignatureDefinitions{ get; } + + /// + /// thumbnail image associated with this doucment + /// + XpsThumbnail + Thumbnail{ get; } + + /// + /// Document Structure associated with this doucment + /// + XpsStructure + DocumentStructure{ get; } + + + #endregion Public properties + + /// + /// Adds the passed Signature Definiton to the cached + /// Signature Definition list + /// Will not be written until FlushSignatureDefinition + /// is called + /// + /// + /// the Signature Definition to be added + /// + void + AddSignatureDefinition( + XpsSignatureDefinition signatureDefinition + ); + + /// + /// This method removes a signature definitions associated with + /// the FixedDocument + /// + /// + /// Signature Definition to remove + /// + void + RemoveSignatureDefinition(XpsSignatureDefinition signatureDefinition); + + /// + /// Writes out all modifications to Signature + /// Definitions as well as new Signature Definitions + /// + void + CommitSignatureDefinition(); + } + + #endregion IXpsFixedDocumentReader interface definition + + #region IXpsFixedDocumentWriter interface definition + + /// + /// Interface for writing fixed document parts to the xps package. + /// + public interface IXpsFixedDocumentWriter: + IDocumentStructureProvider + { + #region Public methods + + /// + /// This method adds a fixed page part to the Xps package + /// and associates it with the current fixed document. + /// + /// + /// Returns an interface to the newly created fixed page. + /// + /// The FixedDocument has already been disposed + /// FixedPage is not completed. + IXpsFixedPageWriter + AddFixedPage( + ); + + /// + /// This method adds a thumbnail to the current DocumentSequence. + /// + /// There can only be one thumbnail attached to the DocumentSequence. + /// Calling this method when there + /// is already a starting part causes InvalidOperationException to be + /// thrown. + /// + /// Returns a XpsThumbnail instance. + XpsThumbnail + AddThumbnail( + XpsImageType imageType + ); + + /// + /// This method commits any changes not already committed for this + /// fixed document. + /// + /// + void + Commit( + ); + + #endregion Public methods + + #region Public properties + + /// + /// Sets the PrintTicket associated with this fixed document. + /// + /// + /// The value must be a valid PrintTicket instance. + /// + /// Note: The PrintTicket can only be assigned to prior to if being + /// committed to the package. The commit happens when a valid PrintTicket + /// is set and a subsequent flush on the document occurs. + /// + /// PrintTicket has already been committed. + /// Property is not a valid PrintTicket instance. + PrintTicket PrintTicket { set; } + + /// + /// Gets the URI assigned to this fixed document part. + /// + /// Value is a URI for the Metro part. + Uri Uri { get; } + + /// + /// 0 based document number in the document sequence + /// + int DocumentNumber{ get; } + + #endregion Public properties + } + + #endregion IXpsFixedDocumentWriter interface definition + + /// + /// This class implements the reading and writing functionality for + /// a fixed document within a Xps package. + /// + internal sealed class XpsFixedDocumentReaderWriter : XpsPartBase, + IXpsFixedDocumentReader, + IXpsFixedDocumentWriter, + INode, + IDisposable + { + #region Constructors + + /// + /// Internal constructor for the XpsFixedDocumentReaderWriter class. + /// This class is created internally so we can keep the Xps hierarchy + /// completely under control when using these APIs. + /// + /// + /// The XpsManager for the current Xps package. + /// + /// + /// The parent node of this fixed document. + /// + /// + /// The internal Metro part that represents this fixed document. + /// + /// + /// The 0 base document number in the document sequence + /// + /// part is null. + internal + XpsFixedDocumentReaderWriter( + XpsManager xpsManager, + INode parent, + PackagePart part, + int documentNumber + ) + : base(xpsManager) + { + if (null == part) + { + throw new ArgumentNullException("part"); + } + + this.Uri = part.Uri; + + _metroPart = part; + + _partEditor = new XmlPartEditor(_metroPart); + + _pageCache = new List(); + + _pagesWritten = 0; + + _parentNode = parent; + + _hasParsedPages = false; + + _documentNumber = documentNumber; + } + + #endregion Constructors + + #region Public properties + + /// + /// Gets or sets the PrintTicket to be stored with this fixed document. + /// + /// NOTE: The PrintTicket can only be assigned to prior to if being + /// committed to the package. The commit happens when a valid PrintTicket + /// is set and a subsequent flush on the document occurs. + /// + /// Value can be a PrintTicket or null. + /// PrintTicket has already been committed. + /// Property is not a valid PrintTicket instance. + public PrintTicket PrintTicket + { + get + { + if( _printTicket == null ) + { + _printTicket = CurrentXpsManager.EnsurePrintTicket( Uri ); + } + return _printTicket; + } + set + { + if(value != null) + { + if (_isPrintTicketCommitted) + { + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PrintTicketAlreadyCommitted)); + } + if (!value.GetType().Equals(typeof(PrintTicket))) + { + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_NotAPrintTicket)); + } + + _printTicket = value.Clone(); + } + else + { + _printTicket = null; + } + } + } + + /// + /// Gets a collection of all fixed pages that are contained within + /// this fixed document. + /// + /// + /// Value is a Collection containing IXpsFixedPageReader interfaces. + /// + /// + /// This is only internal until it is implemented. + /// + public ReadOnlyCollection FixedPages + { + get + { + UpdatePageCache(); + return new ReadOnlyCollection(_pageCache ); + } + } + + /// + /// A list of signature definitions associated with the document + /// + public + ICollection + SignatureDefinitions + { + get + { + EnsureSignatureDefinitions(); + return _signatureDefinitions; + } + } + + /// + /// 0 based document number in the document sequence + /// + public int DocumentNumber + { + get + { + return _documentNumber; + } + } + + public + XpsThumbnail + Thumbnail + { + get + { + EnsureThumbnail(); + return _thumbnail; + } + } + + public + XpsStructure + DocumentStructure + { + get + { + EnsureDoucmentStructure(); + return _documentStructure; + } + } + + #endregion Public properties + + #region Public methods + /// + /// This method adds a fixed page part to the Xps package + /// and associates it with the current fixed document. + /// + /// + /// Returns an interface to the newly created fixed page. + /// + /// The FixedDocument has already been disposed + /// FixedPage is not completed. + public + IXpsFixedPageWriter + AddFixedPage( + ) + { + if (null == _metroPart || null == CurrentXpsManager.MetroPackage) + { + throw new ObjectDisposedException("XpsFixedDocumentReaderWriter"); + } + + // + // Only one page can be created/written at a time. + // + if (null != _currentPage) + { + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PanelOrSequenceAlreadyOpen)); + } + + + _linkTargetStream = new List(); + + // + // Create the part and writer + // + PackagePart metroPart = this.CurrentXpsManager.GenerateUniquePart(XpsS0Markup.FixedPageContentType); + XpsFixedPageReaderWriter fixedPage = new XpsFixedPageReaderWriter(CurrentXpsManager, this, metroPart, _linkTargetStream, _pagesWritten + 1); + + // + // Make the new page the current page + // + _currentPage = fixedPage; + + + //Here we used to add the fixed page to _pageCache, but _pageCache is never accessed if this object was created as an IXpsFixedDocumentWriter. + //So instead keep a separate pagesWritten count and forget about the cache when using this method. + _pagesWritten++; + return fixedPage; + } + + /// + /// This method adds a thumbnail to the current DocumentSequence. + /// + /// There can only be one thumbnail attached to the DocumentSequence. + /// Calling this method when there + /// is already a starting part causes InvalidOperationException to be + /// thrown. + /// + /// Returns a XpsThumbnail instance. + public + XpsThumbnail + AddThumbnail( + XpsImageType imageType + ) + { + _thumbnail = CurrentXpsManager.AddThumbnail( imageType, this, Thumbnail ); + _metroPart.CreateRelationship( _thumbnail.Uri, + TargetMode.Internal, + XpsS0Markup.ThumbnailRelationshipName + ); + return _thumbnail; + } + + /// + /// + public + XpsStructure + AddDocumentStructure( + ) + { + if (this.DocumentStructure != null) + { + // Document structure already available for this FixedDocument + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_MoreThanOneDocStructure)); + } + + Uri pageUri = this.CurrentXpsManager.CreateStructureUri(); + // + // Create the part and writer + // + PackagePart metroPart = this.CurrentXpsManager.GeneratePart( + XpsS0Markup.DocumentStructureContentType, + pageUri); + + _documentStructure = new XpsStructure(CurrentXpsManager, this, metroPart); + + // + // Create the relationship between the document and the document-structure + // Not in INode.Flush because IXpsFixedDocumentReader has no commit. + // + + string structurePath = XpsManager.MakeRelativePath(this.Uri, _documentStructure.Uri); + + _metroPart.CreateRelationship(new Uri(structurePath, UriKind.Relative), + TargetMode.Internal, + XpsS0Markup.StructureRelationshipName + ); + + return _documentStructure; + } + + /// + /// This method adds a relationship for this part that + /// targets the specified Uri and is based on the specified + /// content type. + /// + /// + /// Uri target for relationship. + /// + /// + /// Relationship type to add. + /// + public + void + AddRelationship( + Uri targetUri, + string relationshipName + ) + { + // + // We can not read from the file to do validation + // when streaming + // + if( !CurrentXpsManager.Streaming ) + { + foreach (PackageRelationship rel in _metroPart.GetRelationships()) + { + if (rel.TargetUri.Equals(targetUri)) + { + // + // Relationship already exists + // + return; + } + } + } + + // + // Add the relationship using a relative path to this page. + // + string relativePath = XpsManager.MakeRelativePath(this.Uri, targetUri); + _metroPart.CreateRelationship(new Uri(relativePath, UriKind.Relative), + TargetMode.Internal, + relationshipName); + } + + /// + /// This method retrieves a fixed page part from a Xps + /// package using the given URI. + /// + /// + /// The URI of the fixed page to retrieve. + /// + /// + /// Returns an interface to an IXpsFixedPageReader to read + /// the requested fixed page. + /// + public + IXpsFixedPageReader + GetFixedPage( + Uri pageUri + ) + { + UpdatePageCache(); + IXpsFixedPageReader pageReader = null; + + foreach (IXpsFixedPageReader reader in _pageCache ) + { + if( reader.Uri == pageUri ) + { + pageReader = reader; + } + } + + return pageReader; + } + + public + void + AddSignatureDefinition( + XpsSignatureDefinition signatureDefinition + ) + { + EnsureSignatureDefinitions(); + _signatureDefinitions.Add( signatureDefinition ); + _sigCollectionDirty = true; +} + + public + void + CommitSignatureDefinition() + { + bool isDirty = false; + + // + // if the collection is dirty not point in testing + // each signature. + // + if( !_sigCollectionDirty ) + { + foreach( XpsSignatureDefinition sigDef in _signatureDefinitions ) + { + if( sigDef.HasBeenModified ) + { + isDirty = true; + break; + } + } + } + if( isDirty || _sigCollectionDirty ) + { + WriteSignatureDefinitions(); + } + } + + /// + /// This method removes a signature definitions associated with + /// the FixedDocument + /// + /// + /// Signature Definition to remove + /// + public + void + RemoveSignatureDefinition(XpsSignatureDefinition signatureDefinition) + { + EnsureSignatureDefinitions(); + _signatureDefinitions.Remove( signatureDefinition ); + _sigCollectionDirty = true; +} + + /// + /// This method commits any changes not already committed for this + /// fixed document. + /// + /// NOTE: This commits changes to all child object under this + /// branch of the tree. No further changes will be allowed. + /// + public + void + Commit( + ) + { + CommitInternal(); + } + + /// + /// This method closes streams and frees memory for this + /// fixed document. + /// + internal + override + void + CommitInternal( + ) + { + CommitPrintTicket(); + if (null != _partEditor) + { + if (null != _partEditor.XmlWriter) + { + if(_partEditor.DoesWriteStartEndTags) + { + if(_partEditor.IsStartElementWritten) + { + _partEditor.XmlWriter.WriteEndElement(); + _partEditor.XmlWriter.WriteEndDocument(); + } + } + } + + ((INode)this).Flush(); + _partEditor.Close(); + + + _partEditor = null; + _metroPart = null; + + _parentNode = null; + + _thumbnail = null; + + _pageCache = null; + + _pagesWritten = 0; + + _hasParsedPages = false; + } + + base.CommitInternal(); + } + + + + /// + /// Adds itself and and its reationship if it exists + /// Adds dependent part Uris to the passed list following the passed restrictions + /// dependents include pages, annotaions, properties, and signatures + /// + internal + void + CollectSelfAndDependents( + Dictionary dependentList, + List selectorList, + XpsDigSigPartAlteringRestrictions restrictions + ) + { + // + // Add this document + // + dependentList[Uri] = Uri; + + // + // Add Signature Definition if it exists + // + PackagePart signatureDefinitionPart = + CurrentXpsManager.GetSignatureDefinitionPart(Uri); + + // + // Add Signature Definitions + // + selectorList.Add( new PackageRelationshipSelector( + Uri, + PackageRelationshipSelectorType.Type, + XpsS0Markup.SignatureDefinitionRelationshipName + ) + ); + + + if( signatureDefinitionPart != null ) + { + dependentList[signatureDefinitionPart.Uri] = signatureDefinitionPart.Uri; + } + // + // Add Restricted Font relationship + // + selectorList.Add( new PackageRelationshipSelector( + Uri, + PackageRelationshipSelectorType.Type, + XpsS0Markup.RestrictedFontRelationshipType + ) + ); + // + // Add Document Structure relationship + // + selectorList.Add( new PackageRelationshipSelector( + Uri, + PackageRelationshipSelectorType.Type, + XpsS0Markup.StructureRelationshipName + ) + ); + // + // Add this documents dependants + // + CollectDependents( dependentList, selectorList, restrictions); +} + + internal + void + CollectXmlPartsAndDepenedents( + List xmlPartList + ) + { + // + // Add my self to be tested for V&E Markup + // + xmlPartList.Add(_metroPart); + UpdatePageCache(); + // + // Add all pages + // + foreach (IXpsFixedPageReader reader in _pageCache) + { + (reader as XpsFixedPageReaderWriter).CollectXmlPartsAndDepenedents(xmlPartList); + } + // + // Add DocumentStructure + // + EnsureDoucmentStructure(); + if (_documentStructure != null) + { + // + // Add my DocumentStructure to be tested for V&E Markup + // + xmlPartList.Add((_documentStructure as INode).GetPart()); + } + // + // Add Signature Definition if it exists + // + PackagePart signatureDefinitionPart = + CurrentXpsManager.GetSignatureDefinitionPart(Uri); + if (signatureDefinitionPart != null) + { + // + // Add my signatureDefinitionPart to be tested for V&E Markup + // + xmlPartList.Add(signatureDefinitionPart); + } + } + + /// + /// Adds dependent part Uris to the passed list + /// + internal + void + CollectDependents( + Dictionary dependents, + List selectorList, + XpsDigSigPartAlteringRestrictions restrictions + ) + { + UpdatePageCache(); + // + // Add all pages + // + foreach( IXpsFixedPageReader reader in _pageCache) + { + (reader as XpsFixedPageReaderWriter). + CollectSelfAndDependents( + dependents, + selectorList, + restrictions + ); + } + + // + // Add DocumentStructure + // + EnsureDoucmentStructure(); + if( _documentStructure != null ) + { + dependents[_documentStructure.Uri] = _documentStructure.Uri; + } + } + + #endregion Public methods + + #region Private methods + + private + void + AddPageToDocument( + Uri partUri, + IList linkTargetStream + ) + { + _partEditor.PrepareXmlWriter(XpsS0Markup.FixedDocument, XpsS0Markup.FixedDocumentNamespace); + XmlTextWriter xmlWriter = _partEditor.XmlWriter; + // + // Write + // + String relativePath = XpsManager.MakeRelativePath(Uri, partUri); + + xmlWriter.WriteStartElement(XpsS0Markup.PageContent); + xmlWriter.WriteAttributeString(XmlTags.Source, relativePath); + + // + // Write out link targets if necessary + // + if (linkTargetStream.Count != 0) + { + xmlWriter.WriteRaw (""); + foreach (String nameElement in linkTargetStream) + { + xmlWriter.WriteRaw (String.Format( + System.Globalization.CultureInfo.InvariantCulture, + "", + nameElement) + ); + } + xmlWriter.WriteRaw (""); + } + + xmlWriter.WriteEndElement(); + } + /// + /// This method writes the PrintTicket associated with + /// this fixed document to the Metro package. + /// + private + void + CommitPrintTicket( + ) + { + // + // Flush the PrintTicket if needed + // + if (!_isPrintTicketCommitted ) + { + if(null != _printTicket) + { + CurrentXpsManager.WritePrintTicket(this,_metroPart, _printTicket); + } + else + { + CurrentXpsManager.WriteEmptyPrintTicket(this, _metroPart); + } + _isPrintTicketCommitted = true; + } + } + + /// + /// CurrentPageCommitted signals that our current page + /// is complete and we can write out any associated link + /// targets and end the PageContent element. + /// + internal + void + CurrentPageCommitted() + { + if( _currentPage != null ) + { + //Write out the fixed page tag + AddPageToDocument(_currentPage.Uri, _linkTargetStream); + _currentPage = null; + } + } + + /// + /// Test if the page cache has been initialized and + /// updates it if necessary + /// + private + void + UpdatePageCache() + { + if( !_hasParsedPages ) + { + ParsePages(); + _hasParsedPages = true; + } + } + /// + /// This method parses the part pulling out the Page Referneces + /// and populates the _pageCache + /// + private + void + ParsePages() + { + using (Stream stream = _metroPart.GetStream(FileMode.Open)) + { + // + // If the stream is empty there are no pages to parse + // + if (stream.Length > 0) + { + XmlTextReader reader = new XmlTextReader(stream); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element && reader.Name == XpsS0Markup.PageContent) + { + string attribute = reader.GetAttribute(XmlTags.Source); + if (attribute != null) + { + Uri relativeUri = new Uri(attribute, UriKind.Relative); + AddPageToCache(PackUriHelper.ResolvePartUri(Uri, relativeUri)); + } + } + } + } + } + } + + private + IXpsFixedPageReader + AddPageToCache( Uri pageUri ) + { + PackagePart pagePart = CurrentXpsManager.GetPart(pageUri); + + if (pagePart == null) + { + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_PartNotFound)); + } + + if (!pagePart.ValidatedContentType().AreTypeAndSubTypeEqual(XpsS0Markup.FixedPageContentType)) + { + throw new XpsPackagingException(SR.Get(SRID.ReachPackaging_NotAFixedPage)); + } + + // + // Create the reader/writer for the part + // + IXpsFixedPageReader pageReader = new XpsFixedPageReaderWriter(CurrentXpsManager, this, pagePart, null, _pageCache.Count+1); + + // + // Cache the new reader/writer for later + // + _pageCache.Add(pageReader ); + + return pageReader; + } + + private + void + EnsureThumbnail() + { + if( _thumbnail == null ) + { + _thumbnail = CurrentXpsManager.EnsureThumbnail( this, _metroPart ); + } + } + + + private + void + EnsureSignatureDefinitions() + { + // if _xpsSignaturs is not null we have already initialized this + // + if( null != _signatureDefinitions) + { + return; + } + _signatureDefinitions = new Collection(); + PackagePart sigDefPart = + CurrentXpsManager.GetSignatureDefinitionPart(Uri); + if( sigDefPart != null ) + { + ParseSignaturePart( sigDefPart, _signatureDefinitions ); + } + } + + private + void + EnsureDoucmentStructure() + { + // if _xpsSignaturs is not null we have already initialized this + // + if( null != _documentStructure) + { + return; + } + PackageRelationship documentStructureRelationship = null; + PackagePart documentStructurePart = null; + + foreach (PackageRelationship rel in _metroPart.GetRelationshipsByType(XpsS0Markup.StructureRelationshipName)) + { + if (documentStructureRelationship != null) + { + throw new InvalidDataException(SR.Get(SRID.ReachPackaging_MoreThanOneDocStructure)); + } + + documentStructureRelationship = rel; + } + + if (documentStructureRelationship != null) + { + Uri documentStructureUri = PackUriHelper.ResolvePartUri(documentStructureRelationship.SourceUri, + documentStructureRelationship.TargetUri); + + if (CurrentXpsManager.MetroPackage.PartExists(documentStructureUri)) + { + documentStructurePart = CurrentXpsManager.MetroPackage.GetPart(documentStructureUri); + _documentStructure = new XpsStructure(CurrentXpsManager, this, documentStructurePart); + } + } + } + private + void + ParseSignaturePart( + PackagePart sigDefPart, + Collection sigDefCollection + ) + { + using (XmlTextReader reader = new XmlTextReader(sigDefPart.GetStream(FileMode.Open))) + { + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element && + reader.Name == XpsS0Markup.SignatureDefinitions + ) + { + ParseSignatureDefinitions(reader, sigDefCollection); + } + } + } + } + + private + void + ParseSignatureDefinitions( + XmlReader reader, + Collection sigDefCollection + ) + { + bool endLoop = false; + while (!endLoop && reader.Read()) + { + if( reader.NodeType == XmlNodeType.Element && + reader.Name == XpsS0Markup.SignatureDefinition + ) + { + XpsSignatureDefinition sigDef = new XpsSignatureDefinition(); + sigDef.ReadXML(reader); + sigDefCollection.Add( sigDef ); + } + + if( reader.NodeType == XmlNodeType.EndElement && + reader.Name == XpsS0Markup.SignatureDefinitions + ) + { + endLoop = true; + } + } + } + + private + void + WriteSignatureDefinitions() + { + PackagePart sigDefPart = + CurrentXpsManager.GetSignatureDefinitionPart(Uri); + if( sigDefPart == null ) + { + sigDefPart = CurrentXpsManager.AddSignatureDefinitionPart( _metroPart ); + } + + using (Stream stream = sigDefPart.GetStream(FileMode.Create)) + using (XmlTextWriter writer = new XmlTextWriter(stream, System.Text.Encoding.UTF8)) + { + writer.WriteStartDocument(); + writer.WriteStartElement(XpsS0Markup.SignatureDefinitions, + XpsS0Markup.SignatureDefinitionNamespace); + foreach (XpsSignatureDefinition sigDef in _signatureDefinitions) + { + sigDef.WriteXML(writer); + } + writer.WriteEndElement(); + } + + _sigCollectionDirty = false; + } + + + + #endregion Private methods + + #region Private data + + private PackagePart _metroPart; + private PrintTicket _printTicket; + + private XmlPartEditor _partEditor; + + private IList _linkTargetStream; + + private List _pageCache; + + // + // This variable is used to keep a count of pages written via AddFixedPage + // + private int _pagesWritten; + + // + // This variable flags whether the PrintTicket property is + // committed. A writer can only commit this property once. + // + private bool _isPrintTicketCommitted; + + // + // These variables are used to keep track of the parent + // and current child of this node for walking up and + // down the current tree. This is used be the flushing + // policy to do interleave flushing of parts correctly. + // + private INode _parentNode; + + // + // This variable flags wehter the pageCashe + // has been populated by parsing the part for dependent pages + // + private bool _hasParsedPages; + + // + // 0 based document number in the document sequence + // + private int _documentNumber; + + private XpsThumbnail _thumbnail; + + + // + // Since the current page may add link target information + // we must track our current page + // for this reason we can not handle adding new page + // until the the current page has been committed + // + private XpsFixedPageReaderWriter _currentPage; + // + // A cached list of Signature Definitions + // + private Collection _signatureDefinitions; + // + // Boolean indicating whetehr _signatureDefinitions collection + // has been changed + // + private bool _sigCollectionDirty; + + private XpsStructure _documentStructure; + #endregion Private data + + #region Internal properties + + /// + /// Gets a reference to the XmlWriter for the Metro part + /// that represents this fixed document. + /// + internal System.Xml.XmlWriter XmlWriter + { + get + { + _partEditor.DoesWriteStartEndTags = false; + return _partEditor.XmlWriter; + } + } + + #endregion Internal properties + + #region INode implementation + + void + INode.Flush( + ) + { + // + // Commit the PrintTicket (if necessary) + // + CommitPrintTicket(); + + // + // Create the relationship between the package and the tumbnail + // + if( _thumbnail != null ) + { + _thumbnail = null; + } + } + + void + INode.CommitInternal( + ) + { + CommitInternal(); + } + + PackagePart + INode.GetPart( + ) + { + return _metroPart; + } + + #endregion INode implementation + + #region IDisposable implementation + + void + IDisposable.Dispose( + ) + { + CommitInternal(); + } + + #endregion IDisposable implementation + } +} +