|
|
@@ -0,0 +1,394 @@
|
|
|
+//
|
|
|
+// System.Xml.XmlReaderBinarySupport.cs
|
|
|
+//
|
|
|
+// Author:
|
|
|
+// Atsushi Enomoto ([email protected])
|
|
|
+//
|
|
|
+// (C)2004 Novell Inc,
|
|
|
+//
|
|
|
+
|
|
|
+//
|
|
|
+// Permission is hereby granted, free of charge, to any person obtaining
|
|
|
+// a copy of this software and associated documentation files (the
|
|
|
+// "Software"), to deal in the Software without restriction, including
|
|
|
+// without limitation the rights to use, copy, modify, merge, publish,
|
|
|
+// distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
+// permit persons to whom the Software is furnished to do so, subject to
|
|
|
+// the following conditions:
|
|
|
+//
|
|
|
+// The above copyright notice and this permission notice shall be
|
|
|
+// included in all copies or substantial portions of the Software.
|
|
|
+//
|
|
|
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
|
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
|
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
|
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
+//
|
|
|
+
|
|
|
+using System;
|
|
|
+using System.Collections;
|
|
|
+using System.Text;
|
|
|
+
|
|
|
+namespace System.Xml
|
|
|
+{
|
|
|
+ internal class XmlReaderBinarySupport
|
|
|
+ {
|
|
|
+ public delegate int CharGetter (
|
|
|
+ char [] buffer, int offset, int length);
|
|
|
+
|
|
|
+ public enum CommandState {
|
|
|
+ None,
|
|
|
+ ReadElementContentAsBase64,
|
|
|
+ ReadContentAsBase64,
|
|
|
+ ReadElementContentAsBinHex,
|
|
|
+ ReadContentAsBinHex
|
|
|
+ }
|
|
|
+
|
|
|
+ public XmlReaderBinarySupport (XmlReader reader)
|
|
|
+ {
|
|
|
+ this.reader = reader;
|
|
|
+ Reset ();
|
|
|
+ }
|
|
|
+
|
|
|
+ XmlReader reader;
|
|
|
+ CharGetter getter;
|
|
|
+ byte [] base64Cache = new byte [3];
|
|
|
+ int base64CacheStartsAt;
|
|
|
+ CommandState state;
|
|
|
+ StringBuilder textCache;
|
|
|
+ bool hasCache;
|
|
|
+ bool dontReset;
|
|
|
+
|
|
|
+ public CharGetter Getter {
|
|
|
+ get { return getter; }
|
|
|
+ set { getter = value; }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Reset ()
|
|
|
+ {
|
|
|
+ if (!dontReset) {
|
|
|
+ dontReset = true;
|
|
|
+ if (hasCache) {
|
|
|
+ reader.Read ();
|
|
|
+ switch (state) {
|
|
|
+ case CommandState.ReadElementContentAsBase64:
|
|
|
+ case CommandState.ReadElementContentAsBinHex:
|
|
|
+ reader.Read ();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ base64CacheStartsAt = -1;
|
|
|
+ state = CommandState.None;
|
|
|
+ hasCache = false;
|
|
|
+ dontReset = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ InvalidOperationException StateError (CommandState action)
|
|
|
+ {
|
|
|
+ return new InvalidOperationException (
|
|
|
+ String.Format ("Invalid attempt to read binary content by {0}, while once binary reading was started by {1}", action, state));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CheckState (bool element, CommandState action)
|
|
|
+ {
|
|
|
+ if (state == CommandState.None) {
|
|
|
+ if (textCache == null)
|
|
|
+ textCache = new StringBuilder ();
|
|
|
+ else
|
|
|
+ textCache.Length = 0;
|
|
|
+ if (action == CommandState.None)
|
|
|
+ return; // for ReadValueChunk()
|
|
|
+ if (reader.ReadState != ReadState.Interactive)
|
|
|
+ return;
|
|
|
+ switch (reader.NodeType) {
|
|
|
+ case XmlNodeType.Text:
|
|
|
+ case XmlNodeType.CDATA:
|
|
|
+ case XmlNodeType.SignificantWhitespace:
|
|
|
+ case XmlNodeType.Whitespace:
|
|
|
+ if (!element) {
|
|
|
+ state = action;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case XmlNodeType.Element:
|
|
|
+ if (element) {
|
|
|
+ if (!reader.IsEmptyElement)
|
|
|
+ reader.Read ();
|
|
|
+ state = action;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ throw new XmlException ((element ?
|
|
|
+ "Reader is not positioned on an element."
|
|
|
+ : "Reader is not positioned on a text node."));
|
|
|
+ }
|
|
|
+ if (state == action)
|
|
|
+ return;
|
|
|
+ throw StateError (action);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ReadElementContentAsBase64 (
|
|
|
+ byte [] buffer, int offset, int length)
|
|
|
+ {
|
|
|
+ CheckState (true, CommandState.ReadElementContentAsBase64);
|
|
|
+ return ReadBase64 (buffer, offset, length);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ReadContentAsBase64 (
|
|
|
+ byte [] buffer, int offset, int length)
|
|
|
+ {
|
|
|
+ CheckState (false, CommandState.ReadContentAsBase64);
|
|
|
+ return ReadBase64 (buffer, offset, length);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ReadElementContentAsBinHex (
|
|
|
+ byte [] buffer, int offset, int length)
|
|
|
+ {
|
|
|
+ CheckState (true, CommandState.ReadElementContentAsBinHex);
|
|
|
+ return ReadBinHex (buffer, offset, length);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ReadContentAsBinHex (
|
|
|
+ byte [] buffer, int offset, int length)
|
|
|
+ {
|
|
|
+ CheckState (false, CommandState.ReadContentAsBinHex);
|
|
|
+ return ReadBinHex (buffer, offset, length);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ReadBase64 (byte [] buffer, int offset, int length)
|
|
|
+ {
|
|
|
+ if (offset < 0)
|
|
|
+ throw new ArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
|
|
|
+ else if (length < 0)
|
|
|
+ throw new ArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
|
|
|
+ else if (buffer.Length < offset + length)
|
|
|
+ throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
|
|
|
+
|
|
|
+ if (reader.IsEmptyElement)
|
|
|
+ return 0;
|
|
|
+ if (length == 0) // It does not raise an error.
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ int bufIndex = offset;
|
|
|
+ int bufLast = offset + length;
|
|
|
+
|
|
|
+ if (base64CacheStartsAt >= 0) {
|
|
|
+ for (int i = base64CacheStartsAt; i < 3; i++) {
|
|
|
+ buffer [bufIndex++] = base64Cache [base64CacheStartsAt++];
|
|
|
+ if (bufIndex == bufLast)
|
|
|
+ return bufLast - offset;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = 0; i < 3; i++)
|
|
|
+ base64Cache [i] = 0;
|
|
|
+ base64CacheStartsAt = -1;
|
|
|
+
|
|
|
+ int max = (int) System.Math.Ceiling (4.0 / 3 * length);
|
|
|
+ int additional = max % 4;
|
|
|
+ if (additional > 0)
|
|
|
+ max += 4 - additional;
|
|
|
+ char [] chars = new char [max];
|
|
|
+ int charsLength = getter != null ?
|
|
|
+ getter (chars, 0, max) :
|
|
|
+ ReadValueChunk (chars, 0, max);
|
|
|
+
|
|
|
+ byte b = 0;
|
|
|
+ byte work = 0;
|
|
|
+ for (int i = 0; i < charsLength - 3; i++) {
|
|
|
+ if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
|
|
|
+ break;
|
|
|
+ b = (byte) (GetBase64Byte (chars [i]) << 2);
|
|
|
+ if (bufIndex < bufLast)
|
|
|
+ buffer [bufIndex] = b;
|
|
|
+ else {
|
|
|
+ if (base64CacheStartsAt < 0)
|
|
|
+ base64CacheStartsAt = 0;
|
|
|
+ base64Cache [0] = b;
|
|
|
+ }
|
|
|
+ // charsLength mod 4 might not equals to 0.
|
|
|
+ if (++i == charsLength)
|
|
|
+ break;
|
|
|
+ if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
|
|
|
+ break;
|
|
|
+ b = GetBase64Byte (chars [i]);
|
|
|
+ work = (byte) (b >> 4);
|
|
|
+ if (bufIndex < bufLast) {
|
|
|
+ buffer [bufIndex] += work;
|
|
|
+ bufIndex++;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ base64Cache [0] += work;
|
|
|
+
|
|
|
+ work = (byte) ((b & 0xf) << 4);
|
|
|
+ if (bufIndex < bufLast) {
|
|
|
+ buffer [bufIndex] = work;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ if (base64CacheStartsAt < 0)
|
|
|
+ base64CacheStartsAt = 1;
|
|
|
+ base64Cache [1] = work;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (++i == charsLength)
|
|
|
+ break;
|
|
|
+ if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
|
|
|
+ break;
|
|
|
+ b = GetBase64Byte (chars [i]);
|
|
|
+ work = (byte) (b >> 2);
|
|
|
+ if (bufIndex < bufLast) {
|
|
|
+ buffer [bufIndex] += work;
|
|
|
+ bufIndex++;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ base64Cache [1] += work;
|
|
|
+
|
|
|
+ work = (byte) ((b & 3) << 6);
|
|
|
+ if (bufIndex < bufLast)
|
|
|
+ buffer [bufIndex] = work;
|
|
|
+ else {
|
|
|
+ if (base64CacheStartsAt < 0)
|
|
|
+ base64CacheStartsAt = 2;
|
|
|
+ base64Cache [2] = work;
|
|
|
+ }
|
|
|
+ if (++i == charsLength)
|
|
|
+ break;
|
|
|
+ if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
|
|
|
+ break;
|
|
|
+ work = GetBase64Byte (chars [i]);
|
|
|
+ if (bufIndex < bufLast) {
|
|
|
+ buffer [bufIndex] += work;
|
|
|
+ bufIndex++;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ base64Cache [2] += work;
|
|
|
+ }
|
|
|
+ int ret = System.Math.Min (bufLast - offset, bufIndex - offset);
|
|
|
+ if (ret < length && charsLength > 0)
|
|
|
+ return ret + ReadBase64 (buffer, offset + ret, length - ret);
|
|
|
+ else
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Since ReadBase64() is processed for every 4 chars, it does
|
|
|
+ // not handle '=' here.
|
|
|
+ private byte GetBase64Byte (char ch)
|
|
|
+ {
|
|
|
+ switch (ch) {
|
|
|
+ case '+':
|
|
|
+ return 62;
|
|
|
+ case '/':
|
|
|
+ return 63;
|
|
|
+ default:
|
|
|
+ if (ch >= 'A' && ch <= 'Z')
|
|
|
+ return (byte) (ch - 'A');
|
|
|
+ else if (ch >= 'a' && ch <= 'z')
|
|
|
+ return (byte) (ch - 'a' + 26);
|
|
|
+ else if (ch >= '0' && ch <= '9')
|
|
|
+ return (byte) (ch - '0' + 52);
|
|
|
+ else
|
|
|
+ throw new XmlException ("Invalid Base64 character was found.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private int SkipIgnorableBase64Chars (char [] chars, int charsLength, int i)
|
|
|
+ {
|
|
|
+ while (chars [i] == '=' || XmlChar.IsWhitespace (chars [i]))
|
|
|
+ if (charsLength == ++i)
|
|
|
+ break;
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ReadBinHex (byte [] buffer, int offset, int length)
|
|
|
+ {
|
|
|
+ if (offset < 0)
|
|
|
+ throw new ArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
|
|
|
+ else if (length < 0)
|
|
|
+ throw new ArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
|
|
|
+ else if (buffer.Length < offset + length)
|
|
|
+ throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
|
|
|
+
|
|
|
+ if (length == 0)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ char [] chars = new char [length * 2];
|
|
|
+ int charsLength = getter != null ?
|
|
|
+ getter (chars, 0, length * 2) :
|
|
|
+ ReadValueChunk (chars, 0, length * 2);
|
|
|
+ return XmlConvert.FromBinHexString (chars, offset, charsLength, buffer);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ReadValueChunk (
|
|
|
+ char [] buffer, int offset, int length)
|
|
|
+ {
|
|
|
+ CommandState backup = state;
|
|
|
+ if (state == CommandState.None)
|
|
|
+ CheckState (false, CommandState.None);
|
|
|
+
|
|
|
+ if (offset < 0)
|
|
|
+ throw new ArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
|
|
|
+ else if (length < 0)
|
|
|
+ throw new ArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
|
|
|
+ else if (buffer.Length < offset + length)
|
|
|
+ throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
|
|
|
+
|
|
|
+ if (length == 0)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (!hasCache) {
|
|
|
+ if (reader.IsEmptyElement)
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool consumeToEnd = false;
|
|
|
+ while (!consumeToEnd && textCache.Length < length) {
|
|
|
+ switch (reader.NodeType) {
|
|
|
+ case XmlNodeType.Text:
|
|
|
+ case XmlNodeType.CDATA:
|
|
|
+ case XmlNodeType.SignificantWhitespace:
|
|
|
+ case XmlNodeType.Whitespace:
|
|
|
+ if (hasCache) {
|
|
|
+ switch (reader.NodeType) {
|
|
|
+ case XmlNodeType.Text:
|
|
|
+ case XmlNodeType.CDATA:
|
|
|
+ case XmlNodeType.SignificantWhitespace:
|
|
|
+ case XmlNodeType.Whitespace:
|
|
|
+ Read ();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ consumeToEnd = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ textCache.Append (reader.Value);
|
|
|
+ hasCache = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ state = backup;
|
|
|
+ int min = textCache.Length;
|
|
|
+ if (min > length)
|
|
|
+ min = length;
|
|
|
+ string str = textCache.ToString (0, min);
|
|
|
+ textCache.Remove (0, str.Length);
|
|
|
+ str.CopyTo (0, buffer, offset, str.Length);
|
|
|
+ if (min < length)
|
|
|
+ return min + ReadValueChunk (buffer, offset + min, length - min);
|
|
|
+ else
|
|
|
+ return min;
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool Read ()
|
|
|
+ {
|
|
|
+ dontReset = true;
|
|
|
+ bool b = reader.Read ();
|
|
|
+ dontReset = false;
|
|
|
+ return b;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|