| /* |
| * Copyright 2010 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // Author: sligocki@google.com (Shawn Ligocki) |
| |
| #include "net/instaweb/rewriter/public/js_outline_filter.h" |
| |
| #include "net/instaweb/rewriter/public/debug_filter.h" |
| #include "net/instaweb/rewriter/public/rewrite_driver.h" |
| #include "net/instaweb/rewriter/public/rewrite_options.h" |
| #include "net/instaweb/rewriter/public/rewrite_test_base.h" |
| #include "net/instaweb/rewriter/public/support_noscript_filter.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/hasher.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/html/html_parse_test_base.h" |
| #include "pagespeed/kernel/http/content_type.h" |
| #include "pagespeed/kernel/http/response_headers.h" |
| |
| namespace net_instaweb { |
| |
| namespace { |
| |
| class JsOutlineFilterTest : public RewriteTestBase { |
| protected: |
| // We need an explicitly called method here rather than using SetUp so |
| // that NoOutlineScript can call another AddFilter function first. |
| void SetupOutliner() { |
| options()->set_js_outline_min_bytes(0); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kOutlineJavascript); |
| rewrite_driver()->AddFilters(); |
| } |
| |
| void SetupDebug(StringPiece debug_message) { |
| options()->EnableFilter(RewriteOptions::kDebug); |
| SetupOutliner(); |
| |
| // For some reason SupportNoScript filter is disabled here. |
| StringVector expected_disabled_filters; |
| SupportNoscriptFilter support_noscript_filter(rewrite_driver()); |
| expected_disabled_filters.push_back(support_noscript_filter.Name()); |
| |
| debug_message.CopyToString(&debug_message_); |
| debug_suffix_ = DebugFilter::FormatEndDocumentMessage( |
| 0, 0, 0, 0, 0, false, StringSet(), |
| expected_disabled_filters); |
| } |
| |
| // Test outlining scripts with options to write headers. |
| void OutlineScript(const StringPiece& id, bool expect_outline) { |
| GoogleString script_text = "FOOBAR"; |
| GoogleString outline_text; |
| AppendDefaultHeaders(kContentTypeJavascript, &outline_text); |
| outline_text += script_text; |
| |
| GoogleString hash = hasher()->Hash(script_text); |
| GoogleString outline_url = Encode( |
| kTestDomain, JsOutlineFilter::kFilterId, hash, "_", "js"); |
| |
| GoogleString wrong_hash_outline_url = Encode( |
| kTestDomain, JsOutlineFilter::kFilterId, StrCat("not", hash), |
| "_", "js"); |
| |
| GoogleString html_input = StrCat( |
| "<head>\n" |
| " <title>Example style outline</title>\n" |
| " <!-- Script starts here -->\n" |
| " <script type='text/javascript'>", script_text, "</script>\n" |
| " <!-- Script ends here -->\n" |
| "</head>"); |
| GoogleString expected_output; |
| if (expect_outline) { |
| expected_output = StrCat( |
| "<head>\n" |
| " <title>Example style outline</title>\n" |
| " <!-- Script starts here -->\n" |
| " <script type='text/javascript'" |
| " src=\"", outline_url, "\"></script>\n" |
| " <!-- Script ends here -->\n" |
| "</head>"); |
| } else { |
| expected_output = StrCat( |
| "<head>\n" |
| " <title>Example style outline</title>\n" |
| " <!-- Script starts here -->\n" |
| " <script type='text/javascript'>", script_text, "</script>", |
| debug_message_, |
| "\n" |
| " <!-- Script ends here -->\n" |
| "</head>"); |
| } |
| Parse(id, html_input); |
| EXPECT_HAS_SUBSTR(expected_output, output_buffer_); |
| if (!debug_suffix_.empty()) { |
| EXPECT_HAS_SUBSTR(debug_suffix_, output_buffer_); |
| } |
| |
| if (expect_outline) { |
| GoogleString actual_outline; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResourceUrl(outline_url, &actual_outline, &headers)); |
| EXPECT_EQ(outline_text, StrCat(headers.ToString(), actual_outline)); |
| |
| // Make sure we don't try anything funny with fallbacks if the hash |
| // given is wrong. It'd be an attack vector otherwise since outlined |
| // resources may contain things from private pages. |
| EXPECT_FALSE( |
| FetchResourceUrl(wrong_hash_outline_url, &actual_outline, &headers)); |
| } |
| } |
| |
| GoogleString debug_message_; |
| GoogleString debug_suffix_; |
| }; |
| |
| // Tests for outlining scripts. |
| TEST_F(JsOutlineFilterTest, OutlineScript) { |
| SetupOutliner(); |
| OutlineScript("outline_scripts_no_hash", true); |
| } |
| |
| TEST_F(JsOutlineFilterTest, OutlineScriptMd5) { |
| UseMd5Hasher(); |
| SetupOutliner(); |
| OutlineScript("outline_scripts_md5", true); |
| } |
| |
| // Make sure we don't misplace things into domain of the base tag, |
| // as we may not be able to fetch from it. |
| // (The leaf in base href= also covers a previous check failure) |
| TEST_F(JsOutlineFilterTest, OutlineScriptWithBase) { |
| SetupOutliner(); |
| |
| const char kInput[] = |
| "<base href='http://cdn.example.com/file.html'><script>42;</script>"; |
| GoogleString expected_output = |
| StrCat("<base href='http://cdn.example.com/file.html'>", |
| "<script src=\"", |
| EncodeWithBase("http://cdn.example.com/", kTestDomain, |
| "jo", "0", "_", "js"), |
| "\"></script>"); |
| ValidateExpected("test.html", kInput, expected_output); |
| } |
| |
| // Negative test. |
| TEST_F(JsOutlineFilterTest, NoOutlineScript) { |
| GoogleString file_prefix = GTestTempDir() + "/no_outline"; |
| GoogleString url_prefix = "http://mysite/no_outline"; |
| |
| options()->SoftEnableFilterForTesting(RewriteOptions::kOutlineCss); |
| SetupOutliner(); |
| |
| static const char html_input[] = |
| "<head>\n" |
| " <title>Example style outline</title>\n" |
| " <!-- Script starts here -->\n" |
| " <script type='text/javascript' src='http://othersite/script.js'>" |
| "</script>\n" |
| " <!-- Script ends here -->\n" |
| "</head>"; |
| ValidateNoChanges("no_outline_script", html_input); |
| } |
| |
| |
| // By default we succeed at outlining. |
| TEST_F(JsOutlineFilterTest, UrlNotTooLong) { |
| SetupOutliner(); |
| OutlineScript("url_not_too_long", true); |
| } |
| |
| TEST_F(JsOutlineFilterTest, JsPreserveURL) { |
| options()->set_js_outline_min_bytes(0); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kOutlineJavascript); |
| options()->set_js_preserve_urls(true); |
| rewrite_driver()->AddFilters(); |
| OutlineScript("js_preserve_url", false); |
| } |
| |
| TEST_F(JsOutlineFilterTest, JsPreserveURLOff) { |
| options()->set_js_outline_min_bytes(0); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kOutlineJavascript); |
| options()->set_js_preserve_urls(false); |
| rewrite_driver()->AddFilters(); |
| OutlineScript("js_preserve_url_off", true); |
| } |
| |
| // But if we set max_url_size too small, it will fail cleanly. |
| TEST_F(JsOutlineFilterTest, UrlTooLong) { |
| options()->set_max_url_size(0); |
| SetupDebug(StrCat("<!--Rewritten URL too long: ", kTestDomain, |
| "_.pagespeed.jo.#.-->")); |
| OutlineScript("url_too_long", false); |
| } |
| |
| // Make sure we deal well with no Charactors() node between StartElement() |
| // and EndElement(). |
| TEST_F(JsOutlineFilterTest, EmptyScript) { |
| SetupOutliner(); |
| ValidateNoChanges("empty_script", "<script></script>"); |
| } |
| |
| // http://code.google.com/p/modpagespeed/issues/detail?id=416 |
| TEST_F(JsOutlineFilterTest, RewriteDomain) { |
| SetupOutliner(); |
| AddRewriteDomainMapping("cdn.com", kTestDomain); |
| |
| // Check that CSS gets outlined to the rewritten domain. |
| GoogleString expected_url = Encode("http://cdn.com/", "jo", "0", "_", "js"); |
| ValidateExpected("rewrite_domain", |
| "<script>alert('foo');</script>", |
| StrCat("<script src=\"", expected_url, "\"></script>")); |
| |
| // And check that it serves correctly from that domain. |
| GoogleString content; |
| ASSERT_TRUE(FetchResourceUrl(expected_url, &content)); |
| EXPECT_EQ("alert('foo');", content); |
| } |
| |
| } // namespace |
| |
| } // namespace net_instaweb |