Merge pull request #3036 from tritao/windowsbase_zip_datetime
[mono.git] / mcs / class / WindowsBase / System.IO.Packaging / ZipPackage.cs
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:
8 // 
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 // 
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.
19 //
20 // Copyright (c) 2007 Novell, Inc. (http://www.novell.com)
21 //
22 // Authors:
23 //      Chris Toshok (toshok@ximian.com)
24 //
25
26 using System;
27 using System.Collections.Generic;
28 using System.IO;
29 using System.Xml;
30 using zipsharp;
31
32 namespace System.IO.Packaging {
33
34         class UriComparer : IEqualityComparer<Uri>
35         {
36                 public int GetHashCode(Uri uri)
37                 {
38                         return 1;
39                 }
40                 
41                 public bool Equals(Uri x, Uri y)
42                 {
43                         return x.OriginalString.Equals (y.OriginalString, StringComparison.OrdinalIgnoreCase);
44                 }
45         }
46         
47         public sealed class ZipPackage : Package
48         {
49                 private const string ContentNamespace = "http://schemas.openxmlformats.org/package/2006/content-types";
50                 private const string ContentUri = "[Content_Types].xml";
51                 
52                 bool OwnsStream {
53                         get; set;
54                 }
55                 
56                 Dictionary<Uri, ZipPackagePart> parts;
57                 internal Dictionary<Uri, MemoryStream> PartStreams = new Dictionary<Uri, MemoryStream> (new  UriComparer());
58
59                 internal Stream PackageStream { get; set; }
60
61                 Dictionary<Uri, ZipPackagePart> Parts {
62                         get {
63                                 if (parts == null)
64                                         LoadParts ();
65                                 return parts;
66                         }
67                 }
68                 
69                 internal ZipPackage (FileAccess access, bool ownsStream, Stream stream)
70                         : base (access)
71                 {
72                         OwnsStream = ownsStream;
73                         PackageStream = stream;
74                 }
75
76                 internal ZipPackage (FileAccess access, bool ownsStream, Stream stream, bool streaming)
77                         : base (access, streaming)
78                 {
79                         OwnsStream = ownsStream;
80                         PackageStream = stream;
81                 }
82                 
83                 protected override void Dispose (bool disposing)
84                 {
85                         foreach (Stream s in PartStreams.Values)
86                                 s.Close ();
87                         
88                         base.Dispose (disposing);
89                         
90                         if (OwnsStream)
91                                 PackageStream.Close ();
92                 }
93
94                 protected override void FlushCore ()
95                 {
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 ();
101                         }
102                         
103                         // Empty the package stream
104                         PackageStream.Position = 0;
105                         PackageStream.SetLength (0);
106
107                         // Recreate the zip file
108                         using (ZipArchive archive = new ZipArchive(PackageStream, Append.Create, false)) {
109
110                                 // Write all the part streams
111                                 foreach (ZipPackagePart part in Parts.Values) {
112                                         if (part.Package == null)
113                                                 continue;
114
115                                         Stream partStream = part.GetStream ();
116                                         partStream.Seek (0, SeekOrigin.Begin);
117                                         
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];
121
122                                                 while ((count = partStream.Read (buffer, 0, buffer.Length)) != 0)
123                                                         destination.Write (buffer, 0, count);
124                                         }
125                                 }
126
127                                 using (Stream s = archive.GetStream (ContentUri, CompressionOption.Maximum))
128                                         WriteContentType (s);
129                         }
130                 }
131
132                 protected override PackagePart CreatePartCore (Uri partUri, string contentType, CompressionOption compressionOption)
133                 {
134                         ZipPackagePart part = new ZipPackagePart (this, partUri, contentType, compressionOption);
135                         Parts.Add (part.Uri, part);
136                         return part;
137                 }
138
139                 protected override void DeletePartCore (Uri partUri)
140                 {
141                         Parts.Remove (partUri);
142                 }
143
144                 protected override PackagePart GetPartCore (Uri partUri)
145                 {
146                         ZipPackagePart part;
147                         Parts.TryGetValue (partUri, out part);
148                         return part;
149                 }
150
151                 protected override PackagePart[] GetPartsCore ()
152                 {
153                         ZipPackagePart[] p = new ZipPackagePart [Parts.Count];
154                         Parts.Values.CopyTo (p, 0);
155                         return p;
156                 }
157                 
158                 void LoadParts ()
159                 {
160                         parts = new Dictionary<Uri, ZipPackagePart> (new  UriComparer());
161                         try {
162                                 using (UnzipArchive archive = new UnzipArchive (PackageStream)) {
163
164                                         // Load the content type map file
165                                         XmlDocument doc = new XmlDocument ();
166                                         using (Stream s = archive.GetStream (ContentUri))
167                                                 doc.Load (s);
168
169                                         XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
170                                         manager.AddNamespace ("content", ContentNamespace);
171
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))
175                                                         continue;
176
177                                                 XmlNode node;
178                                                 CompressionOption compression = archive.GetCompressionLevel (file);
179
180                                                 if (file == RelationshipUri.ToString ().Substring (1))
181                                                 {
182                                                         CreatePartCore (RelationshipUri, RelationshipContentType, compression);
183                                                         continue;
184                                                 }
185
186                                                 string xPath = string.Format ("/content:Types/content:Override[@PartName='/{0}']", file);
187                                                 node = doc.SelectSingleNode (xPath, manager);
188
189                                                 if (node == null)
190                                                 {
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);
196                                                 }
197
198                                                 // What do i do if the node is null? This means some has tampered with the
199                                                 // package file manually
200                                                 if (node != null)
201                                                         CreatePartCore (new Uri ("/" + file, UriKind.Relative), node.Attributes["ContentType"].Value, compression);
202                                         }
203                                 }
204                         } catch {
205                                 // The archive is invalid - therefore no parts
206                         }
207                 }
208
209                 void WriteContentType (Stream s)
210                 {
211                         XmlDocument doc = new XmlDocument ();
212                         XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
213                         Dictionary<string, string> mimes = new Dictionary<string, string> ();
214                         
215                         manager.AddNamespace ("content", ContentNamespace);
216
217                         doc.AppendChild(doc.CreateNode (XmlNodeType.XmlDeclaration, "", ""));
218                         
219                         XmlNode root = doc.CreateNode (XmlNodeType.Element, "Types", ContentNamespace);
220                         doc.AppendChild (root);
221                         foreach (ZipPackagePart part in Parts.Values)
222                         {
223                                 XmlNode node = null;
224                                 string existingMimeType;
225                                                                         
226                                 var extension = Path.GetExtension (part.Uri.OriginalString);
227                                 if (extension.Length > 0)
228                                         extension = extension.Substring (1);
229                                 
230                                 if (!mimes.TryGetValue (extension, out existingMimeType)) {
231                                         node = doc.CreateNode (XmlNodeType.Element, "Default", ContentNamespace);
232                                         
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);
239                                         
240                                         XmlAttribute name = doc.CreateAttribute ("PartName");
241                                         name.Value = part.Uri.ToString ();
242                                         node.Attributes.Append (name);
243                                 }
244                                 
245                                 if (node != null) {
246                                         XmlAttribute contentType = doc.CreateAttribute ("ContentType");
247                                         contentType.Value = part.ContentType;
248                                         node.Attributes.Prepend (contentType);
249         
250                                         root.AppendChild (node);
251                                 }
252                         }
253
254                         using (XmlTextWriter writer = new XmlTextWriter (s, System.Text.Encoding.UTF8))
255                                 doc.WriteTo (writer);
256                 }
257         }
258 }