OwlCyberSecurity - MANAGER
Edit File: ValidatorRules.php
<?php namespace AmpProject\Validator; use AmpProject\Format; use AmpProject\Html\Attribute; use AmpProject\Html\LowerCaseTag; use AmpProject\Html\Parser\ParsedAttribute; use AmpProject\Html\Parser\ParsedTag; use AmpProject\Validator\Spec\Error\DevModeOnly; use AmpProject\Validator\Spec\Error\DisallowedAttr; use AmpProject\Validator\Spec\Error\InvalidAttrValue; use AmpProject\Validator\Spec\Error\MandatoryAttrMissing; final class ValidatorRules { /** * Regular expression to validate the value of a transformed attribute. * * @var string */ const TRANSFORMED_VALUE_REGEX = '/^\\w+;v=(\\d+)$/'; /** * Set of valid type identifiers. * * @var array<string> */ const TYPE_IDENTIFIERS = [ Attribute::AMP_EMOJI, Attribute::AMP_EMOJI_ALT, Attribute::AMP, Attribute::AMP4ADS_EMOJI, Attribute::AMP4ADS_EMOJI_ALT, Attribute::AMP4ADS, Attribute::AMP4EMAIL_EMOJI, Attribute::AMP4EMAIL_EMOJI_ALT, Attribute::AMP4EMAIL, Attribute::TRANSFORMED, Attribute::DATA_AMPDEVMODE, Attribute::DATA_CSS_STRICT ]; /** * Set of type identifiers that are not mandatory. * * @var array<string> */ const OPTIONAL_TYPE_IDENTIFIERS = [ Attribute::TRANSFORMED, Attribute::DATA_AMPDEVMODE, Attribute::DATA_CSS_STRICT ]; /** * AMP HTML format to validate against. * * @var string */ private $htmlFormat; /** * Validator Specification to use. * * @var Spec */ private $spec; /** * Creates a new instance and initializes it with the proper specification rules. * * @param string $htmlFormat Optional. AMP HTML format to validate against. * @param Spec|null $spec Optional. Validator specification to use. */ public function __construct($htmlFormat = Format::AMP, ?Spec $spec = null) { $this->htmlFormat = $htmlFormat; $this->spec = $spec instanceof Spec ? $spec : new Spec(); } /** * Emits any validation errors which require a global view (mandatory tags, tags required by other tags, mandatory * alternatives). * * @param Context $context Validation context. * @param ValidationResult $validationResult Validation result. */ public function maybeEmitGlobalTagValidationErrors(Context $context, ValidationResult $validationResult) { $this->maybeEmitMandatoryTagValidationErrors($context, $validationResult); $this->maybeEmitAlsoRequiresTagValidationErrors($context, $validationResult); $this->maybeEmitMandatoryAlternativesSatisfiedErrors($context, $validationResult); $this->maybeEmitDocSizeErrors($context, $validationResult); $this->maybeEmitCssLengthSpecErrors($context, $validationResult); $this->maybeEmitValueSetMismatchErrors($context, $validationResult); } /** * Emits errors for tags that are specified to be mandatory. * * @param Context $context Validation context. * @param ValidationResult $validationResult Validation result. */ private function maybeEmitMandatoryTagValidationErrors($context, $validationResult) { /* For (const tagSpecId of $this->mandatoryTagSpecs_) { const parsedTagSpec = $this->getByTagSpecId(tagSpecId); // Skip TagSpecs that aren't used for these type identifiers. if (!parsedTagSpec.isUsedForTypeIdentifiers( context.getTypeIdentifiers())) { continue; } if (!context.getTagspecsValidated().hasOwnProperty(tagSpecId)) { const spec = parsedTagSpec.getSpec(); context.addError( generated.ValidationError.Code.MANDATORY_TAG_MISSING, context.getFilePosition(), [getTagDescriptiveName(spec)], getTagSpecUrl(spec), validationResult); } } */ } /** * Emits errors for tags that specify that another tag is also required or a condition is required to be satisfied. * * Returns false iff context.Progress(result).complete. * * @param Context $context Validation context. * @param ValidationResult $validationResult Validation result. */ private function maybeEmitAlsoRequiresTagValidationErrors($context, $validationResult) { /* /** @type {!Array<number>} * / const tagspecsValidated = Object.keys($context->g$etTagspecsValidated()).map(Number); googArray.sort(tagspecsValidated); for (const tagSpecId of tagspecsValidated) { const parsedTagSpec = $this->getByTagSpecId(tagSpecId); // Skip TagSpecs that aren't used for these type identifiers. if (!parsedTagSpec.isUsedForTypeIdentifiers( context.getTypeIdentifiers())) { continue; } for (const condition of parsedTagSpec.requires()) { if (!context.satisfiesCondition(condition)) { context.addError( generated.ValidationError.Code.TAG_REQUIRED_BY_MISSING, context.getFilePosition(), [ context.getRules().getInternedString(condition), getTagDescriptiveName(parsedTagSpec.getSpec()), ], getTagSpecUrl(parsedTagSpec), validationResult); } } for (const condition of parsedTagSpec.excludes()) { if ($context->satisfiesCondition(condition)) { context.addError( generated.ValidationError.Code.TAG_EXCLUDED_BY_TAG, context.getFilePosition(), [ getTagDescriptiveName(parsedTagSpec.getSpec()), context.getRules().getInternedString(condition), ], getTagSpecUrl(parsedTagSpec), validationResult); } } for (const tagspecId of parsedTagSpec.getAlsoRequiresTagWarning()) { if (!context.getTagspecsValidated().hasOwnProperty(tagspecId)) { const alsoRequiresTagspec = $this->getByTagSpecId(tagspecId); context.addWarning( generated.ValidationError.Code.WARNING_TAG_REQUIRED_BY_MISSING, context.getFilePosition(), [ getTagDescriptiveName(alsoRequiresTagspec.getSpec()), getTagDescriptiveName(parsedTagSpec.getSpec()), ], getTagSpecUrl(parsedTagSpec), validationResult); } } } const extensionsCtx = context.getExtensionsContext(); const unusedRequired = extensionsCtx.unusedExtensionsRequired(); for (const unusedExtensionName of unusedRequired) { context.addError( generated.ValidationError.Code.EXTENSION_UNUSED, context.getFilePosition(), [unusedExtensionName], '', validationResult); } */ } /** * Emits errors for tags that are specified as mandatory alternatives. * * Returns false iff context.Progress(result).complete. * * @param Context $context Validation context. * @param ValidationResult $validationResult Validation result. */ private function maybeEmitMandatoryAlternativesSatisfiedErrors($context, $validationResult) { /* Const satisfied = context.getMandatoryAlternativesSatisfied(); /** @type {!Array<string>} * / const missing = []; const specUrlsByMissing = Object.create(null); for (const tagSpec of $this->rules_.tags) { if (tagSpec.mandatoryAlternatives === null || !$this->isTagSpecCorrectHtmlFormat_(tagSpec)) { continue; } const alternative = tagSpec.mandatoryAlternatives; if (satisfied.indexOf(alternative) === -1) { const alternativeName = context.getRules().getInternedString(alternative); missing.push(alternativeName); specUrlsByMissing[alternativeName] = getTagSpecUrl(tagSpec); } } sortAndUniquify(missing); for (const tagMissing of missing) { context.addError( generated.ValidationError.Code.MANDATORY_TAG_MISSING, context.getFilePosition(), [tagMissing], specUrlsByMissing[tagMissing], validationResult ); } */ } /** * Emits errors for doc size limitations across entire document. * * @param Context $context Validation context. * @param ValidationResult $validationResult Validation result. */ private function maybeEmitDocSizeErrors($context, $validationResult) { /* Const parsedDocSpec = context.matchingDocSpec(); if (parsedDocSpec !== null) { const bytesUsed = context.getDocByteSize(); /** @type {!generated.DocSpec} * / const docSpec = parsedDocSpec.spec(); if (docSpec.maxBytes !== -2 && bytesUsed > docSpec.maxBytes) { context.addError( generated.ValidationError.Code.DOCUMENT_SIZE_LIMIT_EXCEEDED, context.getFilePosition(), [docSpec.maxBytes.toString(), bytesUsed.toString()], docSpec.maxBytesSpecUrl, validationResult); } } */ } /** * Emits errors for css size limitations across entire document. * * @param Context $context Validation context. * @param ValidationResult $validationResult Validation result. */ private function maybeEmitCssLengthSpecErrors($context, $validationResult) { /* Const bytesUsed = context.getInlineStyleByteSize() + context.getStyleTagByteSize(); const parsedCssSpec = context.matchingDocCssSpec(); if (parsedCssSpec !== null) { /** @type {!generated.DocCssSpec} * / const cssSpec = parsedCssSpec.spec(); if (cssSpec.maxBytes !== -2 && bytesUsed > cssSpec.maxBytes) { if (cssSpec.maxBytesIsWarning) { context.addWarning( generated.ValidationError.Code .STYLESHEET_AND_INLINE_STYLE_TOO_LONG, context.getFilePosition(), [bytesUsed.toString(), cssSpec.maxBytes.toString()], cssSpec.maxBytesSpecUrl, validationResult); } else { context.addError( generated.ValidationError.Code .STYLESHEET_AND_INLINE_STYLE_TOO_LONG, context.getFilePosition(), [bytesUsed.toString(), cssSpec.maxBytes.toString()], cssSpec.maxBytesSpecUrl, validationResult); } } } */ } /** * Emits errors when there is a ValueSetRequirement with no matching ValueSetProvision in the document. * * @param Context $context Validation context. * @param ValidationResult $validationResult Validation result. */ private function maybeEmitValueSetMismatchErrors($context, $validationResult) { /* Const providedKeys = context.valueSetsProvided(); for (const [requiredKey, errors] of context.valueSetsRequired()) { if (!providedKeys.has(/** @type {string} * / (requiredKey))) { for (const error of errors) context.addBuiltError(error, validationResult); } */ } /** * Validates the <html> tag for type identifiers. * * @param ParsedTag $htmlTag <html> tag to validate. * @param Context $context Validation context. * @param ValidationResult $validationResult Validation result. */ public function validateHtmlTag(ParsedTag $htmlTag, Context $context, ValidationResult $validationResult) { switch ($this->htmlFormat) { case Format::AMP: $this->validateTypeIdentifiers( $htmlTag->attributes(), [ Attribute::AMP_EMOJI, Attribute::AMP_EMOJI_ALT, Attribute::AMP, Attribute::TRANSFORMED, Attribute::DATA_AMPDEVMODE ], $context, $validationResult ); break; case Format::AMP4ADS: $this->validateTypeIdentifiers( $htmlTag->attributes(), [ Attribute::AMP4ADS_EMOJI, Attribute::AMP4ADS_EMOJI_ALT, Attribute::AMP4ADS, Attribute::DATA_AMPDEVMODE ], $context, $validationResult ); break; case Format::AMP4EMAIL: $this->validateTypeIdentifiers( $htmlTag->attributes(), [ Attribute::AMP4EMAIL_EMOJI, Attribute::AMP4EMAIL_EMOJI_ALT, Attribute::AMP4EMAIL, Attribute::DATA_AMPDEVMODE, Attribute::DATA_CSS_STRICT ], $context, $validationResult ); break; } } /** * @param ParsedAttribute[] $attributes Array of parsed attributes. * @param string[] $formatIdentifiers Array of format identifiers to validate against. * @param Context $context Validation context. * @param ValidationResult $validationResult Validation result. */ private function validateTypeIdentifiers( $attributes, $formatIdentifiers, Context $context, ValidationResult $validationResult ) { $hasMandatoryTypeIdentifier = false; foreach ($attributes as $attribute) { // Verify this attribute is a type identifier. Other attributes are validated in validateAttributes(). if ($this->isTypeIdentifier($attribute->name())) { // Verify this type identifier is allowed for this format. if (in_array($attribute->name(), $formatIdentifiers, true)) { // Only add the type identifier once per representation. That is, both "⚡" and "amp", which // represent the same type identifier. $typeIdentifier = str_replace( [Attribute::AMP_EMOJI_ALT, Attribute::AMP_EMOJI], Attribute::AMP, $attribute->name() ); if (! in_array($typeIdentifier, $validationResult->getTypeIdentifiers(), true)) { $validationResult->addTypeIdentifier($typeIdentifier); $context->recordTypeIdentifier($typeIdentifier); } // Register the presence of a mandatory identifier (i.e. anything that is not optional). if (! in_array($typeIdentifier, self::OPTIONAL_TYPE_IDENTIFIERS, true)) { $hasMandatoryTypeIdentifier = true; } // The type identifier "transformed" has restrictions on its value. // It must be \w+;v=\d+ (e.g. google;v=1). if (($typeIdentifier === Attribute::TRANSFORMED) && ($attribute->value() !== '')) { $matches = []; if (preg_match(self::TRANSFORMED_VALUE_REGEX, $attribute->value(), $matches)) { $validationResult->setTransformerVersion((int)$matches[1]); } else { $context->addError( InvalidAttrValue::CODE, $context->getFilePosition(), [$attribute->name(), LowerCaseTag::HTML, $attribute->value()], 'https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml#required-markup', $validationResult ); } } if ($typeIdentifier === Attribute::DATA_AMPDEVMODE) { // We always emit an error for this type identifier, but it suppresses other errors later in the // document. See https://github.com/ampproject/amphtml/issues/20974. $context->addError( DevModeOnly::CODE, $context->getFilePosition(), [], '', $validationResult ); } } else { $context->addError( DisallowedAttr::CODE, $context->getFilePosition(), [$attribute->name(), LowerCaseTag::HTML], 'https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml#required-markup', $validationResult ); } } } if (! $hasMandatoryTypeIdentifier) { // Missing mandatory type identifier (any AMP variant but "transformed"). $context->addError( MandatoryAttrMissing::CODE, $context->getFilePosition(), [$formatIdentifiers[0], LowerCaseTag::HTML], 'https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml#required-markup', $validationResult ); } } /** * Check whether a given attribute is a valid type identifier. * * @param string $attribute Attribute to check. * @return bool Whether the provided attribute is a valid type identifier. */ private function isTypeIdentifier($attribute) { return in_array($attribute, self::TYPE_IDENTIFIERS, true); } }