* WindowsBase/System.IO.Packaging/Package.cs:
[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                 Dictionary<Uri, ZipPackagePart> parts;
53                 internal Dictionary<Uri, MemoryStream> PartStreams = new Dictionary<Uri, MemoryStream> (new  UriComparer());
54
55                 internal Stream PackageStream { get; set; }
56
57                 Dictionary<Uri, ZipPackagePart> Parts {
58                         get {
59                                 if (parts == null)
60                                         LoadParts ();
61                                 return parts;
62                         }
63                 }
64                 
65                 internal ZipPackage (FileAccess access, Stream stream)
66                         : base (access)
67                 {
68                         PackageStream = stream;
69                 }
70
71                 internal ZipPackage (FileAccess access, Stream stream, bool streaming)
72                         : base (access, streaming)
73                 {
74                         PackageStream = stream;
75                 }
76                 
77                 protected override void Dispose (bool disposing)
78                 {
79                         foreach (Stream s in PartStreams.Values)
80                                 s.Close ();
81                         
82                         PackageStream.Close ();
83                 }
84
85                 protected override void FlushCore ()
86                 {
87                         // Ensure that all the data has been read out of the package
88                         // stream already. Otherwise we'll lose data when we recreate the zip
89                         foreach (ZipPackagePart part in Parts.Values)
90                                 part.GetStream ().Dispose ();
91                         
92                         // Empty the package stream
93                         PackageStream.Position = 0;
94                         PackageStream.SetLength (0);
95
96                         // Recreate the zip file
97                         using (ZipArchive archive = new ZipArchive(PackageStream, Append.Create, false)) {
98
99                                 // Write all the part streams
100                                 foreach (ZipPackagePart part in Parts.Values) {
101                                         Stream partStream = part.GetStream ();
102                                         partStream.Seek (0, SeekOrigin.Begin);
103                                         
104                                         using (Stream destination = archive.GetStream (part.Uri.ToString ().Substring(1))) {
105                                                 int count = (int) Math.Min (2048, partStream.Length);
106                                                 byte[] buffer = new byte [count];
107
108                                                 while ((count = partStream.Read (buffer, 0, buffer.Length)) != 0)
109                                                         destination.Write (buffer, 0, count);
110                                         }
111                                 }
112
113                                 using (Stream s = archive.GetStream (ContentUri))
114                                         WriteContentType (s);
115                         }
116                 }
117
118                 protected override PackagePart CreatePartCore (Uri partUri, string contentType, CompressionOption compressionOption)
119                 {
120                         ZipPackagePart part = new ZipPackagePart (this, partUri, contentType, compressionOption);
121                         Parts.Add (part.Uri, part);
122                         return part;
123                 }
124
125                 protected override void DeletePartCore (Uri partUri)
126                 {
127                         Parts.Remove (partUri);
128                 }
129
130                 protected override PackagePart GetPartCore (Uri partUri)
131                 {
132                         ZipPackagePart part;
133                         Parts.TryGetValue (partUri, out part);
134                         return part;
135                 }
136
137                 protected override PackagePart[] GetPartsCore ()
138                 {
139                         ZipPackagePart[] p = new ZipPackagePart [Parts.Count];
140                         Parts.Values.CopyTo (p, 0);
141                         return p;
142                 }
143                 
144                 void LoadParts ()
145                 {
146                         parts = new Dictionary<Uri, ZipPackagePart> (new  UriComparer());
147                         try {
148                                 using (UnzipArchive archive = new UnzipArchive (PackageStream)) {
149
150                                         // Load the content type map file
151                                         XmlDocument doc = new XmlDocument ();
152                                         using (Stream s = archive.GetStream (ContentUri))
153                                                 doc.Load (s);
154
155                                         XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
156                                         manager.AddNamespace ("content", ContentNamespace);
157
158                                         foreach (string file in archive.GetFiles ()) {
159                                                 XmlNode node;
160
161                                                 if (file == RelationshipUri.ToString ().Substring (1))
162                                                 {
163                                                         // FIXME: I shouldn't use CompressionOption.NotCompressed - i should read it from the zip archive
164                                                         CreatePartCore (RelationshipUri, RelationshipContentType, CompressionOption.NotCompressed);
165                                                         continue;
166                                                 }
167
168                                                 string xPath = string.Format ("/content:Types/content:Override[@PartName='{0}']", file);
169                                                 node = doc.SelectSingleNode (xPath, manager);
170
171                                                 if (node == null)
172                                                 {
173                                                         string ext = Path.GetExtension (file);
174                                                         if (ext.StartsWith("."))
175                                                                 ext = ext.Substring (1);
176                                                         xPath = string.Format("/content:Types/content:Default[@Extension='{0}']", ext);
177                                                         node = doc.SelectSingleNode (xPath, manager);
178                                                 }
179
180                                                 // What do i do if the node is null? This means some has tampered with the
181                                                 // package file manually
182                                                 
183                                                 // FIXME: I shouldn't use CompressionOption.NotCompressed - i should read it from the zip archive
184                                                 if (node != null)
185                                                         CreatePartCore (new Uri ("/" + file, UriKind.Relative), node.Attributes["ContentType"].Value, CompressionOption.NotCompressed);
186                                         }
187                                 }
188                         } catch {
189                                 // The archive is invalid - therefore no parts
190                         }
191                 }
192
193                 void WriteContentType (Stream s)
194                 {
195                         XmlDocument doc = new XmlDocument ();
196                         XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
197                         manager.AddNamespace ("content", ContentNamespace);
198
199                         doc.AppendChild(doc.CreateNode (XmlNodeType.XmlDeclaration, "", ""));
200                         
201                         XmlNode root = doc.CreateNode (XmlNodeType.Element, "Types", ContentNamespace);
202                         doc.AppendChild (root);
203                         foreach (ZipPackagePart part in Parts.Values)
204                         {
205                                 XmlNode node = doc.CreateNode (XmlNodeType.Element, "Override", ContentNamespace);
206                                 
207                                 XmlAttribute contentType = doc.CreateAttribute ("ContentType");
208                                 contentType.Value = part.ContentType;
209                                 
210                                 XmlAttribute name = doc.CreateAttribute ("PartName");
211                                 name.Value = part.Uri.ToString ().Substring(1);
212                                 
213
214                                 node.Attributes.Append (contentType);
215                                 node.Attributes.Append (name);
216
217                                 root.AppendChild (node);                                
218                         }
219
220                         using (XmlTextWriter writer = new XmlTextWriter (s, System.Text.Encoding.UTF8))
221                                 doc.WriteTo (writer);
222                 }
223         }
224 }