1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2007 Novell, Inc. (http://www.novell.com)
23 // Chris Toshok (toshok@ximian.com)
27 using System.Collections.Generic;
32 namespace System.IO.Packaging {
34 class UriComparer : IEqualityComparer<Uri>
36 public int GetHashCode(Uri uri)
41 public bool Equals(Uri x, Uri y)
43 return x.OriginalString.Equals (y.OriginalString, StringComparison.OrdinalIgnoreCase);
47 public sealed class ZipPackage : Package
49 private const string ContentNamespace = "http://schemas.openxmlformats.org/package/2006/content-types";
50 private const string ContentUri = "[Content_Types].xml";
56 Dictionary<Uri, ZipPackagePart> parts;
57 internal Dictionary<Uri, MemoryStream> PartStreams = new Dictionary<Uri, MemoryStream> (new UriComparer());
59 internal Stream PackageStream { get; set; }
61 Dictionary<Uri, ZipPackagePart> Parts {
69 internal ZipPackage (FileAccess access, bool ownsStream, Stream stream)
72 OwnsStream = ownsStream;
73 PackageStream = stream;
76 internal ZipPackage (FileAccess access, bool ownsStream, Stream stream, bool streaming)
77 : base (access, streaming)
79 OwnsStream = ownsStream;
80 PackageStream = stream;
83 protected override void Dispose (bool disposing)
85 foreach (Stream s in PartStreams.Values)
88 base.Dispose (disposing);
91 PackageStream.Close ();
94 protected override void FlushCore ()
96 // Ensure that all the data has been read out of the package
97 // stream already. Otherwise we'll lose data when we recreate the zip
98 foreach (ZipPackagePart part in Parts.Values) {
99 if (part.Package != null)
100 part.GetStream ().Dispose ();
103 // Empty the package stream
104 PackageStream.Position = 0;
105 PackageStream.SetLength (0);
107 // Recreate the zip file
108 using (ZipArchive archive = new ZipArchive(PackageStream, Append.Create, false)) {
110 // Write all the part streams
111 foreach (ZipPackagePart part in Parts.Values) {
112 if (part.Package == null)
115 Stream partStream = part.GetStream ();
116 partStream.Seek (0, SeekOrigin.Begin);
118 using (Stream destination = archive.GetStream (part.Uri.ToString ().Substring(1), part.CompressionOption)) {
119 int count = (int) Math.Min (2048, partStream.Length);
120 byte[] buffer = new byte [count];
122 while ((count = partStream.Read (buffer, 0, buffer.Length)) != 0)
123 destination.Write (buffer, 0, count);
127 using (Stream s = archive.GetStream (ContentUri, CompressionOption.Maximum))
128 WriteContentType (s);
132 protected override PackagePart CreatePartCore (Uri partUri, string contentType, CompressionOption compressionOption)
134 ZipPackagePart part = new ZipPackagePart (this, partUri, contentType, compressionOption);
135 Parts.Add (part.Uri, part);
139 protected override void DeletePartCore (Uri partUri)
141 Parts.Remove (partUri);
144 protected override PackagePart GetPartCore (Uri partUri)
147 Parts.TryGetValue (partUri, out part);
151 protected override PackagePart[] GetPartsCore ()
153 ZipPackagePart[] p = new ZipPackagePart [Parts.Count];
154 Parts.Values.CopyTo (p, 0);
160 parts = new Dictionary<Uri, ZipPackagePart> (new UriComparer());
162 using (UnzipArchive archive = new UnzipArchive (PackageStream)) {
164 // Load the content type map file
165 XmlDocument doc = new XmlDocument ();
166 using (Stream s = archive.GetStream (ContentUri))
169 XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
170 manager.AddNamespace ("content", ContentNamespace);
172 // The file names in the zip archive are not prepended with '/'
173 foreach (string file in archive.GetFiles ()) {
174 if (file.Equals (ContentUri, StringComparison.Ordinal))
178 CompressionOption compression = archive.GetCompressionLevel (file);
180 if (file == RelationshipUri.ToString ().Substring (1))
182 CreatePartCore (RelationshipUri, RelationshipContentType, compression);
186 string xPath = string.Format ("/content:Types/content:Override[@PartName='/{0}']", file);
187 node = doc.SelectSingleNode (xPath, manager);
191 string ext = Path.GetExtension (file);
192 if (ext.StartsWith("."))
193 ext = ext.Substring (1);
194 xPath = string.Format("/content:Types/content:Default[@Extension='{0}']", ext);
195 node = doc.SelectSingleNode (xPath, manager);
198 // What do i do if the node is null? This means some has tampered with the
199 // package file manually
201 CreatePartCore (new Uri ("/" + file, UriKind.Relative), node.Attributes["ContentType"].Value, compression);
205 // The archive is invalid - therefore no parts
209 void WriteContentType (Stream s)
211 XmlDocument doc = new XmlDocument ();
212 XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
213 Dictionary<string, string> mimes = new Dictionary<string, string> ();
215 manager.AddNamespace ("content", ContentNamespace);
217 doc.AppendChild(doc.CreateNode (XmlNodeType.XmlDeclaration, "", ""));
219 XmlNode root = doc.CreateNode (XmlNodeType.Element, "Types", ContentNamespace);
220 doc.AppendChild (root);
221 foreach (ZipPackagePart part in Parts.Values)
224 string existingMimeType;
226 var extension = Path.GetExtension (part.Uri.OriginalString);
227 if (extension.Length > 0)
228 extension = extension.Substring (1);
230 if (!mimes.TryGetValue (extension, out existingMimeType)) {
231 node = doc.CreateNode (XmlNodeType.Element, "Default", ContentNamespace);
233 XmlAttribute ext = doc.CreateAttribute ("Extension");
234 ext.Value = extension;
235 node.Attributes.Append (ext);
236 mimes [extension] = part.ContentType;
237 } else if (part.ContentType != existingMimeType) {
238 node = doc.CreateNode (XmlNodeType.Element, "Override", ContentNamespace);
240 XmlAttribute name = doc.CreateAttribute ("PartName");
241 name.Value = part.Uri.ToString ();
242 node.Attributes.Append (name);
246 XmlAttribute contentType = doc.CreateAttribute ("ContentType");
247 contentType.Value = part.ContentType;
248 node.Attributes.Prepend (contentType);
250 root.AppendChild (node);
254 using (XmlTextWriter writer = new XmlTextWriter (s, System.Text.Encoding.UTF8))
255 doc.WriteTo (writer);