Merge pull request #228 from QuickJack/3e163743eda89cc8c239779a75dd245be12aee3c
[mono.git] / mcs / class / System.XML / System.Xml / XmlReaderBinarySupport.cs
1 //
2 // System.Xml.XmlReaderBinarySupport.cs
3 //
4 // Author:
5 //   Atsushi Enomoto  (ginga@kit.hi-ho.ne.jp)
6 //
7 // (C)2004 Novell Inc,
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.Collections;
33 using System.Text;
34
35 namespace System.Xml
36 {
37         internal class XmlReaderBinarySupport
38         {
39                 public delegate int CharGetter (
40                         char [] buffer, int offset, int length);
41
42                 public enum CommandState {
43                         None,
44                         ReadElementContentAsBase64,
45                         ReadContentAsBase64,
46                         ReadElementContentAsBinHex,
47                         ReadContentAsBinHex
48                 }
49
50                 public XmlReaderBinarySupport (XmlReader reader)
51                 {
52                         this.reader = reader;
53                         Reset ();
54                 }
55
56                 XmlReader reader;
57                 CharGetter getter;
58                 byte [] base64Cache = new byte [3];
59                 int base64CacheStartsAt;
60                 CommandState state;
61                 StringBuilder textCache;
62                 bool hasCache;
63                 bool dontReset;
64
65                 public CharGetter Getter {
66                         get { return getter; }
67                         set { getter = value; }
68                 }
69
70                 public void Reset ()
71                 {
72                         if (!dontReset) {
73                                 dontReset = true;
74                                 if (hasCache) {
75                                         switch (reader.NodeType) {
76                                         case XmlNodeType.Text:
77                                         case XmlNodeType.CDATA:
78                                         case XmlNodeType.SignificantWhitespace:
79                                         case XmlNodeType.Whitespace:
80                                                 reader.Read ();
81                                                 break;
82                                         }
83                                         switch (state) {
84                                         case CommandState.ReadElementContentAsBase64:
85                                         case CommandState.ReadElementContentAsBinHex:
86                                                 reader.Read ();
87                                                 break;
88                                         }
89                                 }
90                                 base64CacheStartsAt = -1;
91                                 state = CommandState.None;
92                                 hasCache = false;
93                                 dontReset = false;
94                         }
95                 }
96
97                 InvalidOperationException StateError (CommandState action)
98                 {
99                         return new InvalidOperationException (
100                                 String.Format ("Invalid attempt to read binary content by {0}, while once binary reading was started by {1}", action, state));
101                 }
102
103                 private void CheckState (bool element, CommandState action)
104                 {
105                         if (state == CommandState.None) {
106                                 if (textCache == null)
107                                         textCache = new StringBuilder ();
108                                 else
109                                         textCache.Length = 0;
110                                 if (action == CommandState.None)
111                                         return; // for ReadValueChunk()
112                                 if (reader.ReadState != ReadState.Interactive)
113                                         return;
114                                 switch (reader.NodeType) {
115                                 case XmlNodeType.Text:
116                                 case XmlNodeType.CDATA:
117                                 case XmlNodeType.SignificantWhitespace:
118                                 case XmlNodeType.Whitespace:
119                                         if (!element) {
120                                                 state = action;
121                                                 return;
122                                         }
123                                         break;
124                                 case XmlNodeType.Element:
125                                         if (element) {
126                                                 if (!reader.IsEmptyElement)
127                                                         reader.Read ();
128                                                 state = action;
129                                                 return;
130                                         }
131                                         break;
132                                 }
133                                 throw new XmlException ((element ? 
134                                         "Reader is not positioned on an element."
135                                         : "Reader is not positioned on a text node."));
136                         }
137                         if (state == action)
138                                 return;
139                         throw StateError (action);
140                 }
141
142                 public int ReadElementContentAsBase64 (
143                         byte [] buffer, int offset, int length)
144                 {
145                         CheckState (true, CommandState.ReadElementContentAsBase64);
146                         return ReadBase64 (buffer, offset, length);
147                 }
148
149                 public int ReadContentAsBase64 (
150                         byte [] buffer, int offset, int length)
151                 {
152                         CheckState (false, CommandState.ReadContentAsBase64);
153                         return ReadBase64 (buffer, offset, length);
154                 }
155
156                 public int ReadElementContentAsBinHex (
157                         byte [] buffer, int offset, int length)
158                 {
159                         CheckState (true, CommandState.ReadElementContentAsBinHex);
160                         return ReadBinHex (buffer, offset, length);
161                 }
162
163                 public int ReadContentAsBinHex (
164                         byte [] buffer, int offset, int length)
165                 {
166                         CheckState (false, CommandState.ReadContentAsBinHex);
167                         return ReadBinHex (buffer, offset, length);
168                 }
169
170                 public int ReadBase64 (byte [] buffer, int offset, int length)
171                 {
172                         if (offset < 0)
173                                 throw CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
174                         else if (length < 0)
175                                 throw CreateArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
176                         else if (buffer.Length < offset + length)
177                                 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
178
179                         if (reader.IsEmptyElement)
180                                 return 0;
181                         if (length == 0)        // It does not raise an error.
182                                 return 0;
183
184                         int bufIndex = offset;
185                         int bufLast = offset + length;
186
187                         if (base64CacheStartsAt >= 0) {
188                                 for (int i = base64CacheStartsAt; i < 3; i++) {
189                                         buffer [bufIndex++] = base64Cache [base64CacheStartsAt++];
190                                         if (bufIndex == bufLast)
191                                                 return bufLast - offset;
192                                 }
193                         }
194
195                         for (int i = 0; i < 3; i++)
196                                 base64Cache [i] = 0;
197                         base64CacheStartsAt = -1;
198
199                         int max = (int) System.Math.Ceiling (4.0 / 3 * length);
200                         int additional = max % 4;
201                         if (additional > 0)
202                                 max += 4 - additional;
203                         char [] chars = new char [max];
204                         int charsLength = getter != null ?
205                                 getter (chars, 0, max) :
206                                 ReadValueChunk (chars, 0, max);
207
208                         byte b = 0;
209                         byte work = 0;
210                         for (int i = 0; i < charsLength - 3; i++) {
211                                 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
212                                         break;
213                                 b = (byte) (GetBase64Byte (chars [i]) << 2);
214                                 if (bufIndex < bufLast)
215                                         buffer [bufIndex] = b;
216                                 else if (b != 0) {
217                                         if (base64CacheStartsAt < 0)
218                                                 base64CacheStartsAt = 0;
219                                         base64Cache [0] = b;
220                                 }
221                                 // charsLength mod 4 might not equals to 0.
222                                 if (++i == charsLength)
223                                         break;
224                                 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i))  == charsLength)
225                                         break;
226                                 b = GetBase64Byte (chars [i]);
227                                 work = (byte) (b >> 4);
228                                 if (bufIndex < bufLast) {
229                                         buffer [bufIndex] += work;
230                                         bufIndex++;
231                                 }
232                                 else if (work != 0) {
233                                         if (base64CacheStartsAt < 0)
234                                                 base64CacheStartsAt = 0;
235                                         base64Cache [0] += work;
236                                 }
237
238                                 work = (byte) ((b & 0xf) << 4);
239                                 if (bufIndex < bufLast) {
240                                         buffer [bufIndex] = work;
241                                 }
242                                 else if (work != 0) {
243                                         if (base64CacheStartsAt < 0)
244                                                 base64CacheStartsAt = 1;
245                                         base64Cache [1] = work;
246                                 }
247
248                                 if (++i == charsLength)
249                                         break;
250                                 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
251                                         break;
252                                 b = GetBase64Byte (chars [i]);
253                                 work = (byte) (b >> 2);
254                                 if (bufIndex < bufLast) {
255                                         buffer [bufIndex] += work;
256                                         bufIndex++;
257                                 }
258                                 else if (work != 0) {
259                                         if (base64CacheStartsAt < 0)
260                                                 base64CacheStartsAt = 1;
261                                         base64Cache [1] += work;
262                                 }
263
264                                 work = (byte) ((b & 3) << 6);
265                                 if (bufIndex < bufLast)
266                                         buffer [bufIndex] = work;
267                                 else if (work != 0) {
268                                         if (base64CacheStartsAt < 0)
269                                                 base64CacheStartsAt = 2;
270                                         base64Cache [2] = work;
271                                 }
272                                 if (++i == charsLength)
273                                         break;
274                                 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
275                                         break;
276                                 work = GetBase64Byte (chars [i]);
277                                 if (bufIndex < bufLast) {
278                                         buffer [bufIndex] += work;
279                                         bufIndex++;
280                                 }
281                                 else if (work != 0) {
282                                         if (base64CacheStartsAt < 0)
283                                                 base64CacheStartsAt = 2;
284                                         base64Cache [2] += work;
285                                 }
286                         }
287                         int ret = System.Math.Min (bufLast - offset, bufIndex - offset);
288                         if (ret < length && charsLength > 0)
289                                 return ret + ReadBase64 (buffer, offset + ret, length - ret);
290                         else
291                                 return ret;
292                 }
293
294                 // Since ReadBase64() is processed for every 4 chars, it does
295                 // not handle '=' here.
296                 private byte GetBase64Byte (char ch)
297                 {
298                         switch (ch) {
299                         case '+':
300                                 return 62;
301                         case '/':
302                                 return 63;
303                         default:
304                                 if (ch >= 'A' && ch <= 'Z')
305                                         return (byte) (ch - 'A');
306                                 else if (ch >= 'a' && ch <= 'z')
307                                         return (byte) (ch - 'a' + 26);
308                                 else if (ch >= '0' && ch <= '9')
309                                         return (byte) (ch - '0' + 52);
310                                 else
311                                         throw new XmlException ("Invalid Base64 character was found.");
312                         }
313                 }
314
315                 private int SkipIgnorableBase64Chars (char [] chars, int charsLength, int i)
316                 {
317                         while (chars [i] == '=' || XmlChar.IsWhitespace (chars [i]))
318                                 if (charsLength == ++i)
319                                         break;
320                         return i;
321                 }
322
323                 static Exception CreateArgumentOutOfRangeException (string name, object value, string message)
324                 {
325                         return new ArgumentOutOfRangeException (
326 #if !NET_2_1
327                                 name, value,
328 #endif
329                                 message);
330                 }
331
332                 public int ReadBinHex (byte [] buffer, int offset, int length)
333                 {
334                         if (offset < 0)
335                                 throw CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
336                         else if (length < 0)
337                                 throw CreateArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
338                         else if (buffer.Length < offset + length)
339                                 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
340
341                         if (length == 0)
342                                 return 0;
343
344                         char [] chars = new char [length * 2];
345                         int charsLength = getter != null ?
346                                 getter (chars, 0, length * 2) :
347                                 ReadValueChunk (chars, 0, length * 2);
348                         return XmlConvert.FromBinHexString (chars, offset, charsLength, buffer);
349                 }
350
351                 public int ReadValueChunk (
352                         char [] buffer, int offset, int length)
353                 {
354                         CommandState backup = state;
355                         if (state == CommandState.None)
356                                 CheckState (false, CommandState.None);
357
358                         if (offset < 0)
359                                 throw CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
360                         else if (length < 0)
361                                 throw CreateArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
362                         else if (buffer.Length < offset + length)
363                                 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
364
365                         if (length == 0)
366                                 return 0;
367
368                         if (!hasCache) {
369                                 if (reader.IsEmptyElement)
370                                         return 0;
371                         }
372
373                         bool loop = true;
374                         while (loop && textCache.Length < length) {
375                                 switch (reader.NodeType) {
376                                 case XmlNodeType.Text:
377                                 case XmlNodeType.CDATA:
378                                 case XmlNodeType.SignificantWhitespace:
379                                 case XmlNodeType.Whitespace:
380                                         if (hasCache) {
381                                                 switch (reader.NodeType) {
382                                                 case XmlNodeType.Text:
383                                                 case XmlNodeType.CDATA:
384                                                 case XmlNodeType.SignificantWhitespace:
385                                                 case XmlNodeType.Whitespace:
386                                                         Read ();
387                                                         break;
388                                                 default:
389                                                         loop = false;
390                                                         break;
391                                                 }
392                                         }
393                                         textCache.Append (reader.Value);
394                                         hasCache = true;
395                                         break;
396                                 default:
397                                         loop = false;
398                                         break;
399                                 }
400                         }
401                         state = backup;
402                         int min = textCache.Length;
403                         if (min > length)
404                                 min = length;
405                         string str = textCache.ToString (0, min);
406                         textCache.Remove (0, str.Length);
407                         str.CopyTo (0, buffer, offset, str.Length);
408                         if (min < length && loop)
409                                 return min + ReadValueChunk (buffer, offset + min, length - min);
410                         else
411                                 return min;
412                 }
413
414                 private bool Read ()
415                 {
416                         dontReset = true;
417                         bool b = reader.Read ();
418                         dontReset = false;
419                         return b;
420                 }
421         }
422 }