[CSS Shapes] CORS-enabled fetch for shape image values
authorhmuller@adobe.com <hmuller@adobe.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Oct 2013 20:36:21 +0000 (20:36 +0000)
committerhmuller@adobe.com <hmuller@adobe.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Oct 2013 20:36:21 +0000 (20:36 +0000)
https://bugs.webkit.org/show_bug.cgi?id=123114

Reviewed by Andreas Kling.

Source/WebCore:

Access to shape images is now controlled by CORS CSS shape per
http://dev.w3.org/csswg/css-shapes/#shape-outside-property.
Previously shape images had to be same-origin.

Shape image URL access is defined by the same logic that defines
canvas tainting: same-origin and data URLs are allowed and images
with a "Access-Control-Allow-Origin:" header that's either "*" or
that matches the document's origin.

A PotentiallyCrossOriginEnabled RequestOriginPolicy was added to
ResourceLoaderOptions, to indicate that a "potentially CORS-enabled fetch"
was to be undertaken. The CSSImageValue::cachedImage() method handles this
option by effectively setting the "Origin:" request header (see
updateRequestForAccessControl() in CrossOriginAccessControl.cpp).
StyleResolver::loadPendingShapeImage() uses the new ResourceLoaderOption.

The static ShapeInsideInfo and ShapeOutsideInfo isEnabledFor() method
now performs the CORS check for image valued shapes. The private
isOriginClean() method from CanvasRenderingContext2D has been moved to
the CachedImage class so that it can be shared by the Canvas and Shape
implementations. It checks the response headers per the CORS spec.

Test: http/tests/security/shape-image-cors.html

* css/CSSImageValue.cpp:
(WebCore::CSSImageValue::cachedImage): Handle the new ResourceLoaderOption.
* css/StyleResolver.cpp:
(WebCore::StyleResolver::loadPendingShapeImage): Set the new ResourceLoaderOption.
* html/canvas/CanvasRenderingContext2D.cpp:
(WebCore::CanvasRenderingContext2D::createPattern): Use the CachedImage::isOriginClean().
* loader/ResourceLoaderOptions.h: Added PotentiallyCrossOriginEnabled to RequestOriginPolicy.
* loader/cache/CachedImage.cpp:
(WebCore::CachedImage::isOriginClean): Migrated from CanvasRenderingContext2D.
* loader/cache/CachedImage.h:
* rendering/shapes/ShapeInfo.cpp:
(WebCore::::checkImageOrigin): Do the CORS check and log an error message if neccessary.
* rendering/shapes/ShapeInfo.h:
* rendering/shapes/ShapeInsideInfo.cpp:
(WebCore::ShapeInsideInfo::isEnabledFor): Call checkImageOrigin() for images.
* rendering/shapes/ShapeOutsideInfo.cpp:
(WebCore::ShapeOutsideInfo::isEnabledFor): Ditto.

LayoutTests:

Verify that images not allowed by CORS don't affect the layout
and that same-origin images, data URLs, and images with a
"Access-Control-Allow-Origin: *" header do define the layout.
Added a simple PHP script that optionally adds the allow origin header.

Replaced and renamed the original shape same-origin-only test.

* http/tests/security/resources/image-access-control.php: Added.
* http/tests/security/shape-image-cors-expected.html: Added.
* http/tests/security/shape-image-cors.html: Added.
* http/tests/security/shape-inside-image-origin-expected.txt: Removed.
* http/tests/security/shape-inside-image-origin.html: Removed.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@158044 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/security/resources/image-access-control.php [new file with mode: 0644]
LayoutTests/http/tests/security/shape-image-cors-expected.html [new file with mode: 0644]
LayoutTests/http/tests/security/shape-image-cors.html [new file with mode: 0644]
LayoutTests/http/tests/security/shape-inside-image-origin-expected.txt [deleted file]
LayoutTests/http/tests/security/shape-inside-image-origin.html [deleted file]
Source/WebCore/ChangeLog
Source/WebCore/css/CSSImageValue.cpp
Source/WebCore/css/StyleResolver.cpp
Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp
Source/WebCore/loader/ResourceLoaderOptions.h
Source/WebCore/loader/cache/CachedImage.cpp
Source/WebCore/loader/cache/CachedImage.h
Source/WebCore/rendering/shapes/ShapeInfo.cpp
Source/WebCore/rendering/shapes/ShapeInfo.h
Source/WebCore/rendering/shapes/ShapeInsideInfo.cpp
Source/WebCore/rendering/shapes/ShapeOutsideInfo.cpp

index 99688ee..a1ba629 100644 (file)
@@ -1,3 +1,23 @@
+2013-10-25  Hans Muller  <hmuller@adobe.com>
+
+        [CSS Shapes] CORS-enabled fetch for shape image values
+        https://bugs.webkit.org/show_bug.cgi?id=123114
+
+        Reviewed by Andreas Kling.
+
+        Verify that images not allowed by CORS don't affect the layout
+        and that same-origin images, data URLs, and images with a
+        "Access-Control-Allow-Origin: *" header do define the layout.
+        Added a simple PHP script that optionally adds the allow origin header.
+
+        Replaced and renamed the original shape same-origin-only test.
+
+        * http/tests/security/resources/image-access-control.php: Added.
+        * http/tests/security/shape-image-cors-expected.html: Added.
+        * http/tests/security/shape-image-cors.html: Added.
+        * http/tests/security/shape-inside-image-origin-expected.txt: Removed.
+        * http/tests/security/shape-inside-image-origin.html: Removed.
+
 2013-10-25  Simon Fraser  <simon.fraser@apple.com>
 
         Mavericks results with MountainLion variants.
diff --git a/LayoutTests/http/tests/security/resources/image-access-control.php b/LayoutTests/http/tests/security/resources/image-access-control.php
new file mode 100644 (file)
index 0000000..27919da
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+$allowOrigin = $_GET['allow'];
+if ($allowOrigin == "true") {
+    header("Access-Control-Allow-Origin: *");
+}
+
+$file = $_GET['file'];
+$fp = fopen($file, 'rb');
+header("Content-Type: image/png");
+header("Content-Length: " . filesize($file));
+
+fpassthru($fp);
+exit;
+?>
diff --git a/LayoutTests/http/tests/security/shape-image-cors-expected.html b/LayoutTests/http/tests/security/shape-image-cors-expected.html
new file mode 100644 (file)
index 0000000..d19c3f0
--- /dev/null
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+    #clear-left {
+        clear: left;
+        font: 20px/1 Ahem, sans-serif;
+        color: green;
+    }
+
+    #clear-left > div {
+        float: left;
+        height: 20px;
+    }
+
+    #shape-outside-block-origin {
+        width: 200px;
+    }
+
+    #shape-outside-same-origin {
+        width: 100px;
+    }
+
+    #shape-outside-data-url {
+        width: 100px;
+    }
+
+    #shape-outside-allow-origin-url {
+        width: 100px;
+    }
+
+    #shape-outside-disallow-origin-url {
+        width: 200px;
+    } 
+</style>
+</head>
+<body>
+    <p>Verify that images not allowed by CORS don't affect the layout and that same-origin images, data URLs, and images with a "Access-Control-Allow-Origin: *" header do define the layout.</p>
+    <div id="clear-left"><div id="shape-outside-block-origin"></div>X</div>
+    <div id="clear-left"><div id="shape-outside-same-origin"></div>X</div>
+    <div id="clear-left"><div id="shape-outside-data-url"></div>X</div>
+    <div id="clear-left"><div id="shape-outside-allow-origin-url"></div>X</div>
+    <div id="clear-left"><div id="shape-outside-disallow-origin-url"></div>X</div>
+</body>
+</html>
+
+
diff --git a/LayoutTests/http/tests/security/shape-image-cors.html b/LayoutTests/http/tests/security/shape-image-cors.html
new file mode 100644 (file)
index 0000000..c6504be
--- /dev/null
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+    #clear-left {
+        clear: left;
+        font: 20px/1 Ahem, sans-serif;
+        color: green;
+    }
+
+    #clear-left > div {
+        float: left;
+        width: 200px;
+        height: 20px;
+    }
+
+    /* blocked because the URL's port number doesn't match this document's origin. */
+    #shape-outside-block-origin {
+        -webkit-shape-outside: url("http://localhost:8080/resources/square100.png");
+    } 
+
+    /* Access is OK because the URL and this document have the same origin. */
+    #shape-outside-same-origin {
+        -webkit-shape-outside: url("/resources/square100.png");
+    } 
+
+    /* DataURL Access is OK, as it is with Canvas. */
+    #shape-outside-data-url {
+        -webkit-shape-outside: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100px' height='20px'><rect width='200' height='20' fill='blue'/></svg>");
+    } 
+
+    /* Cross-origin request is OK because the "Access-Control-Allow-Origin: *" is returned. */
+    #shape-outside-allow-origin-url {
+        -webkit-shape-outside: url("http://localhost:8080/security/resources/image-access-control.php?file=../../resources/square100.png&allow=true");
+    } 
+
+    /* Cross-origin request is not OK because a "Access-Control-Allow-Origin:" header is not returned. */
+    #shape-outside-disallow-origin-url {
+        -webkit-shape-outside: url("http://localhost:8080/security/resources/image-access-control.php?file=../../resources/square100.png&allow=false");
+    } 
+</style>
+</head>
+<body>
+    <p>Verify that images not allowed by CORS don't affect the layout and that same-origin images, data URLs, and images with a "Access-Control-Allow-Origin: *" header do define the layout.</p>
+    <div id="clear-left"><div id="shape-outside-block-origin"></div>X</div>
+    <div id="clear-left"><div id="shape-outside-same-origin"></div>X</div>
+    <div id="clear-left"><div id="shape-outside-data-url"></div>X</div>
+    <div id="clear-left"><div id="shape-outside-allow-origin-url"></div>X</div>
+    <div id="clear-left"><div id="shape-outside-disallow-origin-url"></div>X</div>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/shape-inside-image-origin-expected.txt b/LayoutTests/http/tests/security/shape-inside-image-origin-expected.txt
deleted file mode 100644 (file)
index 88c2782..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-CONSOLE MESSAGE: Unsafe attempt to load URL http://localhost:8080/security/resources/square100.png from frame with URL http://127.0.0.1:8000/security/shape-inside-image-origin.html. Domains, protocols and ports must match.
-
-CONSOLE MESSAGE: Unsafe attempt to load URL http://localhost:8080/security/resources/square100.png from frame with URL http://127.0.0.1:8000/security/shape-inside-image-origin.html. Domains, protocols and ports must match.
-
-Verify that shape-inside and shape-outside can not be set to a URL with a different origin and that the result of doing so sets the property to "none". This test should generate two error messages about unsafe attempts to load a URL.
-
-PASS window.getComputedStyle(shapeElement).webkitShapeInside is "rectangle(0px, 0px, 0px, 0px, 0px, 0px)"
-PASS window.getComputedStyle(shapeElement).webkitShapeOutside is "rectangle(0px, 0px, 0px, 0px, 0px, 0px)"
-PASS window.getComputedStyle(shapeElement).webkitShapeInside is "none"
-PASS window.getComputedStyle(shapeElement).webkitShapeOutside is "none"
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
-
diff --git a/LayoutTests/http/tests/security/shape-inside-image-origin.html b/LayoutTests/http/tests/security/shape-inside-image-origin.html
deleted file mode 100644 (file)
index 17b86c5..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<script src="../resources/js-test-pre.js"></script>
-<style>
-    #shape {
-        -webkit-shape-inside: rectangle(0, 0, 0, 0);
-        -webkit-shape-outside: rectangle(0, 0, 0, 0);
-    }
-</style>
-</head>
-<body>
-  <p>Verify that shape-inside and shape-outside can not be set to a URL with a different origin
-  and that the result of doing so sets the property to "none". This test should generate two error
-  messages about unsafe attempts to load a URL.</p>
-  <p id="console"></p>
-
-  <div id="shape"></div>
-
-<script>
-  var shapeElement = document.getElementById("shape");
-  shouldBeEqualToString('window.getComputedStyle(shapeElement).webkitShapeInside', "rectangle(0px, 0px, 0px, 0px, 0px, 0px)");
-  shouldBeEqualToString('window.getComputedStyle(shapeElement).webkitShapeOutside', "rectangle(0px, 0px, 0px, 0px, 0px, 0px)");
-
-  // The image URL's port does not match this document's origin. We expect a pair of console warnings.
-  shapeElement.style.webkitShapeInside = 'url("http://localhost:8080/security/resources/square100.png")';
-  shapeElement.style.webkitShapeOutside = 'url("http://localhost:8080/security/resources/square100.png")';
-
-  shouldBeEqualToString('window.getComputedStyle(shapeElement).webkitShapeInside', "none");
-  shouldBeEqualToString('window.getComputedStyle(shapeElement).webkitShapeOutside', "none");
-</script>
-<script src="../../resources/js-test-post.js"></script>
-</body>
-</html>
index 851ff14..dc4e773 100644 (file)
@@ -1,3 +1,52 @@
+2013-10-25  Hans Muller  <hmuller@adobe.com>
+
+        [CSS Shapes] CORS-enabled fetch for shape image values
+        https://bugs.webkit.org/show_bug.cgi?id=123114
+
+        Reviewed by Andreas Kling.
+
+        Access to shape images is now controlled by CORS CSS shape per
+        http://dev.w3.org/csswg/css-shapes/#shape-outside-property.
+        Previously shape images had to be same-origin.
+
+        Shape image URL access is defined by the same logic that defines
+        canvas tainting: same-origin and data URLs are allowed and images
+        with a "Access-Control-Allow-Origin:" header that's either "*" or
+        that matches the document's origin.
+
+        A PotentiallyCrossOriginEnabled RequestOriginPolicy was added to
+        ResourceLoaderOptions, to indicate that a "potentially CORS-enabled fetch"
+        was to be undertaken. The CSSImageValue::cachedImage() method handles this
+        option by effectively setting the "Origin:" request header (see
+        updateRequestForAccessControl() in CrossOriginAccessControl.cpp).
+        StyleResolver::loadPendingShapeImage() uses the new ResourceLoaderOption.
+
+        The static ShapeInsideInfo and ShapeOutsideInfo isEnabledFor() method
+        now performs the CORS check for image valued shapes. The private
+        isOriginClean() method from CanvasRenderingContext2D has been moved to
+        the CachedImage class so that it can be shared by the Canvas and Shape
+        implementations. It checks the response headers per the CORS spec.
+
+        Test: http/tests/security/shape-image-cors.html
+
+        * css/CSSImageValue.cpp:
+        (WebCore::CSSImageValue::cachedImage): Handle the new ResourceLoaderOption.
+        * css/StyleResolver.cpp:
+        (WebCore::StyleResolver::loadPendingShapeImage): Set the new ResourceLoaderOption.
+        * html/canvas/CanvasRenderingContext2D.cpp:
+        (WebCore::CanvasRenderingContext2D::createPattern): Use the CachedImage::isOriginClean().
+        * loader/ResourceLoaderOptions.h: Added PotentiallyCrossOriginEnabled to RequestOriginPolicy.
+        * loader/cache/CachedImage.cpp:
+        (WebCore::CachedImage::isOriginClean): Migrated from CanvasRenderingContext2D.
+        * loader/cache/CachedImage.h:
+        * rendering/shapes/ShapeInfo.cpp:
+        (WebCore::::checkImageOrigin): Do the CORS check and log an error message if neccessary.
+        * rendering/shapes/ShapeInfo.h:
+        * rendering/shapes/ShapeInsideInfo.cpp:
+        (WebCore::ShapeInsideInfo::isEnabledFor): Call checkImageOrigin() for images.
+        * rendering/shapes/ShapeOutsideInfo.cpp:
+        (WebCore::ShapeOutsideInfo::isEnabledFor): Ditto.
+
 2013-10-25  Jer Noble  <jer.noble@apple.com>
 
         [MSE] Fix runtime errors caused by mediasource IDL attributes.
index b9deb6c..7eb6c64 100644 (file)
@@ -28,6 +28,7 @@
 #include "CachedResourceLoader.h"
 #include "CachedResourceRequest.h"
 #include "CachedResourceRequestInitiators.h"
+#include "CrossOriginAccessControl.h"
 #include "Document.h"
 #include "Element.h"
 #include "MemoryCache.h"
@@ -75,6 +76,10 @@ StyleCachedImage* CSSImageValue::cachedImage(CachedResourceLoader* loader, const
             request.setInitiator(cachedResourceRequestInitiators().css);
         else
             request.setInitiator(m_initiatorName);
+
+        if (options.requestOriginPolicy == PotentiallyCrossOriginEnabled)
+            updateRequestForAccessControl(request.mutableResourceRequest(), loader->document()->securityOrigin(), options.allowCredentials);
+
         if (CachedResourceHandle<CachedImage> cachedImage = loader->requestImage(request))
             m_image = StyleCachedImage::create(cachedImage.get());
     }
index e7af63f..d7ab7e4 100644 (file)
@@ -3967,7 +3967,8 @@ void StyleResolver::loadPendingShapeImage(ShapeValue* shapeValue)
     CachedResourceLoader* cachedResourceLoader = m_state.document().cachedResourceLoader();
 
     ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions();
-    options.requestOriginPolicy = RestrictToSameOrigin;
+    options.requestOriginPolicy = PotentiallyCrossOriginEnabled;
+    options.allowCredentials = DoNotAllowStoredCredentials;
 
     shapeValue->setImage(cssImageValue->cachedImage(cachedResourceLoader, options));
 }
index e84aae2..07085b0 100755 (executable)
@@ -82,15 +82,6 @@ static const int defaultFontSize = 10;
 static const char* const defaultFontFamily = "sans-serif";
 static const char* const defaultFont = "10px sans-serif";
 
-static bool isOriginClean(CachedImage* cachedImage, SecurityOrigin* securityOrigin)
-{
-    if (!cachedImage->image()->hasSingleSecurityOrigin())
-        return false;
-    if (cachedImage->passesAccessControlCheck(securityOrigin))
-        return true;
-    return !securityOrigin->taintsCanvas(cachedImage->response().url());
-}
-
 class CanvasStrokeStyleApplier : public StrokeStyleApplier {
 public:
     CanvasStrokeStyleApplier(CanvasRenderingContext2D* canvasContext)
@@ -1700,7 +1691,7 @@ PassRefPtr<CanvasPattern> CanvasRenderingContext2D::createPattern(HTMLImageEleme
     if (!cachedImage || !image->cachedImage()->imageForRenderer(image->renderer()))
         return CanvasPattern::create(Image::nullImage(), repeatX, repeatY, true);
 
-    bool originClean = isOriginClean(cachedImage, canvas()->securityOrigin());
+    bool originClean = cachedImage->isOriginClean(canvas()->securityOrigin());
     return CanvasPattern::create(cachedImage->imageForRenderer(image->renderer()), repeatX, repeatY, originClean);
 }
 
index 0a2f873..8e1b74d 100644 (file)
@@ -57,7 +57,8 @@ enum SecurityCheckPolicy {
 
 enum RequestOriginPolicy {
     UseDefaultOriginRestrictionsForType,
-    RestrictToSameOrigin
+    RestrictToSameOrigin,
+    PotentiallyCrossOriginEnabled // Indicates "potentially CORS-enabled fetch" in HTML standard.
 };
 
 struct ResourceLoaderOptions {
index 1b13cf3..99d1e89 100644 (file)
@@ -38,6 +38,7 @@
 #include "Page.h"
 #include "RenderElement.h"
 #include "ResourceBuffer.h"
+#include "SecurityOrigin.h"
 #include "Settings.h"
 #include "SubresourceLoader.h"
 #include <wtf/CurrentTime.h>
@@ -584,4 +585,13 @@ void CachedImage::useDiskImageCache()
 }
 #endif
 
+bool CachedImage::isOriginClean(SecurityOrigin* securityOrigin)
+{
+    if (!image()->hasSingleSecurityOrigin())
+        return false;
+    if (passesAccessControlCheck(securityOrigin))
+        return true;
+    return !securityOrigin->taintsCanvas(response().url());
+}
+
 } // namespace WebCore
index 3c16599..7b388b4 100644 (file)
@@ -40,6 +40,7 @@ class FloatSize;
 class MemoryCache;
 class RenderElement;
 class RenderObject;
+class SecurityOrigin;
 
 struct Length;
 
@@ -84,6 +85,8 @@ public:
     virtual void useDiskImageCache() OVERRIDE;
 #endif
 
+    bool isOriginClean(SecurityOrigin*);
+
 private:
     virtual void load(CachedResourceLoader*, const ResourceLoaderOptions&) OVERRIDE;
 
index c77cd1c..92b897b 100644 (file)
 #include "Shape.h"
 
 namespace WebCore {
+
+
+bool checkShapeImageOrigin(Document& document, CachedImage& cachedImage)
+{
+    if (cachedImage.isOriginClean(document.securityOrigin()))
+        return true;
+
+    const URL& url = cachedImage.url();
+    String urlString = url.isNull() ? "''" : url.stringCenterEllipsizedToLength();
+    document.addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, "Unsafe attempt to load URL " + urlString + ".");
+
+    return false;
+}
+
 template<class RenderType>
 const Shape* ShapeInfo<RenderType>::computedShape() const
 {
index 03e98b0..4a6c1e9 100644 (file)
@@ -109,6 +109,7 @@ protected:
     ShapeInfo(const RenderType* renderer): m_renderer(renderer) { }
 
     const Shape* computedShape() const;
+
     virtual LayoutRect computedShapeLogicalBoundingBox() const = 0;
     virtual ShapeValue* shapeValue() const = 0;
     virtual void getIntervals(LayoutUnit, LayoutUnit, SegmentList&) const = 0;
@@ -125,6 +126,9 @@ private:
     mutable OwnPtr<Shape> m_shape;
     LayoutSize m_shapeLogicalSize;
 };
+
+bool checkShapeImageOrigin(Document&, CachedImage&);
+
 }
 #endif
 #endif
index e552ae9..11e7ff2 100644 (file)
@@ -53,7 +53,7 @@ bool ShapeInsideInfo::isEnabledFor(const RenderBlock* renderer)
     case ShapeValue::Shape:
         return shapeValue->shape() && shapeValue->shape()->type() != BasicShape::BasicShapeInsetRectangleType;
     case ShapeValue::Image:
-        return shapeValue->isImageValid();
+        return shapeValue->isImageValid() && checkShapeImageOrigin(renderer->document(), *(shapeValue->image()->cachedImage()));
     default:
         return false;
     }
index 178b968..0463e9a 100644 (file)
@@ -48,7 +48,7 @@ bool ShapeOutsideInfo::isEnabledFor(const RenderBox* box)
     case ShapeValue::Shape:
         return shapeValue->shape();
     case ShapeValue::Image:
-        return shapeValue->isImageValid();
+        return shapeValue->isImageValid() && checkShapeImageOrigin(box->document(), *(shapeValue->image()->cachedImage()));
     default:
         return false;
     }