Skip to content

Convert non-seekable PackagePart streams to seekable #1311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ public override Stream GetResponseStream()
throw new WebException(SR.Get(SRID.WebResponsePartNotFound));

PackagePart p = c.GetPart(_partName);
Stream s = p.GetStream(FileMode.Open, FileAccess.Read);

Stream s = p.GetSeekableStream(FileMode.Open, FileAccess.Read);

_mimeType = new MS.Internal.ContentType(p.ContentType); // save this for use in ContentType property - may still be null
_fullStreamLength = s.Length; // just this stream
Expand Down Expand Up @@ -648,7 +649,7 @@ internal Stream GetResponseStream()
System.Threading.Thread.CurrentThread.ManagedThreadId + ": " +
"CachedResponse - Getting part stream ");
#endif
Stream s = p.GetStream(FileMode.Open, FileAccess.Read);
Stream s = p.GetSeekableStream(FileMode.Open, FileAccess.Read);

// Unless package is thread-safe, wrap the returned stream so that
// package access is serialized
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ IndexingFilterMarshaler corePropertiesFilterMarshaler
_currentFilter = GetFilterFromClsid(new Guid(filterClsid));
if (_currentFilter != null)
{
_currentStream = currentPart.GetStream();
_currentStream = currentPart.GetSeekableStream();
ManagedIStream stream = new ManagedIStream(_currentStream);
try
{
Expand Down Expand Up @@ -415,7 +415,7 @@ IndexingFilterMarshaler corePropertiesFilterMarshaler
{
if (_currentStream == null)
{
_currentStream = currentPart.GetStream();
_currentStream = currentPart.GetSeekableStream();
}

IndexingFilterMarshaler xamlFilterMarshaler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ public static void LoadComponent(Object component, Uri resourceLocator)
// if not, this is a first time regular load of the component.
PackagePart part = GetResourceOrContentPart(resourceLocator);
ContentType contentType = new ContentType(part.ContentType);
stream = part.GetStream();
stream = part.GetSeekableStream();
bCloseStream = true;

//
Expand Down Expand Up @@ -509,7 +509,7 @@ internal static object LoadComponent(Uri resourceLocator, bool bSkipJournaledPro

PackagePart part = GetResourceOrContentPart(packUri);
ContentType contentType = new ContentType(part.ContentType);
Stream stream = part.GetStream();
Stream stream = part.GetSeekableStream();

ParserContext pc = new ParserContext();
pc.BaseUri = packUri;
Expand Down Expand Up @@ -623,7 +623,7 @@ public static StreamResourceInfo GetResourceStream(Uri uriResource)
}

ResourcePart part = GetResourceOrContentPart(uriResource) as ResourcePart;
return (part == null) ? null : new StreamResourceInfo(part.GetStream(), part.ContentType);
return (part == null) ? null : new StreamResourceInfo(part.GetSeekableStream(), part.ContentType);
}

/// <summary>
Expand Down Expand Up @@ -656,7 +656,7 @@ public static StreamResourceInfo GetContentStream(Uri uriContent)
}

ContentFilePart part = GetResourceOrContentPart(uriContent) as ContentFilePart;
return (part == null) ? null : new StreamResourceInfo(part.GetStream(), part.ContentType);
return (part == null) ? null : new StreamResourceInfo(part.GetSeekableStream(), part.ContentType);
}

/// <summary>
Expand Down Expand Up @@ -714,7 +714,7 @@ public static StreamResourceInfo GetRemoteStream(Uri uriRemote)
{
try
{
stream = sooPart.GetStream();
stream = sooPart.GetSeekableStream();

if (stream == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
namespace System.Windows.Documents
{
using MS.Internal; // Invariant
using MS.Internal.IO.Packaging;
using System;
using System.Xml;
using System.Windows.Markup; // TypeConvertContext, ParserContext
Expand Down Expand Up @@ -221,7 +222,7 @@ internal static string SaveRange(ITextRange range, ref Stream stream, bool useFl
PackagePart xamlEntryPart = wpfPayload.CreateWpfEntryPart();

// Write the part's content
Stream xamlPartStream = xamlEntryPart.GetStream();
Stream xamlPartStream = xamlEntryPart.GetSeekableStream();
using (xamlPartStream)
{
StreamWriter xamlPartWriter = new StreamWriter(xamlPartStream);
Expand Down Expand Up @@ -261,7 +262,7 @@ internal static MemoryStream SaveImage(BitmapSource bitmapSource, string imageCo
PackagePart xamlEntryPart = wpfPayload.CreateWpfEntryPart();

// Write the part's content
Stream xamlPartStream = xamlEntryPart.GetStream();
Stream xamlPartStream = xamlEntryPart.GetSeekableStream();
using (xamlPartStream)
{
StreamWriter xamlPartWriter = new StreamWriter(xamlPartStream);
Expand Down Expand Up @@ -334,8 +335,8 @@ internal static object LoadElement(Stream stream)
// Uniqueness is required to make sure that cached images are not mixed up.
int newWpfPayoutCount = Interlocked.Increment(ref _wpfPayloadCount);
Uri payloadUri = new Uri("payload://wpf" + newWpfPayoutCount, UriKind.Absolute);
Uri entryPartUri = PackUriHelper.Create(payloadUri, xamlEntryPart.Uri); // gives an absolute uri of the entry part
Uri packageUri = PackUriHelper.GetPackageUri(entryPartUri); // extracts package uri from combined package+part uri
Uri entryPartUri = System.IO.Packaging.PackUriHelper.Create(payloadUri, xamlEntryPart.Uri); // gives an absolute uri of the entry part
Uri packageUri = System.IO.Packaging.PackUriHelper.GetPackageUri(entryPartUri); // extracts package uri from combined package+part uri
PackageStore.AddPackage(packageUri, wpfPayload.Package); // Register the package

// Set this temporary uri as a base uri for xaml parser
Expand All @@ -344,7 +345,7 @@ internal static object LoadElement(Stream stream)

// Call xaml parser
bool useRestrictiveXamlReader = !Clipboard.UseLegacyDangerousClipboardDeserializationMode();
xamlObject = XamlReader.Load(xamlEntryPart.GetStream(), parserContext, useRestrictiveXamlReader);
xamlObject = XamlReader.Load(xamlEntryPart.GetSeekableStream(), parserContext, useRestrictiveXamlReader);

// Remove the temporary uri from the PackageStore
PackageStore.RemovePackage(packageUri);
Expand Down Expand Up @@ -484,7 +485,7 @@ private void CreateImagePart(PackagePart sourcePart, BitmapSource imageSource, s
bitmapEncoder.Frames.Add(BitmapFrame.Create(imageSource));

// Save encoded image data into the image part in the package
Stream imageStream = imagePart.GetStream();
Stream imageStream = imagePart.GetSeekableStream();
using (imageStream)
{
bitmapEncoder.Save(imageStream);
Expand Down Expand Up @@ -692,7 +693,7 @@ internal Stream CreateXamlStream()
PackagePart part = this.CreateWpfEntryPart();

// Return a stream opened for writing an image data
return part.GetStream();
return part.GetSeekableStream();
}

internal Stream CreateImageStream(int imageCount, string contentType, out string imagePartUriString)
Expand All @@ -714,7 +715,7 @@ internal Stream CreateImageStream(int imageCount, string contentType, out string
imagePartUriString = GetImageReference(imagePartUriString);

// Return a stream opened for writing an image data
return imagePart.GetStream();
return imagePart.GetSeekableStream();
}

internal Stream GetImageStream(string imageSourceString)
Expand All @@ -723,7 +724,7 @@ internal Stream GetImageStream(string imageSourceString)
imageSourceString = imageSourceString.Substring(1); // cut the leading dot out
Uri imagePartUri = new Uri(XamlPayloadDirectory + imageSourceString, UriKind.Relative);
PackagePart imagePart = _package.GetPart(imagePartUri);
return imagePart.GetStream();
return imagePart.GetSeekableStream();
}

// -------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2019,18 +2019,13 @@ private void WriteImage(DocumentNode documentNode)
// Read the image binary data from WpfPayLoad that contains Xaml and Images.
// Xaml content have the image source like as "./Image1.png" so that we can
// query the image from the container of WpfPayLoad with the image source name.
Stream imageStream = _wpfPayload.GetImageStream(documentNode.FormatState.ImageSource);

// Get image type to be added to rtf content
RtfImageFormat imageFormat = GetImageFormatFromImageSourceName(documentNode.FormatState.ImageSource);

// Write the shape image like as "\pngblip" or "\jpegblip" rtf control. We wrap the stream that comes
// from the package because we require the stream to be seekable.
Debug.Assert(!imageStream.CanSeek);
using (var seekableStream = new MemoryStream((int)imageStream.Length))
using (Stream imageStream = _wpfPayload.GetImageStream(documentNode.FormatState.ImageSource))
{
imageStream.CopyTo(seekableStream);
WriteShapeImage(documentNode, seekableStream, imageFormat);
// Get image type to be added to rtf content
RtfImageFormat imageFormat = GetImageFormatFromImageSourceName(documentNode.FormatState.ImageSource);

// Write the shape image like as "\pngblip" or "\jpegblip" rtf control.
WriteShapeImage(documentNode, imageStream, imageFormat);
}

#if WindowsMetaFile
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// 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.

using MS.Internal.WindowsBase;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move the other PackagePart extension method in WindowsBase\MS\Internal\IO\Packaging\PackagingExtensions.cs to this file?

public static ContentType ValidatedContentType(this PackagePart packagePart)
{
      return new ContentType(packagePart.ContentType);
}

using System.IO;
using System.IO.Packaging;

namespace MS.Internal.IO.Packaging
{
/// <summary>
/// Extensions to provide wrappers for functionality that no longer exists in System.IO.Packaging.PackagePart
/// </summary>
[FriendAccessAllowed]
internal static class PackagePartExtensions
{
/// <summary>
/// Gets a seekable stream from the PackagePart.
/// <see cref="GetSeekableStream(PackagePart, FileMode, FileAccess)"/> for details.
/// </summary>
/// <param name="packPart"></param>
/// <returns>A seekable stream representing the data in the PackagePart.</returns>
internal static Stream GetSeekableStream(this PackagePart packPart)
{
return GetSeekableStream(packPart, FileMode.OpenOrCreate, packPart.Package.FileOpenAccess);
}

/// <summary>
/// Gets a seekable stream from the PackagePart.
/// <see cref="GetSeekableStream(PackagePart, FileMode, FileAccess)"/> for details.
/// </summary>
/// <param name="packPart"></param>
/// <param name="mode">The FileMode to open the PackagePart</param>
/// <returns>A seekable stream representing the data in the PackagePart.</returns>
internal static Stream GetSeekableStream(this PackagePart packPart, FileMode mode)
{
return GetSeekableStream(packPart, mode, packPart.Package.FileOpenAccess);
}

/// <summary>
/// Gets a seekable stream from the PackagePart.
/// </summary>
/// <remarks>
/// In .NET Core 3.0, System.IO.Packaging was removed, in part, from WPF. WPF now uses the implementation
/// contained in System.IO.Packaging.dll. This implementation has distinct differences from the .NET Framework
/// WPF implementation. One such difference is that the <see cref="DeflateStream"/> returned by <see cref="PackagePart.GetStream"/> calls
/// when the <see cref="PackagePart"/> is opened read-only is not a seekable stream. This breaks several assumptions in WPF
/// and causes crashes when various parts of the code-base call into <see cref="Stream.Seek"/> or <see cref="Stream.Position"/>.
///
/// To fix this, we read the entire <see cref="DeflateStream"/> into a <see cref="MemoryStream"/>, allowing callers to fully seek the stream.
/// This is, generally, what would be the case in .NET Framework.
///
/// Note that if the stream returned is seekable (the <see cref="PackagePart"/> was opened write or read-write) then we just pass the resulting
/// stream back as we're already guaranteed it meets our needs.
/// </remarks>
/// <param name="packPart"></param>
/// <param name="mode">The FileMode to open the PackagePart</param>
/// <param name="access">The FileAccess used to open the PackagePart</param>
/// <returns>A seekable stream representing the data in the PackagePart.</returns>
internal static Stream GetSeekableStream(this PackagePart packPart, FileMode mode, FileAccess access)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it even make sense to specify FileMode/FileAccess, shouldn't it always be open/read? When wrapping a writable stream into a MemoryStream who will be forwarding the writes to the original stream?

{
var packStream = packPart.GetStream(mode, access);

// If the stream returned is seekable it meets all requirements and can be used directly.
if (packStream.CanSeek)
{
return packStream;
}

// Non-seekable streams need to be copied out into memory so they are seekable.
using (packStream)
{
var seekableStream = new MemoryStream((int)packStream.Length);

packStream.CopyTo(seekableStream);

// Reset the stream to the beginning. If this is not done, attempts to read the stream
// from the current position will fail. E.G. XAML/XML parsing.
seekableStream.Position = 0;

return seekableStream;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ protected override void Dispose(bool disposing)
//------------------------------------------------------
private string _path;
private ReliableIsolatedStorageFileFolder _folder;
private bool _disposed;
private bool _disposed;
}


Expand Down Expand Up @@ -820,9 +820,9 @@ void CheckDisposed()
//
//------------------------------------------------------
private static IsolatedStorageFile _file;
private static bool _userHasProfile;
private int _refCount; // number of outstanding "streams"
private bool _disposed;
private static bool _userHasProfile;
private int _refCount; // number of outstanding "streams"
private bool _disposed;
}

//------------------------------------------------------
Expand All @@ -835,7 +835,7 @@ void CheckDisposed()
/// </summary>
/// <remarks>See PS 1468964 for details.</remarks>
private static Object _isoStoreSyncObject = new Object();
private static Object _isolatedStorageFileLock = new Object();
private static Object _isolatedStorageFileLock = new Object();
private static ReliableIsolatedStorageFileFolder _defaultFile;
private const string XmlNamespace = "xmlns";
private const string _encodingAttribute = "encoding";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ internal void SetCertificate(X509Certificate2 certificate)
Byte[] byteArray = _certificate.GetRawCertData();

// FileMode.Create will ensure that the stream will shrink if overwritten
using (Stream s = _part.GetStream(FileMode.Create, FileAccess.Write))
using (Stream s = _part.GetSeekableStream(FileMode.Create, FileAccess.Write))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who writes back the writable stream?

{
s.Write(byteArray, 0, byteArray.Length);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ internal bool Verify(X509Certificate2 signer)
result = false; // content type mismatch
break;
}
s = part.GetStream(FileMode.Open, FileAccess.Read);
s = part.GetSeekableStream(FileMode.Open, FileAccess.Read);
}

using (s)
Expand Down Expand Up @@ -609,7 +609,7 @@ private SignedXml EnsureXmlSignatureParsed()
// Load the XML
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.PreserveWhitespace = true;
using (Stream s = SignaturePart.GetStream())
using (Stream s = SignaturePart.GetSeekableStream())
{
using (XmlTextReader xmlReader = new XmlTextReader(s))
{
Expand Down Expand Up @@ -937,7 +937,7 @@ private void AddCustomObjectTags(IEnumerable<System.Security.Cryptography.Xml.Da
private void UpdatePartFromSignature(Signature sig)
{
// write to stream
using (Stream s = SignaturePart.GetStream(FileMode.Create, FileAccess.Write))
using (Stream s = SignaturePart.GetSeekableStream(FileMode.Create, FileAccess.Write))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who writes back the writable stream?

{
using (XmlTextWriter xWriter = new XmlTextWriter(s, System.Text.Encoding.UTF8))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,7 @@ private static XmlNode GeneratePartSigningReference(
reference.AppendChild(GenerateDigestMethod(manager, xDoc));

// <DigestValue>
using (Stream s = part.GetStream(FileMode.Open, FileAccess.Read))
using (Stream s = part.GetSeekableStream(FileMode.Open, FileAccess.Read))
{
reference.AppendChild(GenerateDigestValueNode(xDoc, hashAlgorithm, s, transformName));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<Compile Include="$(WpfSharedDir)\MS\Internal\Registry.cs" />
<Compile Include="$(WpfSharedDir)\MS\Internal\TokenizerHelper.cs " />
<Compile Include="$(WpfSharedDir)\MS\Internal\IO\Packaging\CompoundFile\ContainerFlags.cs " />
<Compile Include="$(WpfSharedDir)\MS\Internal\IO\Packaging\PackagePartExtensions.cs" />
<Compile Include="$(WpfSharedDir)\MS\Internal\IO\Packaging\PackagingUtilities.cs" />
<Compile Include="$(WpfSharedDir)\MS\Internal\ReaderWriterLockSlimWrapper.cs" />
<Compile Include="$(WpfSharedDir)\MS\Internal\ReaderWriterLockWrapper.cs" />
Expand Down