New test.
[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                                         reader.Read ();
76                                         switch (state) {
77                                         case CommandState.ReadElementContentAsBase64:
78                                         case CommandState.ReadElementContentAsBinHex:
79                                                 reader.Read ();
80                                                 break;
81                                         }
82                                 }
83                                 base64CacheStartsAt = -1;
84                                 state = CommandState.None;
85                                 hasCache = false;
86                                 dontReset = false;
87                         }
88                 }
89
90                 InvalidOperationException StateError (CommandState action)
91                 {
92                         return new InvalidOperationException (
93                                 String.Format ("Invalid attempt to read binary content by {0}, while once binary reading was started by {1}", action, state));
94                 }
95
96                 private void CheckState (bool element, CommandState action)
97                 {
98                         if (state == CommandState.None) {
99                                 if (textCache == null)
100                                         textCache = new StringBuilder ();
101                                 else
102                                         textCache.Length = 0;
103                                 if (action == CommandState.None)
104                                         return; // for ReadValueChunk()
105                                 if (reader.ReadState != ReadState.Interactive)
106                                         return;
107                                 switch (reader.NodeType) {
108                                 case XmlNodeType.Text:
109                                 case XmlNodeType.CDATA:
110                                 case XmlNodeType.SignificantWhitespace:
111                                 case XmlNodeType.Whitespace:
112                                         if (!element) {
113                                                 state = action;
114                                                 return;
115                                         }
116                                         break;
117                                 case XmlNodeType.Element:
118                                         if (element) {
119                                                 if (!reader.IsEmptyElement)
120                                                         reader.Read ();
121                                                 state = action;
122                                                 return;
123                                         }
124                                         break;
125                                 }
126                                 throw new XmlException ((element ? 
127                                         "Reader is not positioned on an element."
128                                         : "Reader is not positioned on a text node."));
129                         }
130                         if (state == action)
131                                 return;
132                         throw StateError (action);
133                 }
134
135                 public int ReadElementContentAsBase64 (
136                         byte [] buffer, int offset, int length)
137                 {
138                         CheckState (true, CommandState.ReadElementContentAsBase64);
139                         return ReadBase64 (buffer, offset, length);
140                 }
141
142                 public int ReadContentAsBase64 (
143                         byte [] buffer, int offset, int length)
144                 {
145                         CheckState (false, CommandState.ReadContentAsBase64);
146                         return ReadBase64 (buffer, offset, length);
147                 }
148
149                 public int ReadElementContentAsBinHex (
150                         byte [] buffer, int offset, int length)
151                 {
152                         CheckState (true, CommandState.ReadElementContentAsBinHex);
153                         return ReadBinHex (buffer, offset, length);
154                 }
155
156                 public int ReadContentAsBinHex (
157                         byte [] buffer, int offset, int length)
158                 {
159                         CheckState (false, CommandState.ReadContentAsBinHex);
160                         return ReadBinHex (buffer, offset, length);
161                 }
162
163                 public int ReadBase64 (byte [] buffer, int offset, int length)
164                 {
165                         if (offset < 0)
166                                 throw new ArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
167                         else if (length < 0)
168                                 throw new ArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
169                         else if (buffer.Length < offset + length)
170                                 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
171
172                         if (reader.IsEmptyElement)
173                                 return 0;
174                         if (length == 0)        // It does not raise an error.
175                                 return 0;
176
177                         int bufIndex = offset;
178                         int bufLast = offset + length;
179
180                         if (base64CacheStartsAt >= 0) {
181                                 for (int i = base64CacheStartsAt; i < 3; i++) {
182                                         buffer [bufIndex++] = base64Cache [base64CacheStartsAt++];
183                                         if (bufIndex == bufLast)
184                                                 return bufLast - offset;
185                                 }
186                         }
187
188                         for (int i = 0; i < 3; i++)
189                                 base64Cache [i] = 0;
190                         base64CacheStartsAt = -1;
191
192                         int max = (int) System.Math.Ceiling (4.0 / 3 * length);
193                         int additional = max % 4;
194                         if (additional > 0)
195                                 max += 4 - additional;
196                         char [] chars = new char [max];
197                         int charsLength = getter != null ?
198                                 getter (chars, 0, max) :
199                                 ReadValueChunk (chars, 0, max);
200
201                         byte b = 0;
202                         byte work = 0;
203                         for (int i = 0; i < charsLength - 3; i++) {
204                                 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
205                                         break;
206                                 b = (byte) (GetBase64Byte (chars [i]) << 2);
207                                 if (bufIndex < bufLast)
208                                         buffer [bufIndex] = b;
209                                 else {
210                                         if (base64CacheStartsAt < 0)
211                                                 base64CacheStartsAt = 0;
212                                         base64Cache [0] = b;
213                                 }
214                                 // charsLength mod 4 might not equals to 0.
215                                 if (++i == charsLength)
216                                         break;
217                                 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i))  == charsLength)
218                                         break;
219                                 b = GetBase64Byte (chars [i]);
220                                 work = (byte) (b >> 4);
221                                 if (bufIndex < bufLast) {
222                                         buffer [bufIndex] += work;
223                                         bufIndex++;
224                                 }
225                                 else
226                                         base64Cache [0] += work;
227
228                                 work = (byte) ((b & 0xf) << 4);
229                                 if (bufIndex < bufLast) {
230                                         buffer [bufIndex] = work;
231                                 }
232                                 else {
233                                         if (base64CacheStartsAt < 0)
234                                                 base64CacheStartsAt = 1;
235                                         base64Cache [1] = work;
236                                 }
237
238                                 if (++i == charsLength)
239                                         break;
240                                 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
241                                         break;
242                                 b = GetBase64Byte (chars [i]);
243                                 work = (byte) (b >> 2);
244                                 if (bufIndex < bufLast) {
245                                         buffer [bufIndex] += work;
246                                         bufIndex++;
247                                 }
248                                 else
249                                         base64Cache [1] += work;
250
251                                 work = (byte) ((b & 3) << 6);
252                                 if (bufIndex < bufLast)
253                                         buffer [bufIndex] = work;
254                                 else {
255                                         if (base64CacheStartsAt < 0)
256                                                 base64CacheStartsAt = 2;
257                                         base64Cache [2] = work;
258                                 }
259                                 if (++i == charsLength)
260                                         break;
261                                 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
262                                         break;
263                                 work = GetBase64Byte (chars [i]);
264                                 if (bufIndex < bufLast) {
265                                         buffer [bufIndex] += work;
266                                         bufIndex++;
267                                 }
268                                 else
269                                         base64Cache [2] += work;
270                         }
271                         int ret = System.Math.Min (bufLast - offset, bufIndex - offset);
272                         if (ret < length && charsLength > 0)
273                                 return ret + ReadBase64 (buffer, offset + ret, length - ret);
274                         else
275                                 return ret;
276                 }
277
278                 // Since ReadBase64() is processed for every 4 chars, it does
279                 // not handle '=' here.
280                 private byte GetBase64Byte (char ch)
281                 {
282                         switch (ch) {
283                         case '+':
284                                 return 62;
285                         case '/':
286                                 return 63;
287                         default:
288                                 if (ch >= 'A' && ch <= 'Z')
289                                         return (byte) (ch - 'A');
290                                 else if (ch >= 'a' && ch <= 'z')
291                                         return (byte) (ch - 'a' + 26);
292                                 else if (ch >= '0' && ch <= '9')
293                                         return (byte) (ch - '0' + 52);
294                                 else
295                                         throw new XmlException ("Invalid Base64 character was found.");
296                         }
297                 }
298
299                 private int SkipIgnorableBase64Chars (char [] chars, int charsLength, int i)
300                 {
301                         while (chars [i] == '=' || XmlChar.IsWhitespace (chars [i]))
302                                 if (charsLength == ++i)
303                                         break;
304                         return i;
305                 }
306
307                 public int ReadBinHex (byte [] buffer, int offset, int length)
308                 {
309                         if (offset < 0)
310                                 throw new ArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
311                         else if (length < 0)
312                                 throw new ArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
313                         else if (buffer.Length < offset + length)
314                                 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
315
316                         if (length == 0)
317                                 return 0;
318
319                         char [] chars = new char [length * 2];
320                         int charsLength = getter != null ?
321                                 getter (chars, 0, length * 2) :
322                                 ReadValueChunk (chars, 0, length * 2);
323                         return XmlConvert.FromBinHexString (chars, offset, charsLength, buffer);
324                 }
325
326                 public int ReadValueChunk (
327                         char [] buffer, int offset, int length)
328                 {
329                         CommandState backup = state;
330                         if (state == CommandState.None)
331                                 CheckState (false, CommandState.None);
332
333                         if (offset < 0)
334                                 throw new ArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
335                         else if (length < 0)
336                                 throw new ArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
337                         else if (buffer.Length < offset + length)
338                                 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
339
340                         if (length == 0)
341                                 return 0;
342
343                         if (!hasCache) {
344                                 if (reader.IsEmptyElement)
345                                         return 0;
346                         }
347
348                         bool consumeToEnd = false;
349                         while (!consumeToEnd && textCache.Length < length) {
350                                 switch (reader.NodeType) {
351                                 case XmlNodeType.Text:
352                                 case XmlNodeType.CDATA:
353                                 case XmlNodeType.SignificantWhitespace:
354                                 case XmlNodeType.Whitespace:
355                                         if (hasCache) {
356                                                 switch (reader.NodeType) {
357                                                 case XmlNodeType.Text:
358                                                 case XmlNodeType.CDATA:
359                                                 case XmlNodeType.SignificantWhitespace:
360                                                 case XmlNodeType.Whitespace:
361                                                         Read ();
362                                                         break;
363                                                 default:
364                                                         consumeToEnd = true;
365                                                         break;
366                                                 }
367                                         }
368                                         textCache.Append (reader.Value);
369                                         hasCache = true;
370                                         break;
371                                 }
372                         }
373                         state = backup;
374                         int min = textCache.Length;
375                         if (min > length)
376                                 min = length;
377                         string str = textCache.ToString (0, min);
378                         textCache.Remove (0, str.Length);
379                         str.CopyTo (0, buffer, offset, str.Length);
380                         if (min < length)
381                                 return min + ReadValueChunk (buffer, offset + min, length - min);
382                         else
383                                 return min;
384                 }
385
386                 private bool Read ()
387                 {
388                         dontReset = true;
389                         bool b = reader.Read ();
390                         dontReset = false;
391                         return b;
392                 }
393         }
394 }