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