|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml;
namespace System.Xml
{
//
// XmlCharCheckingReaderWithNS
//
internal partial class XmlCharCheckingReader : XmlWrappingReader
{
//
// Private types
//
private enum State
{
Initial,
InReadBinary,
Error,
Interactive, // Interactive means other than ReadState.Initial and ReadState.Error; still needs to call
// underlying XmlReader to find out if the reported ReadState should be Interactive or EndOfFile
};
//
// Fields
//
private State _state;
// settings
private readonly bool _checkCharacters;
private readonly bool _ignoreWhitespace;
private readonly bool _ignoreComments;
private readonly bool _ignorePis;
private readonly DtdProcessing _dtdProcessing; // -1 means do nothing
private XmlNodeType _lastNodeType;
private ReadContentAsBinaryHelper? _readBinaryHelper;
//
// Constructor
//
internal XmlCharCheckingReader(XmlReader reader, bool checkCharacters, bool ignoreWhitespace, bool ignoreComments, bool ignorePis, DtdProcessing dtdProcessing)
: base(reader)
{
Debug.Assert(checkCharacters || ignoreWhitespace || ignoreComments || ignorePis || (int)dtdProcessing != -1);
_state = State.Initial;
_checkCharacters = checkCharacters;
_ignoreWhitespace = ignoreWhitespace;
_ignoreComments = ignoreComments;
_ignorePis = ignorePis;
_dtdProcessing = dtdProcessing;
_lastNodeType = XmlNodeType.None;
}
//
// XmlReader implementation
//
public override XmlReaderSettings Settings
{
get
{
XmlReaderSettings? settings = reader.Settings;
if (settings == null)
{
settings = new XmlReaderSettings();
}
else
{
settings = settings.Clone();
}
if (_checkCharacters)
{
settings.CheckCharacters = true;
}
if (_ignoreWhitespace)
{
settings.IgnoreWhitespace = true;
}
if (_ignoreComments)
{
settings.IgnoreComments = true;
}
if (_ignorePis)
{
settings.IgnoreProcessingInstructions = true;
}
if ((int)_dtdProcessing != -1)
{
settings.DtdProcessing = _dtdProcessing;
}
settings.ReadOnly = true;
return settings;
}
}
public override bool MoveToAttribute(string name)
{
if (_state == State.InReadBinary)
{
FinishReadBinary();
}
return base.reader.MoveToAttribute(name);
}
public override bool MoveToAttribute(string name, string? ns)
{
if (_state == State.InReadBinary)
{
FinishReadBinary();
}
return base.reader.MoveToAttribute(name, ns);
}
public override void MoveToAttribute(int i)
{
if (_state == State.InReadBinary)
{
FinishReadBinary();
}
base.reader.MoveToAttribute(i);
}
public override bool MoveToFirstAttribute()
{
if (_state == State.InReadBinary)
{
FinishReadBinary();
}
return base.reader.MoveToFirstAttribute();
}
public override bool MoveToNextAttribute()
{
if (_state == State.InReadBinary)
{
FinishReadBinary();
}
return base.reader.MoveToNextAttribute();
}
public override bool MoveToElement()
{
if (_state == State.InReadBinary)
{
FinishReadBinary();
}
return base.reader.MoveToElement();
}
public override bool Read()
{
switch (_state)
{
case State.Initial:
_state = State.Interactive;
if (base.reader.ReadState == ReadState.Initial)
{
goto case State.Interactive;
}
break;
case State.Error:
return false;
case State.InReadBinary:
FinishReadBinary();
_state = State.Interactive;
goto case State.Interactive;
case State.Interactive:
if (!base.reader.Read())
{
return false;
}
break;
default:
Debug.Fail($"Unexpected state {_state}");
return false;
}
XmlNodeType nodeType = base.reader.NodeType;
if (!_checkCharacters)
{
switch (nodeType)
{
case XmlNodeType.Comment:
if (_ignoreComments)
{
return Read();
}
break;
case XmlNodeType.Whitespace:
if (_ignoreWhitespace)
{
return Read();
}
break;
case XmlNodeType.ProcessingInstruction:
if (_ignorePis)
{
return Read();
}
break;
case XmlNodeType.DocumentType:
if (_dtdProcessing == DtdProcessing.Prohibit)
{
Throw(SR.Xml_DtdIsProhibitedEx, string.Empty);
}
else if (_dtdProcessing == DtdProcessing.Ignore)
{
return Read();
}
break;
}
return true;
}
else
{
switch (nodeType)
{
case XmlNodeType.Element:
if (_checkCharacters)
{
// check element name
ValidateQName(base.reader.Prefix, base.reader.LocalName);
// check values of attributes
if (base.reader.MoveToFirstAttribute())
{
do
{
ValidateQName(base.reader.Prefix, base.reader.LocalName);
CheckCharacters(base.reader.Value);
} while (base.reader.MoveToNextAttribute());
base.reader.MoveToElement();
}
}
break;
case XmlNodeType.Text:
case XmlNodeType.CDATA:
if (_checkCharacters)
{
CheckCharacters(base.reader.Value);
}
break;
case XmlNodeType.EntityReference:
if (_checkCharacters)
{
// check name
ValidateQName(base.reader.Name);
}
break;
case XmlNodeType.ProcessingInstruction:
if (_ignorePis)
{
return Read();
}
if (_checkCharacters)
{
ValidateQName(base.reader.Name);
CheckCharacters(base.reader.Value);
}
break;
case XmlNodeType.Comment:
if (_ignoreComments)
{
return Read();
}
if (_checkCharacters)
{
CheckCharacters(base.reader.Value);
}
break;
case XmlNodeType.DocumentType:
if (_dtdProcessing == DtdProcessing.Prohibit)
{
Throw(SR.Xml_DtdIsProhibitedEx, string.Empty);
}
else if (_dtdProcessing == DtdProcessing.Ignore)
{
return Read();
}
if (_checkCharacters)
{
ValidateQName(base.reader.Name);
CheckCharacters(base.reader.Value);
string? str;
str = base.reader.GetAttribute("SYSTEM");
if (str != null)
{
CheckCharacters(str);
}
str = base.reader.GetAttribute("PUBLIC");
if (str != null)
{
int i;
if ((i = XmlCharType.IsPublicId(str)) >= 0)
{
Throw(SR.Xml_InvalidCharacter, XmlException.BuildCharExceptionArgs(str, i));
}
}
}
break;
case XmlNodeType.Whitespace:
if (_ignoreWhitespace)
{
return Read();
}
if (_checkCharacters)
{
CheckWhitespace(base.reader.Value);
}
break;
case XmlNodeType.SignificantWhitespace:
if (_checkCharacters)
{
CheckWhitespace(base.reader.Value);
}
break;
case XmlNodeType.EndElement:
if (_checkCharacters)
{
ValidateQName(base.reader.Prefix, base.reader.LocalName);
}
break;
default:
break;
}
_lastNodeType = nodeType;
return true;
}
}
public override ReadState ReadState
{
get
{
switch (_state)
{
case State.Initial:
return base.reader.ReadState == ReadState.Closed ? ReadState.Closed : ReadState.Initial;
case State.Error:
return ReadState.Error;
case State.InReadBinary:
case State.Interactive:
default:
return base.reader.ReadState;
}
}
}
public override bool ReadAttributeValue()
{
if (_state == State.InReadBinary)
{
FinishReadBinary();
}
return base.reader.ReadAttributeValue();
}
public override bool CanReadBinaryContent
{
get
{
return true;
}
}
public override int ReadContentAsBase64(byte[] buffer, int index, int count)
{
if (ReadState != ReadState.Interactive)
{
return 0;
}
if (_state != State.InReadBinary)
{
// forward ReadBase64Chunk calls into the base (wrapped) reader if possible, i.e. if it can read binary and we
// should not check characters
if (base.CanReadBinaryContent && (!_checkCharacters))
{
_readBinaryHelper = null;
_state = State.InReadBinary;
return base.ReadContentAsBase64(buffer, index, count);
}
// the wrapped reader cannot read chunks or we are on an element where we should check characters or ignore whitespace
else
{
_readBinaryHelper = ReadContentAsBinaryHelper.CreateOrReset(_readBinaryHelper, this);
}
}
else
{
// forward calls into wrapped reader
if (_readBinaryHelper == null)
{
return base.ReadContentAsBase64(buffer, index, count);
}
}
// turn off InReadBinary state in order to have a normal Read() behavior when called from readBinaryHelper
_state = State.Interactive;
// call to the helper
int readCount = _readBinaryHelper.ReadContentAsBase64(buffer, index, count);
// turn on InReadBinary in again and return
_state = State.InReadBinary;
return readCount;
}
public override int ReadContentAsBinHex(byte[] buffer, int index, int count)
{
if (ReadState != ReadState.Interactive)
{
return 0;
}
if (_state != State.InReadBinary)
{
// forward ReadBinHexChunk calls into the base (wrapped) reader if possible, i.e. if it can read chunks and we
// should not check characters
if (base.CanReadBinaryContent && (!_checkCharacters))
{
_readBinaryHelper = null;
_state = State.InReadBinary;
return base.ReadContentAsBinHex(buffer, index, count);
}
// the wrapped reader cannot read chunks or we are on an element where we should check characters or ignore whitespace
else
{
_readBinaryHelper = ReadContentAsBinaryHelper.CreateOrReset(_readBinaryHelper, this);
}
}
else
{
// forward calls into wrapped reader
if (_readBinaryHelper == null)
{
return base.ReadContentAsBinHex(buffer, index, count);
}
}
// turn off InReadBinary state in order to have a normal Read() behavior when called from readBinaryHelper
_state = State.Interactive;
// call to the helper
int readCount = _readBinaryHelper.ReadContentAsBinHex(buffer, index, count);
// turn on InReadBinary in again and return
_state = State.InReadBinary;
return readCount;
}
public override int ReadElementContentAsBase64(byte[] buffer, int index, int count)
{
ArgumentNullException.ThrowIfNull(buffer);
ArgumentOutOfRangeException.ThrowIfNegative(count);
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThan(count, buffer.Length - index);
if (ReadState != ReadState.Interactive)
{
return 0;
}
if (_state != State.InReadBinary)
{
// forward ReadBase64Chunk calls into the base (wrapped) reader if possible, i.e. if it can read binary and we
// should not check characters
if (base.CanReadBinaryContent && (!_checkCharacters))
{
_readBinaryHelper = null;
_state = State.InReadBinary;
return base.ReadElementContentAsBase64(buffer, index, count);
}
// the wrapped reader cannot read chunks or we are on an element where we should check characters or ignore whitespace
else
{
_readBinaryHelper = ReadContentAsBinaryHelper.CreateOrReset(_readBinaryHelper, this);
}
}
else
{
// forward calls into wrapped reader
if (_readBinaryHelper == null)
{
return base.ReadElementContentAsBase64(buffer, index, count);
}
}
// turn off InReadBinary state in order to have a normal Read() behavior when called from readBinaryHelper
_state = State.Interactive;
// call to the helper
int readCount = _readBinaryHelper.ReadElementContentAsBase64(buffer, index, count);
// turn on InReadBinary in again and return
_state = State.InReadBinary;
return readCount;
}
public override int ReadElementContentAsBinHex(byte[] buffer, int index, int count)
{
ArgumentNullException.ThrowIfNull(buffer);
ArgumentOutOfRangeException.ThrowIfNegative(count);
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThan(count, buffer.Length - index);
if (ReadState != ReadState.Interactive)
{
return 0;
}
if (_state != State.InReadBinary)
{
// forward ReadBinHexChunk calls into the base (wrapped) reader if possible, i.e. if it can read chunks and we
// should not check characters
if (base.CanReadBinaryContent && (!_checkCharacters))
{
_readBinaryHelper = null;
_state = State.InReadBinary;
return base.ReadElementContentAsBinHex(buffer, index, count);
}
// the wrapped reader cannot read chunks or we are on an element where we should check characters or ignore whitespace
else
{
_readBinaryHelper = ReadContentAsBinaryHelper.CreateOrReset(_readBinaryHelper, this);
}
}
else
{
// forward calls into wrapped reader
if (_readBinaryHelper == null)
{
return base.ReadElementContentAsBinHex(buffer, index, count);
}
}
// turn off InReadBinary state in order to have a normal Read() behavior when called from readBinaryHelper
_state = State.Interactive;
// call to the helper
int readCount = _readBinaryHelper.ReadElementContentAsBinHex(buffer, index, count);
// turn on InReadBinary in again and return
_state = State.InReadBinary;
return readCount;
}
//
// Private methods and properties
//
private void Throw(string res, string arg)
{
_state = State.Error;
throw new XmlException(res, arg, (IXmlLineInfo?)null);
}
private void Throw(string res, string[] args)
{
_state = State.Error;
throw new XmlException(res, args, (IXmlLineInfo?)null);
}
private void CheckWhitespace(string value)
{
int i;
if ((i = XmlCharType.IsOnlyWhitespaceWithPos(value)) != -1)
{
Throw(SR.Xml_InvalidWhitespaceCharacter, XmlException.BuildCharExceptionArgs(value, i));
}
}
private static void ValidateQName(string name)
{
ValidateNames.ParseQNameThrow(name);
}
private void ValidateQName(string prefix, string localName)
{
try
{
if (prefix.Length > 0)
{
ValidateNames.ParseNCNameThrow(prefix);
}
ValidateNames.ParseNCNameThrow(localName);
}
catch
{
_state = State.Error;
throw;
}
}
private static void CheckCharacters(string value)
{
XmlConvert.VerifyCharData(value, ExceptionType.ArgumentException, ExceptionType.XmlException);
}
private void FinishReadBinary()
{
_state = State.Interactive;
_readBinaryHelper?.Finish();
}
}
//
// XmlCharCheckingReaderWithNS
//
internal sealed class XmlCharCheckingReaderWithNS : XmlCharCheckingReader, IXmlNamespaceResolver
{
internal IXmlNamespaceResolver readerAsNSResolver;
internal XmlCharCheckingReaderWithNS(XmlReader reader, IXmlNamespaceResolver readerAsNSResolver, bool checkCharacters, bool ignoreWhitespace, bool ignoreComments, bool ignorePis, DtdProcessing dtdProcessing)
: base(reader, checkCharacters, ignoreWhitespace, ignoreComments, ignorePis, dtdProcessing)
{
Debug.Assert(readerAsNSResolver != null);
this.readerAsNSResolver = readerAsNSResolver;
}
//
// IXmlNamespaceResolver
//
IDictionary<string, string> IXmlNamespaceResolver.GetNamespacesInScope(XmlNamespaceScope scope)
{
return readerAsNSResolver.GetNamespacesInScope(scope);
}
string? IXmlNamespaceResolver.LookupNamespace(string prefix)
{
return readerAsNSResolver.LookupNamespace(prefix);
}
string? IXmlNamespaceResolver.LookupPrefix(string namespaceName)
{
return readerAsNSResolver.LookupPrefix(namespaceName);
}
}
}
|