//
// System.Xml.XmlReaderBinarySupport.cs
//
// Author:
// Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
//
// (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) {
switch (reader.NodeType) {
case XmlNodeType.Text:
case XmlNodeType.CDATA:
case XmlNodeType.SignificantWhitespace:
case XmlNodeType.Whitespace:
reader.Read ();
break;
}
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 CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
else if (length < 0)
throw CreateArgumentOutOfRangeException ("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 (b != 0) {
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 if (work != 0) {
if (base64CacheStartsAt < 0)
base64CacheStartsAt = 0;
base64Cache [0] += work;
}
work = (byte) ((b & 0xf) << 4);
if (bufIndex < bufLast) {
buffer [bufIndex] = work;
}
else if (work != 0) {
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 if (work != 0) {
if (base64CacheStartsAt < 0)
base64CacheStartsAt = 1;
base64Cache [1] += work;
}
work = (byte) ((b & 3) << 6);
if (bufIndex < bufLast)
buffer [bufIndex] = work;
else if (work != 0) {
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 if (work != 0) {
if (base64CacheStartsAt < 0)
base64CacheStartsAt = 2;
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;
}
static Exception CreateArgumentOutOfRangeException (string name, object value, string message)
{
return new ArgumentOutOfRangeException (
#if !NET_2_1
name, value,
#endif
message);
}
public int ReadBinHex (byte [] buffer, int offset, int length)
{
if (offset < 0)
throw CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
else if (length < 0)
throw CreateArgumentOutOfRangeException ("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 CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
else if (length < 0)
throw CreateArgumentOutOfRangeException ("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 loop = true;
while (loop && 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:
loop = false;
break;
}
}
textCache.Append (reader.Value);
hasCache = true;
break;
default:
loop = false;
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 && loop)
return min + ReadValueChunk (buffer, offset + min, length - min);
else
return min;
}
private bool Read ()
{
dontReset = true;
bool b = reader.Read ();
dontReset = false;
return b;
}
}
}
|