de1a4a4efe1660a575ebae9b137b0dae9b9f5a47
[mono.git] / mcs / class / WindowsBase / System.IO.Packaging / Package.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 //      Alan McGovern (amcgovern@novell.com)
25 //
26
27 using System;
28 using System.Collections.Generic;
29 using System.Xml;
30
31 namespace System.IO.Packaging {
32
33         public abstract class Package : IDisposable
34         {
35                 internal const string RelationshipContentType = "application/vnd.openxmlformats-package.relationships+xml";
36                 internal const string RelationshipNamespace = "http://schemas.openxmlformats.org/package/2006/relationships";
37                 internal static readonly Uri RelationshipUri = new Uri ("/_rels/.rels", UriKind.Relative);
38                 
39                 PackagePartCollection partsCollection = new PackagePartCollection ();
40                 Dictionary<string, PackageRelationship> relationships;
41                 PackageRelationshipCollection relationshipsCollection = new PackageRelationshipCollection ();
42                 Uri Uri = new Uri ("/", UriKind.Relative);
43                 
44
45                 public FileAccess FileOpenAccess {
46                         get; private set;
47                 }
48
49                 public PackageProperties PackageProperties {
50                         get; private set;
51                 }
52
53                 int RelationshipId {
54                         get; set;
55                 }
56                 
57                 Dictionary<string, PackageRelationship> Relationships {
58                         get {
59                                 if (relationships == null) {
60                                         relationships = new Dictionary<string, PackageRelationship> ();
61                                         
62                                         if (PartExists (RelationshipUri))
63                                                 using (Stream stream = GetPart (RelationshipUri).GetStream ())
64                                                         LoadRelationships (relationships, stream);
65                                 }
66                                 return relationships;
67                         }
68                 }
69
70                 bool Streaming {
71                         get; set;
72                 }
73                 
74                 
75                 protected Package (FileAccess fileOpenAccess)
76                         : this (fileOpenAccess, false)
77                 {
78                         
79                 }
80
81                 protected Package (FileAccess fileOpenAccess, bool streaming)
82                 {
83                         FileOpenAccess = fileOpenAccess;
84                         Streaming = streaming;
85                 }
86
87
88                 internal void CheckIsReadOnly ()
89                 {
90                         if (FileOpenAccess == FileAccess.Read)
91                                 throw new IOException ("Operation not valid when package is read-only");
92                 }
93
94                 public void Close ()
95                 {
96                         // FIXME: Ensure that Flush is actually called before dispose
97                         Flush ();
98                         Dispose (true);
99                 }
100
101                 public PackagePart CreatePart (Uri partUri, string contentType)
102                 {
103                         return CreatePart (partUri, contentType, CompressionOption.NotCompressed);
104                 }
105
106                 public PackagePart CreatePart (Uri partUri, string contentType, CompressionOption compressionOption)
107                 {
108                         CheckIsReadOnly ();
109                         Check.PartUri (partUri);
110                         Check.ContentTypeIsValid (contentType);
111
112                         if (PartExists (partUri))
113                                 throw new InvalidOperationException ("This partUri is already contained in the package");
114                         
115                         PackagePart part = CreatePartCore (partUri, contentType, compressionOption);
116                         partsCollection.Parts.Add (part);
117                         return part;
118                 }
119                 
120                 protected abstract PackagePart CreatePartCore (Uri parentUri, string contentType, CompressionOption compressionOption);
121
122                 public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType)
123                 {
124                         return CreateRelationship (targetUri, targetMode, relationshipType, null);
125                 }
126
127                 public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id)
128                 {
129                         return CreateRelationship (targetUri, targetMode, relationshipType, id, false);
130                 }
131
132                 internal PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id, bool loading)
133                 {
134                         CheckIsReadOnly ();
135                         Check.TargetUri (targetUri);
136                         
137                         Check.RelationshipTypeIsValid (relationshipType);
138                         Check.IdIsValid (id);
139
140                         if (id == null)
141                                 id = NextId ();
142
143                         PackageRelationship r = new PackageRelationship (id, this, relationshipType, Uri, targetMode, targetUri);
144
145                         if (!PartExists (RelationshipUri))
146                                 CreatePart (RelationshipUri, RelationshipContentType).IsRelationship = true;
147                         
148                         Relationships.Add (r.Id, r);
149                         relationshipsCollection.Relationships.Add (r);
150
151                         if (!loading) {
152                                 using (Stream s = GetPart (RelationshipUri).GetStream ())
153                                         WriteRelationships (relationships, s);
154                         }
155                         
156                         return r;
157                 }
158
159                 
160                 public void DeletePart (Uri partUri)
161                 {
162                         CheckIsReadOnly ();
163                         Check.PartUri (partUri);
164
165                         PackagePart part = GetPart (partUri);
166                         if (part != null)
167                         {
168                                 if (part.Package == null)
169                                         throw new InvalidOperationException ("This part has already been removed");
170                                 
171                                 // FIXME: MS.NET doesn't remove the relationship part
172                                 // Instead it throws an exception if you try to use it
173                                 if (PartExists (part.RelationshipsPartUri))
174                                         GetPart (part.RelationshipsPartUri).Package = null;
175
176                                 part.Package = null;
177                                 DeletePartCore (partUri);
178                                 partsCollection.Parts.RemoveAll (p => p.Uri == partUri);
179                         }
180                 }
181                 
182                 protected abstract void DeletePartCore (Uri partUri);
183
184                 public void DeleteRelationship (string id)
185                 {
186                         Check.Id (id);
187                         CheckIsReadOnly ();
188                         
189                         Relationships.Remove (id);
190
191                         relationshipsCollection.Relationships.RemoveAll (r => r.Id == id);
192                         if (Relationships.Count > 0)
193                                 using (Stream s = GetPart (RelationshipUri).GetStream ())
194                                         WriteRelationships (relationships, s);
195                         else
196                                 DeletePart (RelationshipUri);
197                 }
198                 
199                 void IDisposable.Dispose ()
200                 {
201                         Flush ();
202                         Dispose (true);
203                 }
204
205                 protected virtual void Dispose (bool disposing)
206                 {
207                         // Nothing here needs to be disposed of
208                 }
209
210                 public void Flush ()
211                 {
212                         // I should have a dirty boolean
213                         if (FileOpenAccess != FileAccess.Read)
214                                 FlushCore ();
215                 }
216
217                 protected abstract void FlushCore ();
218
219                 public PackagePart GetPart (Uri partUri)
220                 {
221                         Check.PartUri (partUri);
222                         return GetPartCore (partUri);
223                 }
224
225                 protected abstract PackagePart GetPartCore (Uri partUri);
226
227                 public PackagePartCollection GetParts ()
228                 {
229                         partsCollection.Parts.Clear ();
230                         partsCollection.Parts.AddRange (GetPartsCore());
231                         return partsCollection;
232                 }
233
234                 protected abstract PackagePart [] GetPartsCore ();
235
236                 public PackageRelationship GetRelationship (string id)
237                 {
238                         return Relationships [id];
239                 }
240
241                 public PackageRelationshipCollection GetRelationships ()
242                 {
243                         relationshipsCollection.Relationships.Clear ();
244                         relationshipsCollection.Relationships.AddRange (Relationships.Values);
245                         return relationshipsCollection;
246                 }
247
248                 public PackageRelationshipCollection GetRelationshipsByType (string relationshipType)
249                 {
250                         PackageRelationshipCollection collection = new PackageRelationshipCollection ();
251                         foreach (PackageRelationship r in Relationships.Values)
252                                 if (r.RelationshipType == relationshipType)
253                                         collection.Relationships.Add (r);
254                         
255                         return collection;
256                 }
257
258                 void LoadRelationships (Dictionary<string, PackageRelationship> relationships, Stream stream)
259                 {
260                         XmlDocument doc = new XmlDocument ();
261                         doc.Load (stream);
262                         XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
263                         manager.AddNamespace ("rel", RelationshipNamespace);
264
265                         foreach (XmlNode node in doc.SelectNodes ("/rel:Relationships/*", manager))
266                         {
267                                 
268                                 TargetMode mode = TargetMode.Internal;
269                                 if (node.Attributes["TargetMode"] != null)
270                                         mode = (TargetMode) Enum.Parse (typeof(TargetMode), node.Attributes ["TargetMode"].Value);
271                                 
272                                 CreateRelationship (new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Relative),
273                                                     mode,
274                                                     node.Attributes["Type"].Value.ToString (),
275                                                     node.Attributes["Id"].Value.ToString (),
276                                                     true);
277                         }
278                 }
279                 
280                 string NextId ()
281                 {
282                         while (true)
283                         {
284                                 string s = RelationshipId.ToString ();
285                                 if (!Relationships.ContainsKey (s))
286                                         return s;
287                                 
288                                 RelationshipId++;
289                         }
290                 }
291                 
292                 public static Package Open (Stream stream)
293                 {
294                         return Open (stream, FileMode.Open);
295                 }
296
297                 public static Package Open (string path)
298                 {
299                         return Open (path, FileMode.OpenOrCreate);
300                 }
301
302                 public static Package Open (Stream stream, FileMode packageMode)
303                 {
304                         FileAccess access = packageMode == FileMode.Open ? FileAccess.Read : FileAccess.ReadWrite;
305                         return Open (stream, packageMode, access);
306                 }
307
308                 public static Package Open (string path, FileMode packageMode)
309                 {
310                         return Open (path, packageMode, FileAccess.ReadWrite);
311                 }
312
313                 public static Package Open (Stream stream, FileMode packageMode, FileAccess packageAccess)
314                 {
315                         return OpenCore (stream, packageMode, packageAccess);
316                 }
317
318                 public static Package Open (string path, FileMode packageMode, FileAccess packageAccess)
319                 {
320                         return Open (path, packageMode, packageAccess, FileShare.None);
321                 }
322
323                 public static Package Open (string path, FileMode packageMode, FileAccess packageAccess, FileShare packageShare)
324                 {
325                         if (packageShare != FileShare.Read && packageShare != FileShare.None)
326                                 throw new NotSupportedException ("FileShare.Read and FileShare.None are the only supported options");
327
328                         FileInfo info = new FileInfo (path);
329                         
330                         // Bug - MS.NET appears to test for FileAccess.ReadWrite, not FileAccess.Write
331                         if (packageAccess != FileAccess.ReadWrite && !info.Exists)
332                                 throw new ArgumentException ("packageAccess", "Cannot create stream with FileAccess.Read");
333
334                         
335                         if (info.Exists && packageMode == FileMode.OpenOrCreate && info.Length == 0)
336                                 throw new FileFormatException ("Stream length cannot be zero with FileMode.Open");
337
338                         Stream s = File.Open (path, packageMode, packageAccess, packageShare);
339                         return Open (s, packageMode, packageAccess);
340                 }
341
342                 static Package OpenCore (Stream stream, FileMode packageMode, FileAccess packageAccess)
343                 {
344                         if ((packageAccess & FileAccess.Read) == FileAccess.Read && !stream.CanRead)
345                                 throw new IOException ("Stream does not support reading");
346
347                         if ((packageAccess & FileAccess.Write) == FileAccess.Write && !stream.CanWrite)
348                                 throw new IOException ("Stream does not support reading");
349                         
350                         if (!stream.CanSeek)
351                                 throw new ArgumentException ("stream", "Stream must support seeking");
352                         
353                         if (packageMode == FileMode.Open && stream.Length == 0)
354                                 throw new FileFormatException("Stream length cannot be zero with FileMode.Open");
355
356                         if (packageMode == FileMode.CreateNew && stream.Length > 0)
357                                 throw new IOException ("Cannot use CreateNew when stream contains data");
358
359                         if (packageMode == FileMode.Append || packageMode == FileMode.Truncate)
360                         {
361                                 if (stream.CanWrite)
362                                         throw new NotSupportedException (string.Format("PackageMode.{0} is not supported", packageMode));
363                                 else
364                                         throw new IOException (string.Format("PackageMode.{0} is not supported", packageMode));
365                         }
366
367                         // Test to see if archive is valid
368                         if (stream.Length > 0 && packageAccess == FileAccess.Read) {
369                                 try {
370                                         using (zipsharp.UnzipArchive a = new zipsharp.UnzipArchive (stream)) {
371                                         }
372                                 } catch {
373                                         throw new FileFormatException ("The specified archive is invalid.");
374                                 }
375                         }
376                         
377                         // FIXME: MS docs say that a ZipPackage is returned by default.
378                         // It looks like if you create a custom package, you cannot use Package.Open.
379
380                         // FIXME: This is a horrible hack. If a package is opened in readonly mode, i
381                         // need to pretend i have read/write mode so i can load the PackageParts
382                         // and PackageRelations. I think the 'streaming' boolean might be used for this.
383                         ZipPackage package = new ZipPackage (packageAccess);
384                         package.PackageStream = stream;
385                         package.FileOpenAccess = FileAccess.ReadWrite;
386                         package.GetParts ();
387                         package.GetRelationships();
388                         package.FileOpenAccess = packageAccess;
389                         return package;
390                 }
391                 
392                 public virtual bool PartExists (Uri partUri)
393                 {
394                         return GetPart (partUri) != null;
395                 }
396                 
397                 public bool RelationshipExists (string id)
398                 {
399                         return Relationships.ContainsKey (id);
400                 }
401
402                 internal static void WriteRelationships (Dictionary <string, PackageRelationship> relationships, Stream stream)
403                 {
404                         XmlDocument doc = new XmlDocument ();
405                         XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
406                         manager.AddNamespace ("rel", RelationshipNamespace);
407
408                         doc.AppendChild (doc.CreateNode (XmlNodeType.XmlDeclaration, "", ""));
409                         
410                         XmlNode root = doc.CreateNode (XmlNodeType.Element, "Relationships", RelationshipNamespace);
411                         doc.AppendChild (root);
412                         
413                         foreach (PackageRelationship relationship in relationships.Values)
414                         {
415                                 XmlNode node = doc.CreateNode (XmlNodeType.Element, "Relationship", RelationshipNamespace);
416                                 
417                                 XmlAttribute idAtt = doc.CreateAttribute ("Id");
418                                 idAtt.Value = relationship.Id;
419                                 node.Attributes.Append(idAtt);
420                                 
421                                 XmlAttribute targetAtt = doc.CreateAttribute ("Target");
422                                 targetAtt.Value = relationship.TargetUri.ToString ();
423                                 node.Attributes.Append(targetAtt);
424                                 
425                                 XmlAttribute typeAtt = doc.CreateAttribute ("Type");
426                                 typeAtt.Value = relationship.RelationshipType;
427                                 node.Attributes.Append(typeAtt);
428                                 
429                                 root.AppendChild (node);
430                         }
431
432                         using (XmlTextWriter writer = new XmlTextWriter (stream, System.Text.Encoding.UTF8))
433                                 doc.WriteTo (writer);
434                 }
435         }
436 }
437