Skip to content

Commit 27a707e

Browse files
committed
Add the option to perform schema validation over messages before parsing them.
1 parent 542827f commit 27a707e

File tree

1 file changed

+68
-3
lines changed

1 file changed

+68
-3
lines changed

src/DOMDocumentFactory.php

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,25 @@
55
namespace SimpleSAML\XML;
66

77
use DOMDocument;
8+
use Exception;
9+
use LibXMLError;
810
use SimpleSAML\Assert\Assert;
911
use SimpleSAML\XML\Exception\IOException;
1012
use SimpleSAML\XML\Exception\RuntimeException;
13+
use SimpleSAML\XML\Exception\SchemaViolationException;
1114
use SimpleSAML\XML\Exception\UnparseableXMLException;
15+
use XMLReader;
1216

17+
use function array_unique;
1318
use function file_get_contents;
1419
use function func_num_args;
20+
use function implode;
1521
use function libxml_clear_errors;
1622
use function libxml_get_last_error;
1723
use function libxml_set_external_entity_loader;
1824
use function libxml_use_internal_errors;
1925
use function sprintf;
26+
use function trim;
2027

2128
/**
2229
* @package simplesamlphp/xml-common
@@ -32,12 +39,14 @@ final class DOMDocumentFactory
3239

3340
/**
3441
* @param string $xml
42+
* @param string|null $schemaFile
3543
* @param non-negative-int $options
3644
*
3745
* @return \DOMDocument
3846
*/
3947
public static function fromString(
4048
string $xml,
49+
?string $schemaFile = null,
4150
int $options = self::DEFAULT_OPTIONS,
4251
): DOMDocument {
4352
libxml_set_external_entity_loader(null);
@@ -57,6 +66,11 @@ public static function fromString(
5766
$options |= LIBXML_NO_XXE;
5867
}
5968

69+
// Perform optional schema validation
70+
if (!empty($schemaFile)) {
71+
self::schemaValidation($xml, $schemaFile, $options);
72+
}
73+
6074
$domDocument = self::create();
6175
$loaded = $domDocument->loadXML($xml, $options);
6276

@@ -85,12 +99,16 @@ public static function fromString(
8599

86100
/**
87101
* @param string $file
102+
* @param string|null $schemaFile
88103
* @param non-negative-int $options
89104
*
90105
* @return \DOMDocument
91106
*/
92-
public static function fromFile(string $file, int $options = self::DEFAULT_OPTIONS): DOMDocument
93-
{
107+
public static function fromFile(
108+
string $file,
109+
?string $schemaFile = null,
110+
int $options = self::DEFAULT_OPTIONS,
111+
): DOMDocument {
94112
error_clear_last();
95113
$xml = @file_get_contents($file);
96114
if ($xml === false) {
@@ -101,7 +119,9 @@ public static function fromFile(string $file, int $options = self::DEFAULT_OPTIO
101119
}
102120

103121
Assert::notWhitespaceOnly($xml, sprintf('File "%s" does not have content', $file), RuntimeException::class);
104-
return (func_num_args() === 1) ? static::fromString($xml) : static::fromString($xml, $options);
122+
return (func_num_args() < 3)
123+
? static::fromString($xml, $schemaFile)
124+
: static::fromString($xml, $schemaFile, $options);
105125
}
106126

107127

@@ -114,4 +134,49 @@ public static function create(string $version = '1.0', string $encoding = 'UTF-8
114134
{
115135
return new DOMDocument($version, $encoding);
116136
}
137+
138+
139+
/**
140+
* Validate an XML-string against a given schema.
141+
*
142+
* @param string $xml
143+
* @param string $schemaFile
144+
* @param int $options
145+
*
146+
* @throws \SimpleSAML\XML\Exception\SchemaViolationException when validation fails.
147+
*/
148+
public static function schemaValidation(
149+
string $xml,
150+
string $schemaFile,
151+
int $options = self::DEFAULT_OPTIONS,
152+
): void {
153+
$xmlReader = XMLReader::XML($xml, null, $options);
154+
Assert::notFalse($xmlReader, SchemaViolationException::class);
155+
156+
libxml_use_internal_errors(true);
157+
158+
try {
159+
$xmlReader->setSchema($schemaFile);
160+
} catch (Exception) {
161+
$err = libxml_get_last_error();
162+
throw new SchemaViolationException(trim($err->message) . ' on line ' . $err->line);
163+
}
164+
165+
$msgs = [];
166+
while ($xmlReader->read()) {
167+
if (!$xmlReader->isValid()) {
168+
$err = libxml_get_last_error();
169+
if ($err instanceof LibXMLError) {
170+
$msgs[] = trim($err->message) . ' on line ' . $err->line;
171+
}
172+
}
173+
}
174+
175+
if ($msgs) {
176+
throw new SchemaViolationException(sprintf(
177+
"XML schema validation errors:\n - %s",
178+
implode("\n - ", array_unique($msgs)),
179+
));
180+
}
181+
}
117182
}

0 commit comments

Comments
 (0)