Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Resolvers / XmlPreloadedResolver.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlPreloadedResolver.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">[....]</owner>
6 //------------------------------------------------------------------------------
7
8 using System.IO;
9 using System.Xml;
10 #if !SILVERLIGHT
11 using System.Net;
12 #endif
13 using System.Text;
14 using System.Xml.Utils;
15 using System.Reflection;
16 using System.Diagnostics;
17 using System.Collections.Generic;
18 using System.Runtime.Versioning;
19
20 namespace System.Xml.Resolvers {
21
22     // 
23     // XmlPreloadedResolver is an XmlResolver that which can be pre-loaded with data.
24     // By default it contains well-known DTDs for XHTML 1.0 and RSS 0.91. 
25     // Custom mappings of URIs to data can be added with the Add method.
26     //
27     public partial class XmlPreloadedResolver : XmlResolver {
28         //
29         // PreloadedData class
30         //
31         abstract class PreloadedData {
32             // Returns preloaded data as Stream; Stream must always be supported
33             internal abstract Stream AsStream();
34
35             // Returns preloaded data as TextReader, or throws when not supported
36             internal virtual TextReader AsTextReader() {
37                 throw new XmlException(Res.GetString(Res.Xml_UnsupportedClass));
38             }
39
40             // Returns true for types that are supported for this preloaded data; Stream must always be supported
41             internal virtual bool SupportsType(Type type) {
42                 if (type == null || type == typeof(Stream)) {
43                     return true;
44                 }
45                 return false;
46             }
47         };
48
49         //
50         // XmlKnownDtdData class
51         //
52         class XmlKnownDtdData : PreloadedData {
53             internal string publicId;
54             internal string systemId;
55             private string resourceName;
56
57             internal XmlKnownDtdData(string publicId, string systemId, string resourceName) {
58                 this.publicId = publicId;
59                 this.systemId = systemId;
60                 this.resourceName = resourceName;
61             }
62
63             internal override Stream AsStream() {
64                 Assembly asm = Assembly.GetExecutingAssembly();
65                 return asm.GetManifestResourceStream(resourceName);
66             }
67         }
68
69         class ByteArrayChunk : PreloadedData {
70             byte[] array;
71             int offset;
72             int length;
73
74             internal ByteArrayChunk(byte[] array)
75                 : this(array, 0, array.Length) {
76             }
77
78             internal ByteArrayChunk(byte[] array, int offset, int length) {
79                 this.array = array;
80                 this.offset = offset;
81                 this.length = length;
82             }
83
84             internal override Stream AsStream() {
85                 return new MemoryStream(array, offset, length);
86             }
87         }
88
89         class StringData : PreloadedData {
90             string str;
91
92             internal StringData(string str) {
93                 this.str = str;
94             }
95
96             internal override Stream AsStream() {
97                 return new MemoryStream(Encoding.Unicode.GetBytes(str));
98             }
99
100             internal override TextReader AsTextReader() {
101                 return new StringReader(str);
102             }
103
104             internal override bool SupportsType(Type type) {
105                 if (type == typeof(TextReader)) {
106                     return true;
107                 }
108                 return base.SupportsType(type);
109             }
110         }
111
112         //
113         // Fields
114         //
115         XmlResolver fallbackResolver;
116         Dictionary<Uri, PreloadedData> mappings;
117         XmlKnownDtds preloadedDtds;
118
119         //
120         // Static/constant fiels
121         //
122         static XmlKnownDtdData[] Xhtml10_Dtd = new XmlKnownDtdData[] {
123             new XmlKnownDtdData( "-//W3C//DTD XHTML 1.0 Strict//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd", "xhtml1-strict.dtd" ),
124             new XmlKnownDtdData( "-//W3C//DTD XHTML 1.0 Transitional//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd", "xhtml1-transitional.dtd" ),
125             new XmlKnownDtdData( "-//W3C//DTD XHTML 1.0 Frameset//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd", "xhtml1-frameset.dtd" ),
126             new XmlKnownDtdData( "-//W3C//ENTITIES Latin 1 for XHTML//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent", "xhtml-lat1.ent" ),
127             new XmlKnownDtdData( "-//W3C//ENTITIES Symbols for XHTML//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent", "xhtml-symbol.ent" ),
128             new XmlKnownDtdData( "-//W3C//ENTITIES Special for XHTML//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent", "xhtml-special.ent" ),
129         };
130
131         static XmlKnownDtdData[] Rss091_Dtd = new XmlKnownDtdData[] {
132             new XmlKnownDtdData( "-//Netscape Communications//DTD RSS 0.91//EN", "http://my.netscape.com/publish/formats/rss-0.91.dtd", "rss-0.91.dtd" ),
133         };
134
135         //
136         // Constructors
137         //
138         public XmlPreloadedResolver()
139             : this(null) {
140         }
141
142         public XmlPreloadedResolver(XmlKnownDtds preloadedDtds)
143             : this(null, preloadedDtds, null) {
144         }
145
146         public XmlPreloadedResolver(XmlResolver fallbackResolver)
147             : this(fallbackResolver, XmlKnownDtds.All, null) {
148         }
149
150         public XmlPreloadedResolver(XmlResolver fallbackResolver, XmlKnownDtds preloadedDtds) 
151             : this (fallbackResolver, preloadedDtds, null) {
152         }
153
154         public XmlPreloadedResolver(XmlResolver fallbackResolver, XmlKnownDtds preloadedDtds, IEqualityComparer<Uri> uriComparer) {
155             this.fallbackResolver = fallbackResolver;
156             this.mappings = new Dictionary<Uri, PreloadedData>(16, uriComparer);
157             this.preloadedDtds = preloadedDtds;
158
159             // load known DTDs
160             if (preloadedDtds != 0) {
161                 if ((preloadedDtds & XmlKnownDtds.Xhtml10) != 0) {
162                     AddKnownDtd(Xhtml10_Dtd);
163                 }
164                 if ((preloadedDtds & XmlKnownDtds.Rss091) != 0) {
165                     AddKnownDtd(Rss091_Dtd);
166                 }
167             }
168         }
169
170 #if !SILVERLIGHT
171         [ResourceExposure(ResourceScope.Machine)]
172         [ResourceConsumption(ResourceScope.Machine)]
173 #endif
174         public override Uri ResolveUri(Uri baseUri, string relativeUri) {
175             // 1) special-case well-known public IDs
176             // 2) To make FxCop happy we need to use StartsWith() overload that takes StringComparison ->
177             //   .StartsWith(string) is equal to .StartsWith(string, StringComparison.CurrentCulture);
178             if (relativeUri != null && relativeUri.StartsWith("-//", StringComparison.CurrentCulture)) {
179                 // 1) XHTML 1.0 public IDs
180                 // 2) To make FxCop happy we need to use StartsWith() overload that takes StringComparison ->
181                 //   .StartsWith(string) is equal to .StartsWith(string, StringComparison.CurrentCulture);
182                 if ((preloadedDtds & XmlKnownDtds.Xhtml10) != 0 && relativeUri.StartsWith("-//W3C//", StringComparison.CurrentCulture)) {
183                     for (int i = 0; i < Xhtml10_Dtd.Length; i++) {
184                         if (relativeUri == Xhtml10_Dtd[i].publicId) {
185                             return new Uri(relativeUri, UriKind.Relative);
186                         }
187                     }
188                 }
189                 // RSS 0.91 public IDs
190                 if ((preloadedDtds & XmlKnownDtds.Rss091) != 0) {
191                     Debug.Assert(Rss091_Dtd.Length == 1);
192                     if (relativeUri == Rss091_Dtd[0].publicId) {
193                         return new Uri(relativeUri, UriKind.Relative);
194                     }
195                 }
196             }
197             return base.ResolveUri(baseUri, relativeUri);
198         }
199
200         public override Object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn) {
201             if (absoluteUri == null) {
202                 throw new ArgumentNullException("absoluteUri");
203             }
204
205             PreloadedData data;
206             if (!mappings.TryGetValue(absoluteUri, out data)) {
207                 if (fallbackResolver != null) {
208                     return fallbackResolver.GetEntity(absoluteUri, role, ofObjectToReturn);
209                 }
210                 throw new XmlException(Res.GetString(Res.Xml_CannotResolveUrl, absoluteUri.ToString()));
211             }
212
213             if (ofObjectToReturn == null || ofObjectToReturn == typeof(Stream) || ofObjectToReturn == typeof(Object)) {
214                 return data.AsStream();
215             }
216             else if (ofObjectToReturn == typeof(TextReader)) {
217                 return data.AsTextReader();
218             }
219             else {
220                 throw new XmlException(Res.GetString(Res.Xml_UnsupportedClass));
221             }
222         }
223
224 #if !SILVERLIGHT
225         public override ICredentials Credentials {
226             set {
227                 if (fallbackResolver != null) {
228                     fallbackResolver.Credentials = value;
229                 }
230             }
231         }
232 #endif
233
234         public override bool SupportsType(Uri absoluteUri, Type type) {
235             if (absoluteUri == null) {
236                 throw new ArgumentNullException("absoluteUri");
237             }
238
239             PreloadedData data;
240             if (!mappings.TryGetValue(absoluteUri, out data)) {
241                 if (fallbackResolver != null) {
242                     return fallbackResolver.SupportsType(absoluteUri, type);
243                 }
244                 return base.SupportsType(absoluteUri, type);
245             }
246
247             return data.SupportsType(type);
248         }
249
250         public void Add(Uri uri, byte[] value) {
251             if (uri == null) {
252                 throw new ArgumentNullException("uri");
253             }
254             if (value == null) {
255                 throw new ArgumentNullException("value");
256             }
257             Add(uri, new ByteArrayChunk(value, 0, value.Length));
258         }
259
260         public void Add(Uri uri, byte[] value, int offset, int count) {
261             if (uri == null) {
262                 throw new ArgumentNullException("uri");
263             }
264             if (value == null) {
265                 throw new ArgumentNullException("value");
266             }
267             if (count < 0) {
268                 throw new ArgumentOutOfRangeException("count");
269             }
270             if (offset < 0) {
271                 throw new ArgumentOutOfRangeException("offset");
272             }
273             if (value.Length - offset < count) {
274                 throw new ArgumentOutOfRangeException("count");
275             }
276
277             Add(uri, new ByteArrayChunk(value, offset, count));
278         }
279
280         public void Add(Uri uri, Stream value) {
281             if (uri == null) {
282                 throw new ArgumentNullException("uri");
283             }
284             if (value == null) {
285                 throw new ArgumentNullException("value");
286             }
287             if (value.CanSeek) {
288                 // stream of known length -> allocate the byte array and read all data into it
289                 int size = checked((int)value.Length);
290                 byte[] bytes = new byte[size];
291                 value.Read(bytes, 0, size);
292                 Add(uri, new ByteArrayChunk(bytes));
293             }
294             else {
295                 // stream of unknown length -> read into memory stream and then get internal the byte array
296                 MemoryStream ms = new MemoryStream();
297                 byte[] buffer = new byte[4096];
298                 int read;
299                 while ((read = value.Read(buffer, 0, buffer.Length)) > 0) {
300                     ms.Write(buffer, 0, read);
301                 }
302                 int size = checked((int)ms.Position);
303                 byte[] bytes = new byte[size];
304                 Array.Copy(ms.GetBuffer(), bytes, size);
305                 Add(uri, new ByteArrayChunk(bytes));
306             }
307         }
308
309         public void Add(Uri uri, string value) {
310             if (uri == null) {
311                 throw new ArgumentNullException("uri");
312             }
313             if (value == null) {
314                 throw new ArgumentNullException("value");
315             }
316             Add(uri, new StringData(value));
317         }
318
319         public IEnumerable<Uri> PreloadedUris {
320             get {
321                 // read-only collection of keys
322                 return mappings.Keys;
323             }
324         }
325
326         public void Remove(Uri uri) {
327             if (uri == null) {
328                 throw new ArgumentNullException("uri");
329             }
330             mappings.Remove(uri);
331         }
332
333         //
334         // Private implementation methods
335         //
336         private void Add(Uri uri, PreloadedData data) {
337             Debug.Assert(uri != null);
338
339             // override if exists
340             if (mappings.ContainsKey(uri)) {
341                 mappings[uri] = data;
342             }
343             else {
344                 mappings.Add(uri, data);
345             }
346         }
347
348         private void AddKnownDtd(XmlKnownDtdData[] dtdSet) {
349             for (int i = 0; i < dtdSet.Length; i++) {
350                 XmlKnownDtdData dtdInfo = dtdSet[i];
351                 mappings.Add(new Uri(dtdInfo.publicId, UriKind.RelativeOrAbsolute), dtdInfo);
352                 mappings.Add(new Uri(dtdInfo.systemId, UriKind.RelativeOrAbsolute), dtdInfo);
353             }
354         }
355     }
356 }