SEBWIN-352: Implemented new configuration format, i.e. parsing of compressed XML data. Also removed BOM from XML unit test data file.

This commit is contained in:
dbuechel 2020-01-30 15:20:05 +01:00
parent 15f9acd917
commit de00dbc13c
5 changed files with 76 additions and 41 deletions

View file

@ -15,9 +15,10 @@ using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Configuration.ConfigurationData;
using SafeExamBrowser.Configuration.DataFormats;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.DataCompression;
using SafeExamBrowser.Configuration.Contracts.DataFormats;
using SafeExamBrowser.Configuration.DataFormats;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Configuration.UnitTests.DataFormats
@ -25,15 +26,17 @@ namespace SafeExamBrowser.Configuration.UnitTests.DataFormats
[TestClass]
public class XmlParserTests
{
private Mock<IDataCompressor> compressor;
private Mock<ILogger> logger;
private XmlParser sut;
[TestInitialize]
public void Initialize()
{
compressor = new Mock<IDataCompressor>();
logger = new Mock<ILogger>();
sut = new XmlParser(logger.Object);
sut = new XmlParser(compressor.Object, logger.Object);
}
[TestMethod]

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<plist>
<dict>
<key>someString</key>

View file

@ -79,6 +79,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
{
var prefix = ReadPrefix(data);
var isValid = IsValid(prefix);
var result = new ParseResult { Status = LoadStatus.InvalidData };
if (isValid)
{
@ -89,18 +90,25 @@ namespace SafeExamBrowser.Configuration.DataFormats
{
case BinaryBlock.Password:
case BinaryBlock.PasswordConfigureClient:
return ParsePasswordBlock(data, prefix, password);
result = ParsePasswordBlock(data, prefix, password);
break;
case BinaryBlock.PlainData:
return ParsePlainDataBlock(data);
result = ParsePlainDataBlock(data);
break;
case BinaryBlock.PublicKey:
case BinaryBlock.PublicKeySymmetric:
return ParsePublicKeyBlock(data, prefix, password);
result = ParsePublicKeyBlock(data, prefix, password);
break;
}
result.Format = FormatType.Binary;
}
else
{
logger.Error($"'{data}' starting with '{prefix}' does not match the {FormatType.Binary} format!");
}
logger.Error($"'{data}' starting with '{prefix}' does not match the {FormatType.Binary} format!");
return new ParseResult { Status = LoadStatus.InvalidData };
return result;
}
private ParseResult ParsePasswordBlock(Stream data, string prefix, PasswordParameters password = null)
@ -133,10 +141,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
data = compressor.IsCompressed(data) ? compressor.Decompress(data) : data;
logger.Debug("Attempting to parse plain data block...");
var result = xmlParser.TryParse(data);
result.Format = FormatType.Binary;
return result;
return xmlParser.TryParse(data);
}
private ParseResult ParsePublicKeyBlock(Stream data, string prefix, PasswordParameters password = null)

View file

@ -14,6 +14,7 @@ using System.Xml;
using System.Xml.Linq;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Configuration.Contracts.DataCompression;
using SafeExamBrowser.Configuration.Contracts.DataFormats;
using SafeExamBrowser.Logging.Contracts;
@ -23,10 +24,12 @@ namespace SafeExamBrowser.Configuration.DataFormats
{
private const string XML_PREFIX = "<?xm";
private IDataCompressor compressor;
private ILogger logger;
public XmlParser(ILogger logger)
public XmlParser(IDataCompressor compressor, ILogger logger)
{
this.compressor = compressor;
this.logger = logger;
}
@ -34,20 +37,16 @@ namespace SafeExamBrowser.Configuration.DataFormats
{
try
{
var prefixData = new byte[XML_PREFIX.Length];
var longEnough = data.Length > prefixData.Length;
var longEnough = data.Length > XML_PREFIX.Length;
if (longEnough)
{
data.Seek(0, SeekOrigin.Begin);
data.Read(prefixData, 0, prefixData.Length);
var prefix = ReadPrefix(data);
var isValid = XML_PREFIX.Equals(prefix, StringComparison.OrdinalIgnoreCase);
var prefix = Encoding.UTF8.GetString(prefixData);
var isXml = prefix == XML_PREFIX;
logger.Debug($"'{data}' starting with '{prefix}' {(isValid ? "matches" : "does not match")} the {FormatType.Xml} format.");
logger.Debug($"'{data}' starting with '{prefix}' {(isXml ? "matches" : "does not match")} the {FormatType.Xml} format.");
return isXml;
return isValid;
}
logger.Debug($"'{data}' is not long enough ({data.Length} bytes) to match the {FormatType.Xml} format.");
@ -62,29 +61,40 @@ namespace SafeExamBrowser.Configuration.DataFormats
public ParseResult TryParse(Stream data, PasswordParameters password = null)
{
var result = new ParseResult { Format = FormatType.Xml, Status = LoadStatus.InvalidData };
var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
var prefix = ReadPrefix(data);
var isValid = XML_PREFIX.Equals(prefix, StringComparison.OrdinalIgnoreCase);
var result = new ParseResult { Status = LoadStatus.InvalidData };
data.Seek(0, SeekOrigin.Begin);
using (var reader = XmlReader.Create(data, settings))
if (isValid)
{
var hasRoot = reader.ReadToFollowing(XmlElement.Root);
var hasDictionary = reader.ReadToDescendant(XmlElement.Dictionary);
data = compressor.IsCompressed(data) ? compressor.Decompress(data) : data;
data.Seek(0, SeekOrigin.Begin);
if (hasRoot && hasDictionary)
using (var reader = XmlReader.Create(data, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }))
{
logger.Debug($"Found root node, starting to parse data...");
var hasRoot = reader.ReadToFollowing(XmlElement.Root);
var hasDictionary = reader.ReadToDescendant(XmlElement.Dictionary);
result.Status = ParseDictionary(reader, out var rawData);
result.RawData = rawData;
if (hasRoot && hasDictionary)
{
logger.Debug($"Found root node, starting to parse data...");
logger.Debug($"Finished parsing -> Result: {result.Status}.");
}
else
{
logger.Error($"Could not find root {(!hasRoot ? $"node '{XmlElement.Root}'" : $"dictionary '{XmlElement.Dictionary}'")}!");
result.Status = ParseDictionary(reader, out var rawData);
result.RawData = rawData;
logger.Debug($"Finished parsing -> Result: {result.Status}.");
}
else
{
logger.Error($"Could not find root {(!hasRoot ? $"node '{XmlElement.Root}'" : $"dictionary '{XmlElement.Dictionary}'")}!");
}
}
result.Format = FormatType.Xml;
}
else
{
logger.Error($"'{data}' starting with '{prefix}' does not match the {FormatType.Xml} format!");
}
return result;
@ -269,5 +279,22 @@ namespace SafeExamBrowser.Configuration.DataFormats
return status;
}
private string ReadPrefix(Stream data)
{
var prefixData = new byte[XML_PREFIX.Length];
if (compressor.IsCompressed(data))
{
prefixData = compressor.Peek(data, XML_PREFIX.Length);
}
else
{
data.Seek(0, SeekOrigin.Begin);
data.Read(prefixData, 0, XML_PREFIX.Length);
}
return Encoding.UTF8.GetString(prefixData);
}
}
}

View file

@ -141,7 +141,7 @@ namespace SafeExamBrowser.Runtime
var publicKeyEncryption = new PublicKeyEncryption(certificateStore, ModuleLogger(nameof(PublicKeyEncryption)));
var symmetricEncryption = new PublicKeySymmetricEncryption(certificateStore, ModuleLogger(nameof(PublicKeySymmetricEncryption)), passwordEncryption);
var repositoryLogger = ModuleLogger(nameof(ConfigurationRepository));
var xmlParser = new XmlParser(ModuleLogger(nameof(XmlParser)));
var xmlParser = new XmlParser(compressor, ModuleLogger(nameof(XmlParser)));
var xmlSerializer = new XmlSerializer(ModuleLogger(nameof(XmlSerializer)));
configuration = new ConfigurationRepository(
@ -169,7 +169,7 @@ namespace SafeExamBrowser.Runtime
publicKeyEncryption,
symmetricEncryption,
xmlSerializer));
configuration.Register(new XmlParser(ModuleLogger(nameof(XmlParser))));
configuration.Register(new XmlParser(compressor, ModuleLogger(nameof(XmlParser))));
configuration.Register(new XmlSerializer(ModuleLogger(nameof(XmlSerializer))));
configuration.Register(new FileResourceLoader(ModuleLogger(nameof(FileResourceLoader))));
configuration.Register(new FileResourceSaver(ModuleLogger(nameof(FileResourceSaver))));