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 part.GetStream ().Dispose ();
101 // Empty the package stream
102 PackageStream.Position = 0;
103 PackageStream.SetLength (0);
105 // Recreate the zip file
106 using (ZipArchive archive = new ZipArchive(PackageStream, Append.Create, false)) {
108 // Write all the part streams
109 foreach (ZipPackagePart part in Parts.Values) {
110 Stream partStream = part.GetStream ();
111 partStream.Seek (0, SeekOrigin.Begin);
113 using (Stream destination = archive.GetStream (part.Uri.ToString ().Substring(1), part.CompressionOption)) {
114 int count = (int) Math.Min (2048, partStream.Length);
115 byte[] buffer = new byte [count];
117 while ((count = partStream.Read (buffer, 0, buffer.Length)) != 0)
118 destination.Write (buffer, 0, count);
122 using (Stream s = archive.GetStream (ContentUri, CompressionOption.Maximum))
123 WriteContentType (s);
127 protected override PackagePart CreatePartCore (Uri partUri, string contentType, CompressionOption compressionOption)
129 ZipPackagePart part = new ZipPackagePart (this, partUri, contentType, compressionOption);
130 Parts.Add (part.Uri, part);
134 protected override void DeletePartCore (Uri partUri)
136 Parts.Remove (partUri);
139 protected override PackagePart GetPartCore (Uri partUri)
142 Parts.TryGetValue (partUri, out part);
146 protected override PackagePart[] GetPartsCore ()
148 ZipPackagePart[] p = new ZipPackagePart [Parts.Count];
149 Parts.Values.CopyTo (p, 0);
155 parts = new Dictionary<Uri, ZipPackagePart> (new UriComparer());
157 using (UnzipArchive archive = new UnzipArchive (PackageStream)) {
159 // Load the content type map file
160 XmlDocument doc = new XmlDocument ();
161 using (Stream s = archive.GetStream (ContentUri))
164 XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
165 manager.AddNamespace ("content", ContentNamespace);
167 // The file names in the zip archive are not prepended with '/'
168 foreach (string file in archive.GetFiles ()) {
169 if (file.Equals (ContentUri, StringComparison.Ordinal))
173 CompressionOption compression = archive.GetCompressionLevel (file);
175 if (file == RelationshipUri.ToString ().Substring (1))
177 CreatePartCore (RelationshipUri, RelationshipContentType, compression);
181 string xPath = string.Format ("/content:Types/content:Override[@PartName='/{0}']", file);
182 node = doc.SelectSingleNode (xPath, manager);
186 string ext = Path.GetExtension (file);
187 if (ext.StartsWith("."))
188 ext = ext.Substring (1);
189 xPath = string.Format("/content:Types/content:Default[@Extension='{0}']", ext);
190 node = doc.SelectSingleNode (xPath, manager);
193 // What do i do if the node is null? This means some has tampered with the
194 // package file manually
196 CreatePartCore (new Uri ("/" + file, UriKind.Relative), node.Attributes["ContentType"].Value, compression);
200 // The archive is invalid - therefore no parts
204 void WriteContentType (Stream s)
206 XmlDocument doc = new XmlDocument ();
207 XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
208 Dictionary<string, string> mimes = new Dictionary<string, string> ();
210 manager.AddNamespace ("content", ContentNamespace);
212 doc.AppendChild(doc.CreateNode (XmlNodeType.XmlDeclaration, "", ""));
214 XmlNode root = doc.CreateNode (XmlNodeType.Element, "Types", ContentNamespace);
215 doc.AppendChild (root);
216 foreach (ZipPackagePart part in Parts.Values)
219 string existingMimeType;
221 var extension = Path.GetExtension (part.Uri.OriginalString);
222 if (extension.Length > 0)
223 extension = extension.Substring (1);
225 if (!mimes.TryGetValue (extension, out existingMimeType)) {
226 node = doc.CreateNode (XmlNodeType.Element, "Default", ContentNamespace);
228 XmlAttribute ext = doc.CreateAttribute ("Extension");
229 ext.Value = extension;
230 node.Attributes.Append (ext);
231 mimes [extension] = part.ContentType;
232 } else if (part.ContentType != existingMimeType) {
233 node = doc.CreateNode (XmlNodeType.Element, "Override", ContentNamespace);
235 XmlAttribute name = doc.CreateAttribute ("PartName");
236 name.Value = part.Uri.ToString ();
237 node.Attributes.Append (name);
241 XmlAttribute contentType = doc.CreateAttribute ("ContentType");
242 contentType.Value = part.ContentType;
243 node.Attributes.Prepend (contentType);
245 root.AppendChild (node);
249 using (XmlTextWriter writer = new XmlTextWriter (s, System.Text.Encoding.UTF8))
250 doc.WriteTo (writer);