diff --git a/bin/amphtml-update.py b/bin/amphtml-update.py index f9ddb389312..87f8aaad937 100755 --- a/bin/amphtml-update.py +++ b/bin/amphtml-update.py @@ -799,6 +799,9 @@ def GetTagRules(tag_spec): if tag_spec.HasField('unique_warning'): tag_rules['unique_warning'] = tag_spec.unique_warning + if tag_spec.HasField('siblings_disallowed'): + tag_rules['siblings_disallowed'] = tag_spec.siblings_disallowed + if tag_spec.HasField('child_tags'): child_tags = collections.defaultdict( lambda: [] ) for field in tag_spec.child_tags.ListFields(): diff --git a/bin/latest-extension-versions.json b/bin/latest-extension-versions.json index 3a00f2a877b..991e2b07a28 100644 --- a/bin/latest-extension-versions.json +++ b/bin/latest-extension-versions.json @@ -115,6 +115,7 @@ "amp-sticky-ad" : "1.0", "amp-story" : "1.0", "amp-story-360" : "0.1", + "amp-story-audio-sticker" : "0.1", "amp-story-auto-ads" : "0.1", "amp-story-auto-analytics" : "0.1", "amp-story-captions" : "0.1", diff --git a/includes/sanitizers/class-amp-allowed-tags-generated.php b/includes/sanitizers/class-amp-allowed-tags-generated.php index 45b87352fb9..22c9f4c4a64 100644 --- a/includes/sanitizers/class-amp-allowed-tags-generated.php +++ b/includes/sanitizers/class-amp-allowed-tags-generated.php @@ -249,6 +249,7 @@ class AMP_Allowed_Tags_Generated { 'address', 'amp-analytics', 'amp-audio', + 'amp-bodymovin-animation', 'amp-date-countdown', 'amp-date-display', 'amp-experiment', @@ -271,6 +272,7 @@ class AMP_Allowed_Tags_Generated { 'amp-story-interactive-poll', 'amp-story-interactive-quiz', 'amp-story-interactive-results', + 'amp-story-interactive-slider', 'amp-story-panning-media', 'amp-story-shopping-tag', 'amp-timeago', @@ -607,6 +609,14 @@ class AMP_Allowed_Tags_Generated { 'attributionreportto' => array(), 'attributionsourceeventid' => array(), 'attributionsourceid' => array(), + 'attributionsrc' => array( + 'value_url' => array( + 'allow_empty' => true, + 'protocol' => array( + 'https', + ), + ), + ), 'border' => array(), 'conversiondestination' => array(), 'data-amp-bind-href' => array(), @@ -5238,6 +5248,14 @@ class AMP_Allowed_Tags_Generated { array( 'attr_spec_list' => array( 'allow-ssr-img' => array(), + 'attributionsrc' => array( + 'value_url' => array( + 'allow_empty' => true, + 'protocol' => array( + 'https', + ), + ), + ), 'media' => array(), 'noloading' => array( 'value' => array( @@ -6102,6 +6120,9 @@ class AMP_Allowed_Tags_Generated { ), ), ), + 'desktop-aspect-ratio' => array( + 'value_regex' => '\\d+:\\d+', + ), 'entity' => array(), 'entity-logo-src' => array( 'value_url' => array( @@ -6194,6 +6215,7 @@ class AMP_Allowed_Tags_Generated { 'amp-story-bookend', 'amp-story-page', 'amp-story-social-share', + 'amp-story-subscriptions', ), 'mandatory_min_num_child_tags' => 1, ), @@ -6201,6 +6223,7 @@ class AMP_Allowed_Tags_Generated { 'requires_extension' => array( 'amp-story', ), + 'siblings_disallowed' => true, ), ), ), @@ -6306,7 +6329,15 @@ class AMP_Allowed_Tags_Generated { ), 'amp-story-auto-ads' => array( array( - 'attr_spec_list' => array(), + 'attr_spec_list' => array( + 'src' => array( + 'value_url' => array( + 'protocol' => array( + 'https', + ), + ), + ), + ), 'tag_spec' => array( 'mandatory_parent' => 'amp-story', 'requires_extension' => array( @@ -6921,6 +6952,55 @@ class AMP_Allowed_Tags_Generated { ), ), ), + 'amp-story-interactive-slider' => array( + array( + 'attr_spec_list' => array( + 'chip-style' => array( + 'value' => array( + 'shadow', + 'flat', + 'transparent', + ), + ), + 'endpoint' => array( + 'mandatory' => true, + 'value_url' => array( + 'allow_empty' => false, + 'allow_relative' => false, + 'protocol' => array( + 'https', + ), + ), + ), + 'id' => array( + 'mandatory' => true, + ), + 'option-1-text' => array( + 'mandatory' => false, + ), + 'prompt-size' => array( + 'value' => array( + 'small', + 'medium', + 'large', + ), + ), + 'prompt-text' => array(), + 'theme' => array( + 'value' => array( + 'light', + 'dark', + ), + ), + ), + 'tag_spec' => array( + 'mandatory_ancestor' => 'amp-story-grid-layer', + 'requires_extension' => array( + 'amp-story-interactive', + ), + ), + ), + ), 'amp-story-page' => array( array( 'attr_spec_list' => array( @@ -7226,12 +7306,21 @@ class AMP_Allowed_Tags_Generated { 'amp-story-subscriptions' => array( array( 'attr_spec_list' => array( + 'additional-description' => array(), + 'description' => array( + 'mandatory' => true, + ), + 'headline' => array(), 'media' => array(), 'noloading' => array( 'value' => array( '', ), ), + 'price' => array( + 'mandatory' => true, + ), + 'subscriptions-page-index' => array(), ), 'tag_spec' => array( 'amp_layout' => array( @@ -8004,6 +8093,11 @@ class AMP_Allowed_Tags_Generated { 'mandatory' => true, 'value_regex' => '[0-9]+', ), + 'do-not-track' => array( + 'value' => array( + '', + ), + ), 'media' => array(), 'noloading' => array( 'value' => array( @@ -11031,6 +11125,7 @@ class AMP_Allowed_Tags_Generated { ), ), ), + 'xssi-prefix' => array(), ), 'tag_spec' => array( 'disallowed_ancestor' => array( @@ -15127,10 +15222,10 @@ class AMP_Allowed_Tags_Generated { ), 'nonce' => array(), 'src' => array( - 'dispatch_key' => 2, 'mandatory' => true, 'value' => array( 'https://cdn.ampproject.org/v0.js', + 'https://ampjs.org/v0.js', ), ), 'type' => array( @@ -15173,6 +15268,7 @@ class AMP_Allowed_Tags_Generated { 'mandatory' => true, 'value' => array( 'https://cdn.ampproject.org/lts/v0.js', + 'https://ampjs.org/lts/v0.js', ), ), 'type' => array( @@ -15721,6 +15817,7 @@ class AMP_Allowed_Tags_Generated { 'requires_extension' => array( 'amp-story', ), + 'siblings_disallowed' => true, 'spec_name' => 'amp-story-bookend extension .json script', 'unique' => true, ), @@ -15741,6 +15838,7 @@ class AMP_Allowed_Tags_Generated { 'requires_extension' => array( 'amp-story', ), + 'siblings_disallowed' => true, 'spec_name' => 'amp-story-social-share extension .json script', 'unique' => true, ), @@ -22766,6 +22864,7 @@ class AMP_Allowed_Tags_Generated { 'unique' => false, ), ), + 'siblings_disallowed' => true, 'spec_name' => 'AMP-MEGA-MENU > NAV', ), ), diff --git a/includes/sanitizers/class-amp-rule-spec.php b/includes/sanitizers/class-amp-rule-spec.php index 3df2e0d667e..644b42bc9c3 100644 --- a/includes/sanitizers/class-amp-rule-spec.php +++ b/includes/sanitizers/class-amp-rule-spec.php @@ -37,6 +37,7 @@ abstract class AMP_Rule_Spec { const MANDATORY_PARENT = 'mandatory_parent'; const DESCENDANT_TAG_LIST = 'descendant_tag_list'; const CHILD_TAGS = 'child_tags'; + const SIBLINGS_DISALLOWED = 'siblings_disallowed'; /* * HTML Element Attribute rule names diff --git a/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php b/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php index 47069101e68..aac823f191a 100644 --- a/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php +++ b/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php @@ -45,6 +45,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer { const DISALLOWED_CHILD_TAG = 'DISALLOWED_CHILD_TAG'; const DISALLOWED_DESCENDANT_TAG = 'DISALLOWED_DESCENDANT_TAG'; const DISALLOWED_FIRST_CHILD_TAG = 'DISALLOWED_FIRST_CHILD_TAG'; + const DISALLOWED_SIBLING_TAG = 'DISALLOWED_SIBLING_TAG'; const DISALLOWED_PROCESSING_INSTRUCTION = 'DISALLOWED_PROCESSING_INSTRUCTION'; const DISALLOWED_PROPERTY_IN_ATTR_VALUE = 'DISALLOWED_PROPERTY_IN_ATTR_VALUE'; const DISALLOWED_RELATIVE_URL = 'DISALLOWED_RELATIVE_URL'; @@ -749,6 +750,11 @@ static function ( $validation_error ) { } } + // If the node disallows any siblings, remove them. + if ( ! empty( $tag_spec[ AMP_Rule_Spec::SIBLINGS_DISALLOWED ] ) ) { + $this->remove_disallowed_siblings( $node, $this->get_spec_name( $node, $tag_spec ) ); + } + // After attributes have been sanitized (and potentially removed), if mandatory attribute(s) are missing, remove the element. $missing_mandatory_attributes = $this->get_missing_mandatory_attributes( $attr_spec_list, $node ); if ( ! empty( $missing_mandatory_attributes ) ) { @@ -2453,6 +2459,46 @@ private function remove_disallowed_descendants( DOMElement $node, $allowed_desce } } + /** + * Loop through node's siblings and remove them. + * + * @param DOMElement $node Node. + * @param string $spec_name Spec name. + */ + private function remove_disallowed_siblings( DOMElement $node, $spec_name ) { + $prev_sibling = $node->previousSibling; + while ( null !== $prev_sibling ) { + $prev_prev_sibling = $prev_sibling->previousSibling; + if ( $prev_sibling instanceof Element ) { + $this->remove_invalid_child( + $prev_sibling, + [ + 'code' => self::DISALLOWED_SIBLING_TAG, + 'sibling' => $node->nodeName, + 'spec_name' => $spec_name, + ] + ); + } + $prev_sibling = $prev_prev_sibling; + } + + $next_sibling = $node->nextSibling; + while ( null !== $next_sibling ) { + $next_next_sibling = $next_sibling->nextSibling; + if ( $next_sibling instanceof Element ) { + $this->remove_invalid_child( + $next_sibling, + [ + 'code' => self::DISALLOWED_SIBLING_TAG, + 'sibling' => $node->nodeName, + 'spec_name' => $spec_name, + ] + ); + } + $next_sibling = $next_next_sibling; + } + } + /** * Check whether the node validates the constraints for children. * diff --git a/tests/php/test-tag-and-attribute-sanitizer.php b/tests/php/test-tag-and-attribute-sanitizer.php index 29d83ad9308..52adaf8359f 100644 --- a/tests/php/test-tag-and-attribute-sanitizer.php +++ b/tests/php/test-tag-and-attribute-sanitizer.php @@ -1397,6 +1397,48 @@ static function() { [ 'amp-sidebar', 'amp-story' ], ], + 'amp_story_with_disallowed_sibling' => [ + str_replace( + [ "\n", "\t" ], + '', + ' + + + + + +

Hello World

+

This is the cover page of this story.

+
+
+
+ +
+ ' + ), + str_replace( + [ "\n", "\t" ], + '', + ' + + + +

Hello World

+

This is the cover page of this story.

+
+
+
+ ' + ), + [ 'amp-story' ], + [ + AMP_Tag_And_Attribute_Sanitizer::DISALLOWED_SIBLING_TAG, + AMP_Tag_And_Attribute_Sanitizer::DISALLOWED_SIBLING_TAG, + AMP_Tag_And_Attribute_Sanitizer::DISALLOWED_SIBLING_TAG, + AMP_Tag_And_Attribute_Sanitizer::DISALLOWED_SIBLING_TAG, + ], + ], + 'amp_sidebar_with_autoscroll' => [ str_replace( [ "\n", "\t" ],